From a64b97dd5ad709bde210745eaf609cbb23bbfd8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20M=C3=B6hlmann?= Date: Wed, 22 Mar 2023 15:29:41 +0200 Subject: [PATCH] fix: allow RFC3339 encoded time strings Fixes #292 --- pkg/oidc/types.go | 25 +++++++++++++++---- pkg/oidc/types_test.go | 56 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 5 deletions(-) diff --git a/pkg/oidc/types.go b/pkg/oidc/types.go index 1260798d..4ceef6fc 100644 --- a/pkg/oidc/types.go +++ b/pkg/oidc/types.go @@ -127,12 +127,27 @@ func (s SpaceDelimitedArray) Value() (driver.Value, error) { type Time time.Time -func (t *Time) UnmarshalJSON(data []byte) error { - var i int64 - if err := json.Unmarshal(data, &i); err != nil { - return err +func (ts *Time) UnmarshalJSON(data []byte) error { + var v any + if err := json.Unmarshal(data, &v); err != nil { + return fmt.Errorf("oidc.Time: %w", err) + } + switch x := v.(type) { + case float64: + *ts = Time(time.Unix(int64(x), 0)) + case string: + // Compatibility with Auth0: + // https://github.com/zitadel/oidc/issues/292 + tt, err := time.Parse(time.RFC3339, x) + if err != nil { + return fmt.Errorf("oidc.Time: %w", err) + } + *ts = Time(tt.Round(time.Second)) + case nil: + *ts = Time{} + default: + return fmt.Errorf("oidc.Time: unable to parse type %T with value %v", x, x) } - *t = Time(time.Unix(i, 0).UTC()) return nil } diff --git a/pkg/oidc/types_test.go b/pkg/oidc/types_test.go index 6c62c409..653840ca 100644 --- a/pkg/oidc/types_test.go +++ b/pkg/oidc/types_test.go @@ -6,8 +6,10 @@ import ( "strconv" "strings" "testing" + "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "golang.org/x/text/language" ) @@ -335,3 +337,57 @@ func TestSpaceDelimitatedArray_ValuerNil(t *testing.T) { assert.Equal(t, SpaceDelimitedArray(nil), reversed, "scan nil") } } + +func TestTime_UnmarshalJSON(t *testing.T) { + type dst struct { + UpdatedAt Time `json:"updated_at"` + } + tests := []struct { + name string + json string + want dst + wantErr bool + }{ + { + name: "RFC3339", // https://github.com/zitadel/oidc/issues/292 + json: `{"updated_at": "2021-05-11T21:13:25.566Z"}`, + want: dst{UpdatedAt: Time(time.Unix(1620767606, 0))}, + }, + { + name: "int", + json: `{"updated_at":1620767606}`, + want: dst{UpdatedAt: Time(time.Unix(1620767606, 0))}, + }, + { + name: "time parse error", + json: `{"updated_at":"foo"}`, + wantErr: true, + }, + { + name: "null", + json: `{"updated_at":null}`, + }, + { + name: "invalid type", + json: `{"updated_at":["foo","bar"]}`, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var got dst + err := json.Unmarshal([]byte(tt.json), &got) + if tt.wantErr { + assert.Error(t, err) + } else { + require.NoError(t, err) + } + assert.WithinDuration(t, time.Time(tt.want.UpdatedAt), time.Time(got.UpdatedAt), 0) + }) + } + t.Run("syntax error", func(t *testing.T) { + var ts Time + err := ts.UnmarshalJSON([]byte{'~'}) + assert.Error(t, err) + }) +}