Skip to content

Commit

Permalink
Implementing Is(err) bool to support Go 1.13 style error checking
Browse files Browse the repository at this point in the history
Fixes #135 by implementing `Is(err) bool` for the `ValidationError`. It both checks
for the actual inner error message as well as our error flags for maximum backwards compatibility
  • Loading branch information
oxisto committed Nov 30, 2021
1 parent a725c1f commit 278eb3c
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 26 deletions.
12 changes: 6 additions & 6 deletions claims.go
Expand Up @@ -56,17 +56,17 @@ func (c RegisteredClaims) Valid() error {
// default value in Go, let's not fail the verification for them.
if !c.VerifyExpiresAt(now, false) {
delta := now.Sub(c.ExpiresAt.Time)
vErr.Inner = fmt.Errorf("token is expired by %v", delta)
vErr.Inner = fmt.Errorf("%s by %v", delta, ErrTokenExpired)
vErr.Errors |= ValidationErrorExpired
}

if !c.VerifyIssuedAt(now, false) {
vErr.Inner = fmt.Errorf("token used before issued")
vErr.Inner = ErrTokenUsedBeforeIssued
vErr.Errors |= ValidationErrorIssuedAt
}

if !c.VerifyNotBefore(now, false) {
vErr.Inner = fmt.Errorf("token is not valid yet")
vErr.Inner = ErrTokenNotValidYet
vErr.Errors |= ValidationErrorNotValidYet
}

Expand Down Expand Up @@ -143,17 +143,17 @@ func (c StandardClaims) Valid() error {
// default value in Go, let's not fail the verification for them.
if !c.VerifyExpiresAt(now, false) {
delta := time.Unix(now, 0).Sub(time.Unix(c.ExpiresAt, 0))
vErr.Inner = fmt.Errorf("token is expired by %v", delta)
vErr.Inner = fmt.Errorf("%s by %v", delta, ErrTokenExpired)
vErr.Errors |= ValidationErrorExpired
}

if !c.VerifyIssuedAt(now, false) {
vErr.Inner = fmt.Errorf("token used before issued")
vErr.Inner = ErrTokenUsedBeforeIssued
vErr.Errors |= ValidationErrorIssuedAt
}

if !c.VerifyNotBefore(now, false) {
vErr.Inner = fmt.Errorf("token is not valid yet")
vErr.Inner = ErrTokenNotValidYet
vErr.Errors |= ValidationErrorNotValidYet
}

Expand Down
36 changes: 36 additions & 0 deletions errors.go
Expand Up @@ -9,6 +9,14 @@ var (
ErrInvalidKey = errors.New("key is invalid")
ErrInvalidKeyType = errors.New("key is of invalid type")
ErrHashUnavailable = errors.New("the requested hash function is unavailable")

ErrTokenMalformed = errors.New("token is malformed")
ErrTokenUnverifiable = errors.New("token is unverifiable")
ErrTokenSignatureInvalid = errors.New("token signature is invalid")

ErrTokenExpired = errors.New("token is expired")
ErrTokenUsedBeforeIssued = errors.New("token used before issued")
ErrTokenNotValidYet = errors.New("token is not valid yet")
)

// The errors that might occur when parsing and validating a token
Expand Down Expand Up @@ -62,3 +70,31 @@ func (e *ValidationError) Unwrap() error {
func (e *ValidationError) valid() bool {
return e.Errors == 0
}

// Is checks if this ValidationError is of the supplied error type. We are first checking for the exact error message
// by suppling the inner message. If that fails, we the error flags. This way we can supply custom error messages
// and still use errors.Is using the pre-supplied error variables.
func (e *ValidationError) Is(err error) bool {
// Check, if our inner error is a direct match
if errors.Is(errors.Unwrap(e), err) {
return true
}

// Otherwise, we need to match using our error flags
switch err {
case ErrTokenMalformed:
return e.Errors&ValidationErrorMalformed != 0
case ErrTokenUnverifiable:
return e.Errors&ValidationErrorUnverifiable != 0
case ErrTokenSignatureInvalid:
return e.Errors&ValidationErrorUnverifiable != 0
case ErrTokenExpired:
return e.Errors&ValidationErrorExpired != 0
case ErrTokenNotValidYet:
return e.Errors&ValidationErrorNotValidYet != 0
case ErrTokenUsedBeforeIssued:
return e.Errors&ValidationErrorIssuedAt != 0
}

return false
}
24 changes: 13 additions & 11 deletions example_test.go
@@ -1,6 +1,7 @@
package jwt_test

import (
"errors"
"fmt"
"time"

Expand Down Expand Up @@ -94,24 +95,25 @@ func ExampleParseWithClaims_customClaimsType() {

// An example of parsing the error types using bitfield checks
func ExampleParse_errorChecking() {
var (
token *jwt.Token
err error
)

// Token from another example. This token is expired
var tokenString = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJleHAiOjE1MDAwLCJpc3MiOiJ0ZXN0In0.HE7fK0xOQwFEr4WDgRWj4teRPZ6i3GLwD5YCm6Pwu_c"
tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJleHAiOjE1MDAwLCJpc3MiOiJ0ZXN0In0.HE7fK0xOQwFEr4WDgRWj4teRPZ6i3GLwD5YCm6Pwu_c"

token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
token, err = jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
return []byte("AllYourBase"), nil
})

if token.Valid {
fmt.Println("You look nice today")
} else if ve, ok := err.(*jwt.ValidationError); ok {
if ve.Errors&jwt.ValidationErrorMalformed != 0 {
fmt.Println("That's not even a token")
} else if ve.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0 {
// Token is either expired or not active yet
fmt.Println("Timing is everything")
} else {
fmt.Println("Couldn't handle this token:", err)
}
} else if errors.Is(err, jwt.ErrTokenMalformed) {
fmt.Println("That's not even a token")
} else if errors.Is(err, jwt.ErrTokenExpired) || errors.Is(err, jwt.ErrTokenNotValidYet) {
// Token is either expired or not active yet
fmt.Println("Timing is everything")
} else {
fmt.Println("Couldn't handle this token:", err)
}
Expand Down
3 changes: 3 additions & 0 deletions map_claims.go
Expand Up @@ -126,16 +126,19 @@ func (m MapClaims) Valid() error {
now := TimeFunc().Unix()

if !m.VerifyExpiresAt(now, false) {
// TODO(oxisto): this should be replaced with ErrTokenExpired
vErr.Inner = errors.New("Token is expired")
vErr.Errors |= ValidationErrorExpired
}

if !m.VerifyIssuedAt(now, false) {
// TODO(oxisto): this should be replaced with ErrTokenUsedBeforeIssued
vErr.Inner = errors.New("Token used before issued")
vErr.Errors |= ValidationErrorIssuedAt
}

if !m.VerifyNotBefore(now, false) {
// TODO(oxisto): this should be replaced with ErrTokenNotValidYet
vErr.Inner = errors.New("Token is not valid yet")
vErr.Errors |= ValidationErrorNotValidYet
}
Expand Down
20 changes: 11 additions & 9 deletions parser_test.go
Expand Up @@ -4,6 +4,7 @@ import (
"crypto"
"crypto/rsa"
"encoding/json"
"errors"
"fmt"
"reflect"
"testing"
Expand Down Expand Up @@ -325,6 +326,7 @@ func TestParser_Parse(t *testing.T) {

// Parse the token
var token *jwt.Token
var ve *jwt.ValidationError
var err error
var parser = data.parser
if parser == nil {
Expand Down Expand Up @@ -361,15 +363,15 @@ func TestParser_Parse(t *testing.T) {
if err == nil {
t.Errorf("[%v] Expecting error. Didn't get one.", data.name)
} else {

ve := err.(*jwt.ValidationError)
// compare the bitfield part of the error
if e := ve.Errors; e != data.errors {
t.Errorf("[%v] Errors don't match expectation. %v != %v", data.name, e, data.errors)
}

if err.Error() == errKeyFuncError.Error() && ve.Inner != errKeyFuncError {
t.Errorf("[%v] Inner error does not match expectation. %v != %v", data.name, ve.Inner, errKeyFuncError)
if errors.As(err, &ve) {
// compare the bitfield part of the error
if e := ve.Errors; e != data.errors {
t.Errorf("[%v] Errors don't match expectation. %v != %v", data.name, e, data.errors)
}

if err.Error() == errKeyFuncError.Error() && ve.Inner != errKeyFuncError {
t.Errorf("[%v] Inner error does not match expectation. %v != %v", data.name, ve.Inner, errKeyFuncError)
}
}
}
}
Expand Down

0 comments on commit 278eb3c

Please sign in to comment.