From f2d0ddeffeaddf4106ae41a4b6814be2912d598a Mon Sep 17 00:00:00 2001 From: Kolawole Segun Date: Mon, 6 Dec 2021 10:13:51 -0600 Subject: [PATCH 1/9] feat: port clockskew support The approach is similar to that used by the golang mongodb driver to maintain backwards compatibility --- claims.go | 63 ++++++++++++++++++++++++++++++------------------ claims_option.go | 31 ++++++++++++++++++++++++ map_claims.go | 32 +++++++++++++++--------- parser.go | 4 ++- parser_option.go | 8 ++++++ parser_test.go | 40 ++++++++++++++++++++++++++++++ 6 files changed, 143 insertions(+), 35 deletions(-) create mode 100644 claims_option.go diff --git a/claims.go b/claims.go index 41cc8265..99c53171 100644 --- a/claims.go +++ b/claims.go @@ -9,7 +9,7 @@ import ( // Claims must just have a Valid method that determines // if the token is invalid for any supported reason type Claims interface { - Valid() error + Valid(options ...*ClaimsValidationOptions) error } // RegisteredClaims are a structured version of the JWT Claims Set, @@ -48,13 +48,13 @@ type RegisteredClaims struct { // There is no accounting for clock skew. // As well, if any of the above claims are not in the token, it will still // be considered a valid claim. -func (c RegisteredClaims) Valid() error { +func (c RegisteredClaims) Valid(opts ...*ClaimsValidationOptions) error { vErr := new(ValidationError) now := TimeFunc() // The claims below are optional, by default, so if they are set to the // default value in Go, let's not fail the verification for them. - if !c.VerifyExpiresAt(now, false) { + if !c.VerifyExpiresAt(now, false, opts...) { delta := now.Sub(c.ExpiresAt.Time) vErr.Inner = fmt.Errorf("token is expired by %v", delta) vErr.Errors |= ValidationErrorExpired @@ -65,7 +65,7 @@ func (c RegisteredClaims) Valid() error { vErr.Errors |= ValidationErrorIssuedAt } - if !c.VerifyNotBefore(now, false) { + if !c.VerifyNotBefore(now, false, opts...) { vErr.Inner = fmt.Errorf("token is not valid yet") vErr.Errors |= ValidationErrorNotValidYet } @@ -85,12 +85,16 @@ func (c *RegisteredClaims) VerifyAudience(cmp string, req bool) bool { // VerifyExpiresAt compares the exp claim against cmp (cmp < exp). // If req is false, it will return true, if exp is unset. -func (c *RegisteredClaims) VerifyExpiresAt(cmp time.Time, req bool) bool { +func (c *RegisteredClaims) VerifyExpiresAt(cmp time.Time, req bool, opts ...*ClaimsValidationOptions) bool { + var s time.Duration + if len(opts) > 0 && opts[0] != nil { + s = opts[0].Leeway + } if c.ExpiresAt == nil { - return verifyExp(nil, cmp, req) + return verifyExp(nil, cmp, req, s) } - return verifyExp(&c.ExpiresAt.Time, cmp, req) + return verifyExp(&c.ExpiresAt.Time, cmp, req, s) } // VerifyIssuedAt compares the iat claim against cmp (cmp >= iat). @@ -105,12 +109,16 @@ func (c *RegisteredClaims) VerifyIssuedAt(cmp time.Time, req bool) bool { // VerifyNotBefore compares the nbf claim against cmp (cmp >= nbf). // If req is false, it will return true, if nbf is unset. -func (c *RegisteredClaims) VerifyNotBefore(cmp time.Time, req bool) bool { +func (c *RegisteredClaims) VerifyNotBefore(cmp time.Time, req bool, opts ...*ClaimsValidationOptions) bool { + var s time.Duration + if len(opts) > 0 && opts[0] != nil { + s = opts[0].Leeway + } if c.NotBefore == nil { - return verifyNbf(nil, cmp, req) + return verifyNbf(nil, cmp, req, s) } - return verifyNbf(&c.NotBefore.Time, cmp, req) + return verifyNbf(&c.NotBefore.Time, cmp, req, s) } // VerifyIssuer compares the iss claim against cmp. @@ -141,13 +149,13 @@ type StandardClaims struct { // Valid validates time based claims "exp, iat, nbf". There is no accounting for clock skew. // As well, if any of the above claims are not in the token, it will still // be considered a valid claim. -func (c StandardClaims) Valid() error { +func (c StandardClaims) Valid(opts ...*ClaimsValidationOptions) error { vErr := new(ValidationError) now := TimeFunc().Unix() // The claims below are optional, by default, so if they are set to the // default value in Go, let's not fail the verification for them. - if !c.VerifyExpiresAt(now, false) { + if !c.VerifyExpiresAt(now, false, opts...) { delta := time.Unix(now, 0).Sub(time.Unix(c.ExpiresAt, 0)) vErr.Inner = fmt.Errorf("token is expired by %v", delta) vErr.Errors |= ValidationErrorExpired @@ -158,7 +166,7 @@ func (c StandardClaims) Valid() error { vErr.Errors |= ValidationErrorIssuedAt } - if !c.VerifyNotBefore(now, false) { + if !c.VerifyNotBefore(now, false, opts...) { vErr.Inner = fmt.Errorf("token is not valid yet") vErr.Errors |= ValidationErrorNotValidYet } @@ -178,13 +186,17 @@ func (c *StandardClaims) VerifyAudience(cmp string, req bool) bool { // VerifyExpiresAt compares the exp claim against cmp (cmp < exp). // If req is false, it will return true, if exp is unset. -func (c *StandardClaims) VerifyExpiresAt(cmp int64, req bool) bool { +func (c *StandardClaims) VerifyExpiresAt(cmp int64, req bool, opts ...*ClaimsValidationOptions) bool { + var s time.Duration + if len(opts) > 0 && opts[0] != nil { + s = opts[0].Leeway + } if c.ExpiresAt == 0 { - return verifyExp(nil, time.Unix(cmp, 0), req) + return verifyExp(nil, time.Unix(cmp, 0), req, s) } t := time.Unix(c.ExpiresAt, 0) - return verifyExp(&t, time.Unix(cmp, 0), req) + return verifyExp(&t, time.Unix(cmp, 0), req, s) } // VerifyIssuedAt compares the iat claim against cmp (cmp >= iat). @@ -200,13 +212,17 @@ func (c *StandardClaims) VerifyIssuedAt(cmp int64, req bool) bool { // VerifyNotBefore compares the nbf claim against cmp (cmp >= nbf). // If req is false, it will return true, if nbf is unset. -func (c *StandardClaims) VerifyNotBefore(cmp int64, req bool) bool { +func (c *StandardClaims) VerifyNotBefore(cmp int64, req bool, opts ...*ClaimsValidationOptions) bool { + var s time.Duration + if len(opts) > 0 && opts[0] != nil { + s = opts[0].Leeway + } if c.NotBefore == 0 { - return verifyNbf(nil, time.Unix(cmp, 0), req) + return verifyNbf(nil, time.Unix(cmp, 0), req, s) } t := time.Unix(c.NotBefore, 0) - return verifyNbf(&t, time.Unix(cmp, 0), req) + return verifyNbf(&t, time.Unix(cmp, 0), req, s) } // VerifyIssuer compares the iss claim against cmp. @@ -240,11 +256,11 @@ func verifyAud(aud []string, cmp string, required bool) bool { return result } -func verifyExp(exp *time.Time, now time.Time, required bool) bool { +func verifyExp(exp *time.Time, now time.Time, required bool, clockSkew time.Duration) bool { if exp == nil { return !required } - return now.Before(*exp) + return now.Before((*exp).Add(+clockSkew)) } func verifyIat(iat *time.Time, now time.Time, required bool) bool { @@ -254,11 +270,12 @@ func verifyIat(iat *time.Time, now time.Time, required bool) bool { return now.After(*iat) || now.Equal(*iat) } -func verifyNbf(nbf *time.Time, now time.Time, required bool) bool { +func verifyNbf(nbf *time.Time, now time.Time, required bool, clockSkew time.Duration) bool { if nbf == nil { return !required } - return now.After(*nbf) || now.Equal(*nbf) + t := (*nbf).Add(-clockSkew) + return now.After(t) || now.Equal(t) } func verifyIss(iss string, cmp string, required bool) bool { diff --git a/claims_option.go b/claims_option.go new file mode 100644 index 00000000..b9396ad7 --- /dev/null +++ b/claims_option.go @@ -0,0 +1,31 @@ +package jwt + +import "time" + +// ClaimsValidationOptions represents options that can be used for claims validation +type ClaimsValidationOptions struct { + Leeway time.Duration +} + +func ClaimsValidation() *ClaimsValidationOptions { + return &ClaimsValidationOptions{} +} + +func (c *ClaimsValidationOptions) SetClockSkew(d time.Duration) { + c.Leeway = d +} + +// MergeClaimsValidationOptions combines the given ClaimsValidationOptions instancs into a single ClaimsValidationOptions +// in a last-one-wins fashion +func MergeClaimsValidationOptions(opts ...*ClaimsValidationOptions) *ClaimsValidationOptions { + c := ClaimsValidation() + for _, opt := range opts { + if opt == nil { + continue + } + if opt.Leeway != 0 { + c.Leeway = opt.Leeway + } + } + return c +} diff --git a/map_claims.go b/map_claims.go index e7da633b..4f4b2487 100644 --- a/map_claims.go +++ b/map_claims.go @@ -34,7 +34,7 @@ func (m MapClaims) VerifyAudience(cmp string, req bool) bool { // VerifyExpiresAt compares the exp claim against cmp (cmp <= exp). // If req is false, it will return true, if exp is unset. -func (m MapClaims) VerifyExpiresAt(cmp int64, req bool) bool { +func (m MapClaims) VerifyExpiresAt(cmp int64, req bool, opts ...*ClaimsValidationOptions) bool { cmpTime := time.Unix(cmp, 0) v, ok := m["exp"] @@ -42,17 +42,22 @@ func (m MapClaims) VerifyExpiresAt(cmp int64, req bool) bool { return !req } + var s time.Duration + if len(opts) > 0 && opts[0] != nil { + s = opts[0].Leeway + } + switch exp := v.(type) { case float64: if exp == 0 { - return verifyExp(nil, cmpTime, req) + return verifyExp(nil, cmpTime, req, s) } - return verifyExp(&newNumericDateFromSeconds(exp).Time, cmpTime, req) + return verifyExp(&newNumericDateFromSeconds(exp).Time, cmpTime, req, s) case json.Number: v, _ := exp.Float64() - return verifyExp(&newNumericDateFromSeconds(v).Time, cmpTime, req) + return verifyExp(&newNumericDateFromSeconds(v).Time, cmpTime, req, s) } return false @@ -86,7 +91,7 @@ func (m MapClaims) VerifyIssuedAt(cmp int64, req bool) bool { // VerifyNotBefore compares the nbf claim against cmp (cmp >= nbf). // If req is false, it will return true, if nbf is unset. -func (m MapClaims) VerifyNotBefore(cmp int64, req bool) bool { +func (m MapClaims) VerifyNotBefore(cmp int64, req bool, opts ...*ClaimsValidationOptions) bool { cmpTime := time.Unix(cmp, 0) v, ok := m["nbf"] @@ -94,17 +99,22 @@ func (m MapClaims) VerifyNotBefore(cmp int64, req bool) bool { return !req } + var s time.Duration + if len(opts) > 0 && opts[0] != nil { + s = opts[0].Leeway + } + switch nbf := v.(type) { case float64: if nbf == 0 { - return verifyNbf(nil, cmpTime, req) + return verifyNbf(nil, cmpTime, req, s) } - return verifyNbf(&newNumericDateFromSeconds(nbf).Time, cmpTime, req) + return verifyNbf(&newNumericDateFromSeconds(nbf).Time, cmpTime, req, s) case json.Number: v, _ := nbf.Float64() - return verifyNbf(&newNumericDateFromSeconds(v).Time, cmpTime, req) + return verifyNbf(&newNumericDateFromSeconds(v).Time, cmpTime, req, s) } return false @@ -121,11 +131,11 @@ func (m MapClaims) VerifyIssuer(cmp string, req bool) bool { // There is no accounting for clock skew. // As well, if any of the above claims are not in the token, it will still // be considered a valid claim. -func (m MapClaims) Valid() error { +func (m MapClaims) Valid(opts ...*ClaimsValidationOptions) error { vErr := new(ValidationError) now := TimeFunc().Unix() - if !m.VerifyExpiresAt(now, false) { + if !m.VerifyExpiresAt(now, false, opts...) { vErr.Inner = errors.New("Token is expired") vErr.Errors |= ValidationErrorExpired } @@ -135,7 +145,7 @@ func (m MapClaims) Valid() error { vErr.Errors |= ValidationErrorIssuedAt } - if !m.VerifyNotBefore(now, false) { + if !m.VerifyNotBefore(now, false, opts...) { vErr.Inner = errors.New("Token is not valid yet") vErr.Errors |= ValidationErrorNotValidYet } diff --git a/parser.go b/parser.go index 2f61a69d..b35506eb 100644 --- a/parser.go +++ b/parser.go @@ -22,6 +22,8 @@ type Parser struct { // // Deprecated: In future releases, this field will not be exported anymore and should be set with an option to NewParser instead. SkipClaimsValidation bool + + options []*ClaimsValidationOptions } // NewParser creates a new Parser with the specified options @@ -82,7 +84,7 @@ func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyf // Validate Claims if !p.SkipClaimsValidation { - if err := token.Claims.Valid(); err != nil { + if err := token.Claims.Valid(MergeClaimsValidationOptions(p.options...)); err != nil { // If the Claims Valid returned an error, check if it is a validation error, // If it was another error type, create a ValidationError with a generic ClaimsInvalid flag set diff --git a/parser_option.go b/parser_option.go index 0fede4f1..dbd0d19f 100644 --- a/parser_option.go +++ b/parser_option.go @@ -1,5 +1,7 @@ package jwt +import "time" + // ParserOption is used to implement functional-style options that modify the behaviour of the parser. To add // new options, just create a function (ideally beginning with With or Without) that returns an anonymous function that // takes a *Parser type as input and manipulates its configuration accordingly. @@ -27,3 +29,9 @@ func WithoutClaimsValidation() ParserOption { p.SkipClaimsValidation = true } } + +func WithLeeway(d time.Duration) ParserOption { + return func(p *Parser) { + p.options = append(p.options, &ClaimsValidationOptions{Leeway: d}) + } +} diff --git a/parser_test.go b/parser_test.go index 7a7bf0ab..46ab9bd9 100644 --- a/parser_test.go +++ b/parser_test.go @@ -74,6 +74,26 @@ var jwtTestData = []struct { nil, jwt.SigningMethodRS256, }, + { + "basic expired with 60s skew", + "", // autogen + defaultKeyFunc, + jwt.MapClaims{"foo": "bar", "exp": float64(time.Now().Unix() - 100)}, + false, + jwt.ValidationErrorExpired, + jwt.NewParser(jwt.WithLeeway(time.Minute)), + jwt.SigningMethodRS256, + }, + { + "basic expired with 120s skew", + "", // autogen + defaultKeyFunc, + jwt.MapClaims{"foo": "bar", "exp": float64(time.Now().Unix() - 100)}, + true, + 0, + jwt.NewParser(jwt.WithLeeway(2 * time.Minute)), + jwt.SigningMethodRS256, + }, { "basic nbf", "", // autogen @@ -84,6 +104,26 @@ var jwtTestData = []struct { nil, jwt.SigningMethodRS256, }, + { + "basic nbf with 60s skew", + "", // autogen + defaultKeyFunc, + jwt.MapClaims{"foo": "bar", "nbf": float64(time.Now().Unix() + 100)}, + false, + jwt.ValidationErrorNotValidYet, + jwt.NewParser(jwt.WithLeeway(time.Minute)), + jwt.SigningMethodRS256, + }, + { + "basic nbf with 120s skew", + "", // autogen + defaultKeyFunc, + jwt.MapClaims{"foo": "bar", "nbf": float64(time.Now().Unix() + 100)}, + true, + 0, + jwt.NewParser(jwt.WithLeeway(2 * time.Minute)), + jwt.SigningMethodRS256, + }, { "expired and nbf", "", // autogen From 0a2022f998221e52d168edc68f90c5729c9048fc Mon Sep 17 00:00:00 2001 From: Kolawole Segun Date: Tue, 4 Jan 2022 08:01:09 -0600 Subject: [PATCH 2/9] rename ClaimsValidationOptions to ValidatorOptions --- claims.go | 24 +++++++++++++----------- claims_option.go | 31 ------------------------------- map_claims.go | 16 +++++++++------- parser.go | 5 ++--- parser_option.go | 2 +- 5 files changed, 25 insertions(+), 53 deletions(-) delete mode 100644 claims_option.go diff --git a/claims.go b/claims.go index 99c53171..fb052401 100644 --- a/claims.go +++ b/claims.go @@ -9,7 +9,7 @@ import ( // Claims must just have a Valid method that determines // if the token is invalid for any supported reason type Claims interface { - Valid(options ...*ClaimsValidationOptions) error + Valid(options ...*ValidatorOptions) error } // RegisteredClaims are a structured version of the JWT Claims Set, @@ -48,7 +48,7 @@ type RegisteredClaims struct { // There is no accounting for clock skew. // As well, if any of the above claims are not in the token, it will still // be considered a valid claim. -func (c RegisteredClaims) Valid(opts ...*ClaimsValidationOptions) error { +func (c RegisteredClaims) Valid(opts ...*ValidatorOptions) error { vErr := new(ValidationError) now := TimeFunc() @@ -85,10 +85,11 @@ func (c *RegisteredClaims) VerifyAudience(cmp string, req bool) bool { // VerifyExpiresAt compares the exp claim against cmp (cmp < exp). // If req is false, it will return true, if exp is unset. -func (c *RegisteredClaims) VerifyExpiresAt(cmp time.Time, req bool, opts ...*ClaimsValidationOptions) bool { +func (c *RegisteredClaims) VerifyExpiresAt(cmp time.Time, req bool, opts ...*ValidatorOptions) bool { var s time.Duration - if len(opts) > 0 && opts[0] != nil { - s = opts[0].Leeway + o := MergeValidatorOptions(opts...) + if o != nil { + s = o.Leeway } if c.ExpiresAt == nil { return verifyExp(nil, cmp, req, s) @@ -109,10 +110,11 @@ func (c *RegisteredClaims) VerifyIssuedAt(cmp time.Time, req bool) bool { // VerifyNotBefore compares the nbf claim against cmp (cmp >= nbf). // If req is false, it will return true, if nbf is unset. -func (c *RegisteredClaims) VerifyNotBefore(cmp time.Time, req bool, opts ...*ClaimsValidationOptions) bool { +func (c *RegisteredClaims) VerifyNotBefore(cmp time.Time, req bool, opts ...*ValidatorOptions) bool { var s time.Duration - if len(opts) > 0 && opts[0] != nil { - s = opts[0].Leeway + o := MergeValidatorOptions(opts...) + if o != nil { + s = o.Leeway } if c.NotBefore == nil { return verifyNbf(nil, cmp, req, s) @@ -149,7 +151,7 @@ type StandardClaims struct { // Valid validates time based claims "exp, iat, nbf". There is no accounting for clock skew. // As well, if any of the above claims are not in the token, it will still // be considered a valid claim. -func (c StandardClaims) Valid(opts ...*ClaimsValidationOptions) error { +func (c StandardClaims) Valid(opts ...*ValidatorOptions) error { vErr := new(ValidationError) now := TimeFunc().Unix() @@ -186,7 +188,7 @@ func (c *StandardClaims) VerifyAudience(cmp string, req bool) bool { // VerifyExpiresAt compares the exp claim against cmp (cmp < exp). // If req is false, it will return true, if exp is unset. -func (c *StandardClaims) VerifyExpiresAt(cmp int64, req bool, opts ...*ClaimsValidationOptions) bool { +func (c *StandardClaims) VerifyExpiresAt(cmp int64, req bool, opts ...*ValidatorOptions) bool { var s time.Duration if len(opts) > 0 && opts[0] != nil { s = opts[0].Leeway @@ -212,7 +214,7 @@ func (c *StandardClaims) VerifyIssuedAt(cmp int64, req bool) bool { // VerifyNotBefore compares the nbf claim against cmp (cmp >= nbf). // If req is false, it will return true, if nbf is unset. -func (c *StandardClaims) VerifyNotBefore(cmp int64, req bool, opts ...*ClaimsValidationOptions) bool { +func (c *StandardClaims) VerifyNotBefore(cmp int64, req bool, opts ...*ValidatorOptions) bool { var s time.Duration if len(opts) > 0 && opts[0] != nil { s = opts[0].Leeway diff --git a/claims_option.go b/claims_option.go deleted file mode 100644 index b9396ad7..00000000 --- a/claims_option.go +++ /dev/null @@ -1,31 +0,0 @@ -package jwt - -import "time" - -// ClaimsValidationOptions represents options that can be used for claims validation -type ClaimsValidationOptions struct { - Leeway time.Duration -} - -func ClaimsValidation() *ClaimsValidationOptions { - return &ClaimsValidationOptions{} -} - -func (c *ClaimsValidationOptions) SetClockSkew(d time.Duration) { - c.Leeway = d -} - -// MergeClaimsValidationOptions combines the given ClaimsValidationOptions instancs into a single ClaimsValidationOptions -// in a last-one-wins fashion -func MergeClaimsValidationOptions(opts ...*ClaimsValidationOptions) *ClaimsValidationOptions { - c := ClaimsValidation() - for _, opt := range opts { - if opt == nil { - continue - } - if opt.Leeway != 0 { - c.Leeway = opt.Leeway - } - } - return c -} diff --git a/map_claims.go b/map_claims.go index 4f4b2487..e91b8e81 100644 --- a/map_claims.go +++ b/map_claims.go @@ -34,7 +34,7 @@ func (m MapClaims) VerifyAudience(cmp string, req bool) bool { // VerifyExpiresAt compares the exp claim against cmp (cmp <= exp). // If req is false, it will return true, if exp is unset. -func (m MapClaims) VerifyExpiresAt(cmp int64, req bool, opts ...*ClaimsValidationOptions) bool { +func (m MapClaims) VerifyExpiresAt(cmp int64, req bool, opts ...*ValidatorOptions) bool { cmpTime := time.Unix(cmp, 0) v, ok := m["exp"] @@ -43,8 +43,9 @@ func (m MapClaims) VerifyExpiresAt(cmp int64, req bool, opts ...*ClaimsValidatio } var s time.Duration - if len(opts) > 0 && opts[0] != nil { - s = opts[0].Leeway + o := MergeValidatorOptions(opts...) + if o != nil { + s = o.Leeway } switch exp := v.(type) { @@ -91,7 +92,7 @@ func (m MapClaims) VerifyIssuedAt(cmp int64, req bool) bool { // VerifyNotBefore compares the nbf claim against cmp (cmp >= nbf). // If req is false, it will return true, if nbf is unset. -func (m MapClaims) VerifyNotBefore(cmp int64, req bool, opts ...*ClaimsValidationOptions) bool { +func (m MapClaims) VerifyNotBefore(cmp int64, req bool, opts ...*ValidatorOptions) bool { cmpTime := time.Unix(cmp, 0) v, ok := m["nbf"] @@ -100,8 +101,9 @@ func (m MapClaims) VerifyNotBefore(cmp int64, req bool, opts ...*ClaimsValidatio } var s time.Duration - if len(opts) > 0 && opts[0] != nil { - s = opts[0].Leeway + o := MergeValidatorOptions(opts...) + if o != nil { + s = o.Leeway } switch nbf := v.(type) { @@ -131,7 +133,7 @@ func (m MapClaims) VerifyIssuer(cmp string, req bool) bool { // There is no accounting for clock skew. // As well, if any of the above claims are not in the token, it will still // be considered a valid claim. -func (m MapClaims) Valid(opts ...*ClaimsValidationOptions) error { +func (m MapClaims) Valid(opts ...*ValidatorOptions) error { vErr := new(ValidationError) now := TimeFunc().Unix() diff --git a/parser.go b/parser.go index b35506eb..c1248bf8 100644 --- a/parser.go +++ b/parser.go @@ -23,7 +23,7 @@ type Parser struct { // Deprecated: In future releases, this field will not be exported anymore and should be set with an option to NewParser instead. SkipClaimsValidation bool - options []*ClaimsValidationOptions + options []*ValidatorOptions } // NewParser creates a new Parser with the specified options @@ -84,8 +84,7 @@ func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyf // Validate Claims if !p.SkipClaimsValidation { - if err := token.Claims.Valid(MergeClaimsValidationOptions(p.options...)); err != nil { - + if err := token.Claims.Valid(p.options...); err != nil { // If the Claims Valid returned an error, check if it is a validation error, // If it was another error type, create a ValidationError with a generic ClaimsInvalid flag set if e, ok := err.(*ValidationError); !ok { diff --git a/parser_option.go b/parser_option.go index dbd0d19f..7077b0e8 100644 --- a/parser_option.go +++ b/parser_option.go @@ -32,6 +32,6 @@ func WithoutClaimsValidation() ParserOption { func WithLeeway(d time.Duration) ParserOption { return func(p *Parser) { - p.options = append(p.options, &ClaimsValidationOptions{Leeway: d}) + p.options = append(p.options, &ValidatorOptions{Leeway: d}) } } From e9c5bc003a15e8b87eada4c1e78ec97efb298152 Mon Sep 17 00:00:00 2001 From: Kolawole Segun Date: Tue, 4 Jan 2022 08:01:57 -0600 Subject: [PATCH 3/9] add renamed file --- validator_option.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 validator_option.go diff --git a/validator_option.go b/validator_option.go new file mode 100644 index 00000000..5ff7c07c --- /dev/null +++ b/validator_option.go @@ -0,0 +1,27 @@ +package jwt + +import "time" + +// ValidatorOptions represents options that can be used for claims validation +type ValidatorOptions struct { + Leeway time.Duration +} + +func Validator() *ValidatorOptions { + return &ValidatorOptions{} +} + +// MergeValidatorOptions combines the given ValidatorOptions instances into a single ValidatorOptions +// in a last-one-wins fashion +func MergeValidatorOptions(opts ...*ValidatorOptions) *ValidatorOptions { + v := Validator() + for _, opt := range opts { + if opt == nil { + continue + } + if opt.Leeway != 0 { + v.Leeway = opt.Leeway + } + } + return v +} From 2ba43a18eec8e5a225e2824b587a412ae552c82a Mon Sep 17 00:00:00 2001 From: Kolawole Segun Date: Tue, 4 Jan 2022 08:31:24 -0600 Subject: [PATCH 4/9] update StandardClaims functions --- claims.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/claims.go b/claims.go index fb052401..fc39f42d 100644 --- a/claims.go +++ b/claims.go @@ -190,8 +190,9 @@ func (c *StandardClaims) VerifyAudience(cmp string, req bool) bool { // If req is false, it will return true, if exp is unset. func (c *StandardClaims) VerifyExpiresAt(cmp int64, req bool, opts ...*ValidatorOptions) bool { var s time.Duration - if len(opts) > 0 && opts[0] != nil { - s = opts[0].Leeway + o := MergeValidatorOptions(opts...) + if o != nil { + s = o.Leeway } if c.ExpiresAt == 0 { return verifyExp(nil, time.Unix(cmp, 0), req, s) @@ -216,8 +217,9 @@ func (c *StandardClaims) VerifyIssuedAt(cmp int64, req bool) bool { // If req is false, it will return true, if nbf is unset. func (c *StandardClaims) VerifyNotBefore(cmp int64, req bool, opts ...*ValidatorOptions) bool { var s time.Duration - if len(opts) > 0 && opts[0] != nil { - s = opts[0].Leeway + o := MergeValidatorOptions(opts...) + if o != nil { + s = o.Leeway } if c.NotBefore == 0 { return verifyNbf(nil, time.Unix(cmp, 0), req, s) From fc4da9d50e8428ed8f9c33fa7fbde32509656f1b Mon Sep 17 00:00:00 2001 From: Kolawole Segun Date: Tue, 4 Jan 2022 08:51:25 -0600 Subject: [PATCH 5/9] unexport leeway --- claims.go | 8 ++++---- map_claims.go | 4 ++-- parser_option.go | 3 ++- validator_option.go | 10 +++++++--- 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/claims.go b/claims.go index fc39f42d..e5fe225f 100644 --- a/claims.go +++ b/claims.go @@ -89,7 +89,7 @@ func (c *RegisteredClaims) VerifyExpiresAt(cmp time.Time, req bool, opts ...*Val var s time.Duration o := MergeValidatorOptions(opts...) if o != nil { - s = o.Leeway + s = o.leeway } if c.ExpiresAt == nil { return verifyExp(nil, cmp, req, s) @@ -114,7 +114,7 @@ func (c *RegisteredClaims) VerifyNotBefore(cmp time.Time, req bool, opts ...*Val var s time.Duration o := MergeValidatorOptions(opts...) if o != nil { - s = o.Leeway + s = o.leeway } if c.NotBefore == nil { return verifyNbf(nil, cmp, req, s) @@ -192,7 +192,7 @@ func (c *StandardClaims) VerifyExpiresAt(cmp int64, req bool, opts ...*Validator var s time.Duration o := MergeValidatorOptions(opts...) if o != nil { - s = o.Leeway + s = o.leeway } if c.ExpiresAt == 0 { return verifyExp(nil, time.Unix(cmp, 0), req, s) @@ -219,7 +219,7 @@ func (c *StandardClaims) VerifyNotBefore(cmp int64, req bool, opts ...*Validator var s time.Duration o := MergeValidatorOptions(opts...) if o != nil { - s = o.Leeway + s = o.leeway } if c.NotBefore == 0 { return verifyNbf(nil, time.Unix(cmp, 0), req, s) diff --git a/map_claims.go b/map_claims.go index e91b8e81..1a110919 100644 --- a/map_claims.go +++ b/map_claims.go @@ -45,7 +45,7 @@ func (m MapClaims) VerifyExpiresAt(cmp int64, req bool, opts ...*ValidatorOption var s time.Duration o := MergeValidatorOptions(opts...) if o != nil { - s = o.Leeway + s = o.leeway } switch exp := v.(type) { @@ -103,7 +103,7 @@ func (m MapClaims) VerifyNotBefore(cmp int64, req bool, opts ...*ValidatorOption var s time.Duration o := MergeValidatorOptions(opts...) if o != nil { - s = o.Leeway + s = o.leeway } switch nbf := v.(type) { diff --git a/parser_option.go b/parser_option.go index 7077b0e8..f16c5eab 100644 --- a/parser_option.go +++ b/parser_option.go @@ -30,8 +30,9 @@ func WithoutClaimsValidation() ParserOption { } } +// WithLeeway returns the ParserOption for specifying the leeway window. func WithLeeway(d time.Duration) ParserOption { return func(p *Parser) { - p.options = append(p.options, &ValidatorOptions{Leeway: d}) + p.options = append(p.options, &ValidatorOptions{leeway: d}) } } diff --git a/validator_option.go b/validator_option.go index 5ff7c07c..2ee80c47 100644 --- a/validator_option.go +++ b/validator_option.go @@ -4,13 +4,17 @@ import "time" // ValidatorOptions represents options that can be used for claims validation type ValidatorOptions struct { - Leeway time.Duration + leeway time.Duration // Leeway to provide when validating time values } func Validator() *ValidatorOptions { return &ValidatorOptions{} } +func (v *ValidatorOptions) SetLeeway(d time.Duration) { + v.leeway = d +} + // MergeValidatorOptions combines the given ValidatorOptions instances into a single ValidatorOptions // in a last-one-wins fashion func MergeValidatorOptions(opts ...*ValidatorOptions) *ValidatorOptions { @@ -19,8 +23,8 @@ func MergeValidatorOptions(opts ...*ValidatorOptions) *ValidatorOptions { if opt == nil { continue } - if opt.Leeway != 0 { - v.Leeway = opt.Leeway + if opt.leeway != 0 { + v.SetLeeway(opt.leeway) } } return v From 727b3aa0f56b53b2949e245dcd7dd21876018d2f Mon Sep 17 00:00:00 2001 From: Kolawole Segun Date: Tue, 4 Jan 2022 13:52:33 -0600 Subject: [PATCH 6/9] merge in valid function --- claims.go | 10 ++++++---- map_claims.go | 5 +++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/claims.go b/claims.go index e5fe225f..f37e0647 100644 --- a/claims.go +++ b/claims.go @@ -51,10 +51,11 @@ type RegisteredClaims struct { func (c RegisteredClaims) Valid(opts ...*ValidatorOptions) error { vErr := new(ValidationError) now := TimeFunc() + o := MergeValidatorOptions(opts...) // The claims below are optional, by default, so if they are set to the // default value in Go, let's not fail the verification for them. - if !c.VerifyExpiresAt(now, false, opts...) { + if !c.VerifyExpiresAt(now, false, o) { delta := now.Sub(c.ExpiresAt.Time) vErr.Inner = fmt.Errorf("token is expired by %v", delta) vErr.Errors |= ValidationErrorExpired @@ -65,7 +66,7 @@ func (c RegisteredClaims) Valid(opts ...*ValidatorOptions) error { vErr.Errors |= ValidationErrorIssuedAt } - if !c.VerifyNotBefore(now, false, opts...) { + if !c.VerifyNotBefore(now, false, o) { vErr.Inner = fmt.Errorf("token is not valid yet") vErr.Errors |= ValidationErrorNotValidYet } @@ -154,10 +155,11 @@ type StandardClaims struct { func (c StandardClaims) Valid(opts ...*ValidatorOptions) error { vErr := new(ValidationError) now := TimeFunc().Unix() + o := MergeValidatorOptions(opts...) // The claims below are optional, by default, so if they are set to the // default value in Go, let's not fail the verification for them. - if !c.VerifyExpiresAt(now, false, opts...) { + if !c.VerifyExpiresAt(now, false, o) { delta := time.Unix(now, 0).Sub(time.Unix(c.ExpiresAt, 0)) vErr.Inner = fmt.Errorf("token is expired by %v", delta) vErr.Errors |= ValidationErrorExpired @@ -168,7 +170,7 @@ func (c StandardClaims) Valid(opts ...*ValidatorOptions) error { vErr.Errors |= ValidationErrorIssuedAt } - if !c.VerifyNotBefore(now, false, opts...) { + if !c.VerifyNotBefore(now, false, o) { vErr.Inner = fmt.Errorf("token is not valid yet") vErr.Errors |= ValidationErrorNotValidYet } diff --git a/map_claims.go b/map_claims.go index 1a110919..729f4a61 100644 --- a/map_claims.go +++ b/map_claims.go @@ -136,8 +136,9 @@ func (m MapClaims) VerifyIssuer(cmp string, req bool) bool { func (m MapClaims) Valid(opts ...*ValidatorOptions) error { vErr := new(ValidationError) now := TimeFunc().Unix() + o := MergeValidatorOptions(opts...) - if !m.VerifyExpiresAt(now, false, opts...) { + if !m.VerifyExpiresAt(now, false, o) { vErr.Inner = errors.New("Token is expired") vErr.Errors |= ValidationErrorExpired } @@ -147,7 +148,7 @@ func (m MapClaims) Valid(opts ...*ValidatorOptions) error { vErr.Errors |= ValidationErrorIssuedAt } - if !m.VerifyNotBefore(now, false, opts...) { + if !m.VerifyNotBefore(now, false, o) { vErr.Inner = errors.New("Token is not valid yet") vErr.Errors |= ValidationErrorNotValidYet } From 64c4dc66f31a5b406119a8cd11e208b89da0ffc0 Mon Sep 17 00:00:00 2001 From: Kolawole Segun Date: Sun, 13 Feb 2022 14:10:13 -0600 Subject: [PATCH 7/9] convert validatior to functional style --- claims.go | 68 +++++++++++++++++++++------------------------ map_claims.go | 37 ++++++++++++------------ parser.go | 2 +- parser_option.go | 2 +- validator_option.go | 30 +++++++------------- 5 files changed, 60 insertions(+), 79 deletions(-) diff --git a/claims.go b/claims.go index 585fce01..5f419cb4 100644 --- a/claims.go +++ b/claims.go @@ -9,7 +9,7 @@ import ( // Claims must just have a Valid method that determines // if the token is invalid for any supported reason type Claims interface { - Valid(options ...*ValidatorOptions) error + Valid(options ...ValidatorOption) error } // RegisteredClaims are a structured version of the JWT Claims Set, @@ -48,14 +48,13 @@ type RegisteredClaims struct { // There is no accounting for clock skew. // As well, if any of the above claims are not in the token, it will still // be considered a valid claim. -func (c RegisteredClaims) Valid(opts ...*ValidatorOptions) error { +func (c RegisteredClaims) Valid(opts ...ValidatorOption) error { vErr := new(ValidationError) now := TimeFunc() - o := MergeValidatorOptions(opts...) // The claims below are optional, by default, so if they are set to the // default value in Go, let's not fail the verification for them. - if !c.VerifyExpiresAt(now, false, o) { + if !c.VerifyExpiresAt(now, false, opts...) { delta := now.Sub(c.ExpiresAt.Time) vErr.Inner = fmt.Errorf("%s by %v", delta, ErrTokenExpired) vErr.Errors |= ValidationErrorExpired @@ -66,7 +65,7 @@ func (c RegisteredClaims) Valid(opts ...*ValidatorOptions) error { vErr.Errors |= ValidationErrorIssuedAt } - if !c.VerifyNotBefore(now, false, o) { + if !c.VerifyNotBefore(now, false, opts...) { vErr.Inner = ErrTokenNotValidYet vErr.Errors |= ValidationErrorNotValidYet } @@ -86,17 +85,16 @@ func (c *RegisteredClaims) VerifyAudience(cmp string, req bool) bool { // VerifyExpiresAt compares the exp claim against cmp (cmp < exp). // If req is false, it will return true, if exp is unset. -func (c *RegisteredClaims) VerifyExpiresAt(cmp time.Time, req bool, opts ...*ValidatorOptions) bool { - var s time.Duration - o := MergeValidatorOptions(opts...) - if o != nil { - s = o.leeway +func (c *RegisteredClaims) VerifyExpiresAt(cmp time.Time, req bool, opts ...ValidatorOption) bool { + validator := ValidatorOptions{} + for _, o := range opts { + o(&validator) } if c.ExpiresAt == nil { - return verifyExp(nil, cmp, req, s) + return verifyExp(nil, cmp, req, validator.leeway) } - return verifyExp(&c.ExpiresAt.Time, cmp, req, s) + return verifyExp(&c.ExpiresAt.Time, cmp, req, validator.leeway) } // VerifyIssuedAt compares the iat claim against cmp (cmp >= iat). @@ -111,17 +109,16 @@ func (c *RegisteredClaims) VerifyIssuedAt(cmp time.Time, req bool) bool { // VerifyNotBefore compares the nbf claim against cmp (cmp >= nbf). // If req is false, it will return true, if nbf is unset. -func (c *RegisteredClaims) VerifyNotBefore(cmp time.Time, req bool, opts ...*ValidatorOptions) bool { - var s time.Duration - o := MergeValidatorOptions(opts...) - if o != nil { - s = o.leeway +func (c *RegisteredClaims) VerifyNotBefore(cmp time.Time, req bool, opts ...ValidatorOption) bool { + validator := ValidatorOptions{} + for _, o := range opts { + o(&validator) } if c.NotBefore == nil { - return verifyNbf(nil, cmp, req, s) + return verifyNbf(nil, cmp, req, validator.leeway) } - return verifyNbf(&c.NotBefore.Time, cmp, req, s) + return verifyNbf(&c.NotBefore.Time, cmp, req, validator.leeway) } // VerifyIssuer compares the iss claim against cmp. @@ -152,14 +149,13 @@ type StandardClaims struct { // Valid validates time based claims "exp, iat, nbf". There is no accounting for clock skew. // As well, if any of the above claims are not in the token, it will still // be considered a valid claim. -func (c StandardClaims) Valid(opts ...*ValidatorOptions) error { +func (c StandardClaims) Valid(opts ...ValidatorOption) error { vErr := new(ValidationError) now := TimeFunc().Unix() - o := MergeValidatorOptions(opts...) // The claims below are optional, by default, so if they are set to the // default value in Go, let's not fail the verification for them. - if !c.VerifyExpiresAt(now, false, o) { + if !c.VerifyExpiresAt(now, false, opts...) { delta := time.Unix(now, 0).Sub(time.Unix(c.ExpiresAt, 0)) vErr.Inner = fmt.Errorf("%s by %v", delta, ErrTokenExpired) vErr.Errors |= ValidationErrorExpired @@ -170,7 +166,7 @@ func (c StandardClaims) Valid(opts ...*ValidatorOptions) error { vErr.Errors |= ValidationErrorIssuedAt } - if !c.VerifyNotBefore(now, false, o) { + if !c.VerifyNotBefore(now, false, opts...) { vErr.Inner = ErrTokenNotValidYet vErr.Errors |= ValidationErrorNotValidYet } @@ -190,18 +186,17 @@ func (c *StandardClaims) VerifyAudience(cmp string, req bool) bool { // VerifyExpiresAt compares the exp claim against cmp (cmp < exp). // If req is false, it will return true, if exp is unset. -func (c *StandardClaims) VerifyExpiresAt(cmp int64, req bool, opts ...*ValidatorOptions) bool { - var s time.Duration - o := MergeValidatorOptions(opts...) - if o != nil { - s = o.leeway +func (c *StandardClaims) VerifyExpiresAt(cmp int64, req bool, opts ...ValidatorOption) bool { + validator := ValidatorOptions{} + for _, o := range opts { + o(&validator) } if c.ExpiresAt == 0 { - return verifyExp(nil, time.Unix(cmp, 0), req, s) + return verifyExp(nil, time.Unix(cmp, 0), req, validator.leeway) } t := time.Unix(c.ExpiresAt, 0) - return verifyExp(&t, time.Unix(cmp, 0), req, s) + return verifyExp(&t, time.Unix(cmp, 0), req, validator.leeway) } // VerifyIssuedAt compares the iat claim against cmp (cmp >= iat). @@ -217,18 +212,17 @@ func (c *StandardClaims) VerifyIssuedAt(cmp int64, req bool) bool { // VerifyNotBefore compares the nbf claim against cmp (cmp >= nbf). // If req is false, it will return true, if nbf is unset. -func (c *StandardClaims) VerifyNotBefore(cmp int64, req bool, opts ...*ValidatorOptions) bool { - var s time.Duration - o := MergeValidatorOptions(opts...) - if o != nil { - s = o.leeway +func (c *StandardClaims) VerifyNotBefore(cmp int64, req bool, opts ...ValidatorOption) bool { + validator := ValidatorOptions{} + for _, o := range opts { + o(&validator) } if c.NotBefore == 0 { - return verifyNbf(nil, time.Unix(cmp, 0), req, s) + return verifyNbf(nil, time.Unix(cmp, 0), req, validator.leeway) } t := time.Unix(c.NotBefore, 0) - return verifyNbf(&t, time.Unix(cmp, 0), req, s) + return verifyNbf(&t, time.Unix(cmp, 0), req, validator.leeway) } // VerifyIssuer compares the iss claim against cmp. diff --git a/map_claims.go b/map_claims.go index b106fe74..7be34882 100644 --- a/map_claims.go +++ b/map_claims.go @@ -34,7 +34,7 @@ func (m MapClaims) VerifyAudience(cmp string, req bool) bool { // VerifyExpiresAt compares the exp claim against cmp (cmp <= exp). // If req is false, it will return true, if exp is unset. -func (m MapClaims) VerifyExpiresAt(cmp int64, req bool, opts ...*ValidatorOptions) bool { +func (m MapClaims) VerifyExpiresAt(cmp int64, req bool, opts ...ValidatorOption) bool { cmpTime := time.Unix(cmp, 0) v, ok := m["exp"] @@ -42,23 +42,22 @@ func (m MapClaims) VerifyExpiresAt(cmp int64, req bool, opts ...*ValidatorOption return !req } - var s time.Duration - o := MergeValidatorOptions(opts...) - if o != nil { - s = o.leeway + validator := ValidatorOptions{} + for _, o := range opts { + o(&validator) } switch exp := v.(type) { case float64: if exp == 0 { - return verifyExp(nil, cmpTime, req, s) + return verifyExp(nil, cmpTime, req, validator.leeway) } - return verifyExp(&newNumericDateFromSeconds(exp).Time, cmpTime, req, s) + return verifyExp(&newNumericDateFromSeconds(exp).Time, cmpTime, req, validator.leeway) case json.Number: v, _ := exp.Float64() - return verifyExp(&newNumericDateFromSeconds(v).Time, cmpTime, req, s) + return verifyExp(&newNumericDateFromSeconds(v).Time, cmpTime, req, validator.leeway) } return false @@ -92,7 +91,7 @@ func (m MapClaims) VerifyIssuedAt(cmp int64, req bool) bool { // VerifyNotBefore compares the nbf claim against cmp (cmp >= nbf). // If req is false, it will return true, if nbf is unset. -func (m MapClaims) VerifyNotBefore(cmp int64, req bool, opts ...*ValidatorOptions) bool { +func (m MapClaims) VerifyNotBefore(cmp int64, req bool, opts ...ValidatorOption) bool { cmpTime := time.Unix(cmp, 0) v, ok := m["nbf"] @@ -100,23 +99,22 @@ func (m MapClaims) VerifyNotBefore(cmp int64, req bool, opts ...*ValidatorOption return !req } - var s time.Duration - o := MergeValidatorOptions(opts...) - if o != nil { - s = o.leeway + validator := ValidatorOptions{} + for _, o := range opts { + o(&validator) } switch nbf := v.(type) { case float64: if nbf == 0 { - return verifyNbf(nil, cmpTime, req, s) + return verifyNbf(nil, cmpTime, req, validator.leeway) } - return verifyNbf(&newNumericDateFromSeconds(nbf).Time, cmpTime, req, s) + return verifyNbf(&newNumericDateFromSeconds(nbf).Time, cmpTime, req, validator.leeway) case json.Number: v, _ := nbf.Float64() - return verifyNbf(&newNumericDateFromSeconds(v).Time, cmpTime, req, s) + return verifyNbf(&newNumericDateFromSeconds(v).Time, cmpTime, req, validator.leeway) } return false @@ -133,12 +131,11 @@ func (m MapClaims) VerifyIssuer(cmp string, req bool) bool { // There is no accounting for clock skew. // As well, if any of the above claims are not in the token, it will still // be considered a valid claim. -func (m MapClaims) Valid(opts ...*ValidatorOptions) error { +func (m MapClaims) Valid(opts ...ValidatorOption) error { vErr := new(ValidationError) now := TimeFunc().Unix() - o := MergeValidatorOptions(opts...) - if !m.VerifyExpiresAt(now, false, o) { + if !m.VerifyExpiresAt(now, false, opts...) { // TODO(oxisto): this should be replaced with ErrTokenExpired vErr.Inner = errors.New("Token is expired") vErr.Errors |= ValidationErrorExpired @@ -150,7 +147,7 @@ func (m MapClaims) Valid(opts ...*ValidatorOptions) error { vErr.Errors |= ValidationErrorIssuedAt } - if !m.VerifyNotBefore(now, false, o) { + if !m.VerifyNotBefore(now, false, opts...) { // TODO(oxisto): this should be replaced with ErrTokenNotValidYet vErr.Inner = errors.New("Token is not valid yet") vErr.Errors |= ValidationErrorNotValidYet diff --git a/parser.go b/parser.go index c1248bf8..7e97fe52 100644 --- a/parser.go +++ b/parser.go @@ -23,7 +23,7 @@ type Parser struct { // Deprecated: In future releases, this field will not be exported anymore and should be set with an option to NewParser instead. SkipClaimsValidation bool - options []*ValidatorOptions + options []ValidatorOption } // NewParser creates a new Parser with the specified options diff --git a/parser_option.go b/parser_option.go index aa454009..e00f44d4 100644 --- a/parser_option.go +++ b/parser_option.go @@ -33,6 +33,6 @@ func WithoutClaimsValidation() ParserOption { // WithLeeway returns the ParserOption for specifying the leeway window. func WithLeeway(d time.Duration) ParserOption { return func(p *Parser) { - p.options = append(p.options, &ValidatorOptions{leeway: d}) + p.options = append(p.options, WithLeewayValidator(d)) } } diff --git a/validator_option.go b/validator_option.go index 2ee80c47..56f7e4b1 100644 --- a/validator_option.go +++ b/validator_option.go @@ -2,30 +2,20 @@ package jwt import "time" +// ValidatorOption is used to implement functional-style options that modify the behavior of the parser. To add +// new options, just create a function (ideally beginning with With or Without) that returns an anonymous function that +// takes a *ValidatorOptions type as input and manipulates its configuration accordingly. +type ValidatorOption func(*ValidatorOptions) + // ValidatorOptions represents options that can be used for claims validation type ValidatorOptions struct { - leeway time.Duration // Leeway to provide when validating time values -} - -func Validator() *ValidatorOptions { - return &ValidatorOptions{} + leeway time.Duration // Leeway to provide when validating time values } -func (v *ValidatorOptions) SetLeeway(d time.Duration) { - v.leeway = d -} -// MergeValidatorOptions combines the given ValidatorOptions instances into a single ValidatorOptions -// in a last-one-wins fashion -func MergeValidatorOptions(opts ...*ValidatorOptions) *ValidatorOptions { - v := Validator() - for _, opt := range opts { - if opt == nil { - continue - } - if opt.leeway != 0 { - v.SetLeeway(opt.leeway) - } +// WithLeewayValidator is an option to set the clock skew (leeway) windows +func WithLeewayValidator(d time.Duration) ValidatorOption { + return func(v *ValidatorOptions) { + v.leeway = d } - return v } From 1bbb42baf7ec73c08602c813f0c56cbfb9422219 Mon Sep 17 00:00:00 2001 From: Kolawole Segun Date: Wed, 16 Feb 2022 16:17:14 -0600 Subject: [PATCH 8/9] rename variables and unexport --- claims.go | 30 +++++++++++++++--------------- map_claims.go | 10 +++++----- parser.go | 4 ++-- parser_option.go | 2 +- validator_option.go | 14 +++++++------- 5 files changed, 30 insertions(+), 30 deletions(-) diff --git a/claims.go b/claims.go index 5f419cb4..29b7f953 100644 --- a/claims.go +++ b/claims.go @@ -9,7 +9,7 @@ import ( // Claims must just have a Valid method that determines // if the token is invalid for any supported reason type Claims interface { - Valid(options ...ValidatorOption) error + Valid(opts ...validationOption) error } // RegisteredClaims are a structured version of the JWT Claims Set, @@ -48,7 +48,7 @@ type RegisteredClaims struct { // There is no accounting for clock skew. // As well, if any of the above claims are not in the token, it will still // be considered a valid claim. -func (c RegisteredClaims) Valid(opts ...ValidatorOption) error { +func (c RegisteredClaims) Valid(opts ...validationOption) error { vErr := new(ValidationError) now := TimeFunc() @@ -85,8 +85,8 @@ func (c *RegisteredClaims) VerifyAudience(cmp string, req bool) bool { // VerifyExpiresAt compares the exp claim against cmp (cmp < exp). // If req is false, it will return true, if exp is unset. -func (c *RegisteredClaims) VerifyExpiresAt(cmp time.Time, req bool, opts ...ValidatorOption) bool { - validator := ValidatorOptions{} +func (c *RegisteredClaims) VerifyExpiresAt(cmp time.Time, req bool, opts ...validationOption) bool { + validator := validator{} for _, o := range opts { o(&validator) } @@ -109,8 +109,8 @@ func (c *RegisteredClaims) VerifyIssuedAt(cmp time.Time, req bool) bool { // VerifyNotBefore compares the nbf claim against cmp (cmp >= nbf). // If req is false, it will return true, if nbf is unset. -func (c *RegisteredClaims) VerifyNotBefore(cmp time.Time, req bool, opts ...ValidatorOption) bool { - validator := ValidatorOptions{} +func (c *RegisteredClaims) VerifyNotBefore(cmp time.Time, req bool, opts ...validationOption) bool { + validator := validator{} for _, o := range opts { o(&validator) } @@ -149,7 +149,7 @@ type StandardClaims struct { // Valid validates time based claims "exp, iat, nbf". There is no accounting for clock skew. // As well, if any of the above claims are not in the token, it will still // be considered a valid claim. -func (c StandardClaims) Valid(opts ...ValidatorOption) error { +func (c StandardClaims) Valid(opts ...validationOption) error { vErr := new(ValidationError) now := TimeFunc().Unix() @@ -186,8 +186,8 @@ func (c *StandardClaims) VerifyAudience(cmp string, req bool) bool { // VerifyExpiresAt compares the exp claim against cmp (cmp < exp). // If req is false, it will return true, if exp is unset. -func (c *StandardClaims) VerifyExpiresAt(cmp int64, req bool, opts ...ValidatorOption) bool { - validator := ValidatorOptions{} +func (c *StandardClaims) VerifyExpiresAt(cmp int64, req bool, opts ...validationOption) bool { + validator := validator{} for _, o := range opts { o(&validator) } @@ -212,8 +212,8 @@ func (c *StandardClaims) VerifyIssuedAt(cmp int64, req bool) bool { // VerifyNotBefore compares the nbf claim against cmp (cmp >= nbf). // If req is false, it will return true, if nbf is unset. -func (c *StandardClaims) VerifyNotBefore(cmp int64, req bool, opts ...ValidatorOption) bool { - validator := ValidatorOptions{} +func (c *StandardClaims) VerifyNotBefore(cmp int64, req bool, opts ...validationOption) bool { + validator := validator{} for _, o := range opts { o(&validator) } @@ -256,11 +256,11 @@ func verifyAud(aud []string, cmp string, required bool) bool { return result } -func verifyExp(exp *time.Time, now time.Time, required bool, clockSkew time.Duration) bool { +func verifyExp(exp *time.Time, now time.Time, required bool, skew time.Duration) bool { if exp == nil { return !required } - return now.Before((*exp).Add(+clockSkew)) + return now.Before((*exp).Add(+skew)) } func verifyIat(iat *time.Time, now time.Time, required bool) bool { @@ -270,11 +270,11 @@ func verifyIat(iat *time.Time, now time.Time, required bool) bool { return now.After(*iat) || now.Equal(*iat) } -func verifyNbf(nbf *time.Time, now time.Time, required bool, clockSkew time.Duration) bool { +func verifyNbf(nbf *time.Time, now time.Time, required bool, skew time.Duration) bool { if nbf == nil { return !required } - t := (*nbf).Add(-clockSkew) + t := (*nbf).Add(-skew) return now.After(t) || now.Equal(t) } diff --git a/map_claims.go b/map_claims.go index 7be34882..e4a08079 100644 --- a/map_claims.go +++ b/map_claims.go @@ -34,7 +34,7 @@ func (m MapClaims) VerifyAudience(cmp string, req bool) bool { // VerifyExpiresAt compares the exp claim against cmp (cmp <= exp). // If req is false, it will return true, if exp is unset. -func (m MapClaims) VerifyExpiresAt(cmp int64, req bool, opts ...ValidatorOption) bool { +func (m MapClaims) VerifyExpiresAt(cmp int64, req bool, opts ...validationOption) bool { cmpTime := time.Unix(cmp, 0) v, ok := m["exp"] @@ -42,7 +42,7 @@ func (m MapClaims) VerifyExpiresAt(cmp int64, req bool, opts ...ValidatorOption) return !req } - validator := ValidatorOptions{} + validator := validator{} for _, o := range opts { o(&validator) } @@ -91,7 +91,7 @@ func (m MapClaims) VerifyIssuedAt(cmp int64, req bool) bool { // VerifyNotBefore compares the nbf claim against cmp (cmp >= nbf). // If req is false, it will return true, if nbf is unset. -func (m MapClaims) VerifyNotBefore(cmp int64, req bool, opts ...ValidatorOption) bool { +func (m MapClaims) VerifyNotBefore(cmp int64, req bool, opts ...validationOption) bool { cmpTime := time.Unix(cmp, 0) v, ok := m["nbf"] @@ -99,7 +99,7 @@ func (m MapClaims) VerifyNotBefore(cmp int64, req bool, opts ...ValidatorOption) return !req } - validator := ValidatorOptions{} + validator := validator{} for _, o := range opts { o(&validator) } @@ -131,7 +131,7 @@ func (m MapClaims) VerifyIssuer(cmp string, req bool) bool { // There is no accounting for clock skew. // As well, if any of the above claims are not in the token, it will still // be considered a valid claim. -func (m MapClaims) Valid(opts ...ValidatorOption) error { +func (m MapClaims) Valid(opts ...validationOption) error { vErr := new(ValidationError) now := TimeFunc().Unix() diff --git a/parser.go b/parser.go index 7e97fe52..6c9128e8 100644 --- a/parser.go +++ b/parser.go @@ -23,7 +23,7 @@ type Parser struct { // Deprecated: In future releases, this field will not be exported anymore and should be set with an option to NewParser instead. SkipClaimsValidation bool - options []ValidatorOption + validationOptions []validationOption } // NewParser creates a new Parser with the specified options @@ -84,7 +84,7 @@ func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyf // Validate Claims if !p.SkipClaimsValidation { - if err := token.Claims.Valid(p.options...); err != nil { + if err := token.Claims.Valid(p.validationOptions...); err != nil { // If the Claims Valid returned an error, check if it is a validation error, // If it was another error type, create a ValidationError with a generic ClaimsInvalid flag set if e, ok := err.(*ValidationError); !ok { diff --git a/parser_option.go b/parser_option.go index e00f44d4..d30fb8a7 100644 --- a/parser_option.go +++ b/parser_option.go @@ -33,6 +33,6 @@ func WithoutClaimsValidation() ParserOption { // WithLeeway returns the ParserOption for specifying the leeway window. func WithLeeway(d time.Duration) ParserOption { return func(p *Parser) { - p.options = append(p.options, WithLeewayValidator(d)) + p.validationOptions = append(p.validationOptions, withLeewayValidator(d)) } } diff --git a/validator_option.go b/validator_option.go index 56f7e4b1..a96c444c 100644 --- a/validator_option.go +++ b/validator_option.go @@ -2,20 +2,20 @@ package jwt import "time" -// ValidatorOption is used to implement functional-style options that modify the behavior of the parser. To add +// validationOption is used to implement functional-style options that modify the behavior of the parser. To add // new options, just create a function (ideally beginning with With or Without) that returns an anonymous function that // takes a *ValidatorOptions type as input and manipulates its configuration accordingly. -type ValidatorOption func(*ValidatorOptions) +type validationOption func(*validator) -// ValidatorOptions represents options that can be used for claims validation -type ValidatorOptions struct { +// validator represents options that can be used for claims validation +type validator struct { leeway time.Duration // Leeway to provide when validating time values } -// WithLeewayValidator is an option to set the clock skew (leeway) windows -func WithLeewayValidator(d time.Duration) ValidatorOption { - return func(v *ValidatorOptions) { +// withLeewayValidator is an option to set the clock skew (leeway) windows +func withLeewayValidator(d time.Duration) validationOption { + return func(v *validator) { v.leeway = d } } From b3d52c5b846ba67063145903e43085a632749e3f Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Mon, 7 Mar 2022 22:57:23 +0100 Subject: [PATCH 9/9] Added some more documentation, why types are not exported --- claims.go | 3 +++ parser_option.go | 2 +- validator_option.go | 16 ++++++++++++---- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/claims.go b/claims.go index 29b7f953..d0e2eb58 100644 --- a/claims.go +++ b/claims.go @@ -9,6 +9,9 @@ import ( // Claims must just have a Valid method that determines // if the token is invalid for any supported reason type Claims interface { + // Valid implements claim validation. The opts are function style options that can + // be used to fine-tune the validation. The type used for the options is intentionally + // un-exported, since its API and its naming is subject to change. Valid(opts ...validationOption) error } diff --git a/parser_option.go b/parser_option.go index d30fb8a7..a7976645 100644 --- a/parser_option.go +++ b/parser_option.go @@ -33,6 +33,6 @@ func WithoutClaimsValidation() ParserOption { // WithLeeway returns the ParserOption for specifying the leeway window. func WithLeeway(d time.Duration) ParserOption { return func(p *Parser) { - p.validationOptions = append(p.validationOptions, withLeewayValidator(d)) + p.validationOptions = append(p.validationOptions, withLeeway(d)) } } diff --git a/validator_option.go b/validator_option.go index a96c444c..eb29dc30 100644 --- a/validator_option.go +++ b/validator_option.go @@ -4,17 +4,25 @@ import "time" // validationOption is used to implement functional-style options that modify the behavior of the parser. To add // new options, just create a function (ideally beginning with With or Without) that returns an anonymous function that -// takes a *ValidatorOptions type as input and manipulates its configuration accordingly. +// takes a *validator type as input and manipulates its configuration accordingly. +// +// Note that this struct is (currently) un-exported, its naming is subject to change and will only be exported once +// the API is more stable. type validationOption func(*validator) // validator represents options that can be used for claims validation +// +// Note that this struct is (currently) un-exported, its naming is subject to change and will only be exported once +// the API is more stable. type validator struct { leeway time.Duration // Leeway to provide when validating time values } - -// withLeewayValidator is an option to set the clock skew (leeway) windows -func withLeewayValidator(d time.Duration) validationOption { +// withLeeway is an option to set the clock skew (leeway) window +// +// Note that this function is (currently) un-exported, its naming is subject to change and will only be exported once +// the API is more stable. +func withLeeway(d time.Duration) validationOption { return func(v *validator) { v.leeway = d }