Skip to content

Commit

Permalink
convert validatior to functional style
Browse files Browse the repository at this point in the history
  • Loading branch information
Kolawole Segun authored and Kolawole Segun committed Feb 14, 2022
1 parent 9629854 commit 64c4dc6
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 79 deletions.
68 changes: 31 additions & 37 deletions claims.go
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand All @@ -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
}
Expand All @@ -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).
Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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
}
Expand All @@ -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).
Expand All @@ -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.
Expand Down
37 changes: 17 additions & 20 deletions map_claims.go
Expand Up @@ -34,31 +34,30 @@ 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"]
if !ok {
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
Expand Down Expand Up @@ -92,31 +91,30 @@ 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"]
if !ok {
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
Expand All @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion parser.go
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion parser_option.go
Expand Up @@ -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))
}
}
30 changes: 10 additions & 20 deletions validator_option.go
Expand Up @@ -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
}

0 comments on commit 64c4dc6

Please sign in to comment.