Skip to content

Commit 0bcf64b

Browse files
committed
Implementing Is(err) bool to support Go 1.13 style error checking
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
1 parent a725c1f commit 0bcf64b

File tree

5 files changed

+60
-19
lines changed

5 files changed

+60
-19
lines changed

claims.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,17 +56,17 @@ func (c RegisteredClaims) Valid() error {
5656
// default value in Go, let's not fail the verification for them.
5757
if !c.VerifyExpiresAt(now, false) {
5858
delta := now.Sub(c.ExpiresAt.Time)
59-
vErr.Inner = fmt.Errorf("token is expired by %v", delta)
59+
vErr.Inner = fmt.Errorf("%s by %v", delta, ErrTokenExpired)
6060
vErr.Errors |= ValidationErrorExpired
6161
}
6262

6363
if !c.VerifyIssuedAt(now, false) {
64-
vErr.Inner = fmt.Errorf("token used before issued")
64+
vErr.Inner = ErrTokenUsedBeforeIssued
6565
vErr.Errors |= ValidationErrorIssuedAt
6666
}
6767

6868
if !c.VerifyNotBefore(now, false) {
69-
vErr.Inner = fmt.Errorf("token is not valid yet")
69+
vErr.Inner = ErrTokenNotValidYet
7070
vErr.Errors |= ValidationErrorNotValidYet
7171
}
7272

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

150150
if !c.VerifyIssuedAt(now, false) {
151-
vErr.Inner = fmt.Errorf("token used before issued")
151+
vErr.Inner = ErrTokenUsedBeforeIssued
152152
vErr.Errors |= ValidationErrorIssuedAt
153153
}
154154

155155
if !c.VerifyNotBefore(now, false) {
156-
vErr.Inner = fmt.Errorf("token is not valid yet")
156+
vErr.Inner = ErrTokenNotValidYet
157157
vErr.Errors |= ValidationErrorNotValidYet
158158
}
159159

errors.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ var (
99
ErrInvalidKey = errors.New("key is invalid")
1010
ErrInvalidKeyType = errors.New("key is of invalid type")
1111
ErrHashUnavailable = errors.New("the requested hash function is unavailable")
12+
13+
ErrTokenMalformed = errors.New("token is malformed")
14+
ErrTokenExpired = errors.New("token is expired")
15+
ErrTokenUsedBeforeIssued = errors.New("token used before issued")
16+
ErrTokenNotValidYet = errors.New("token is not valid yet")
1217
)
1318

1419
// The errors that might occur when parsing and validating a token
@@ -62,3 +67,27 @@ func (e *ValidationError) Unwrap() error {
6267
func (e *ValidationError) valid() bool {
6368
return e.Errors == 0
6469
}
70+
71+
// Is checks if this ValidationError is of the supplied error type. We are first checking for the exact error message
72+
// by suppling the inner message. If that fails, we the error flags. This way we can supply custom error messages
73+
// and still use errors.Is using the pre-supplied error variables.
74+
func (e *ValidationError) Is(err error) bool {
75+
// Check, if our inner error is a direct match
76+
if errors.Is(errors.Unwrap(e), err) {
77+
return true
78+
}
79+
80+
// Otherwise, we need to match using our error flags
81+
switch err {
82+
case ErrTokenMalformed:
83+
return e.Errors&ValidationErrorExpired != 0
84+
case ErrTokenExpired:
85+
return e.Errors&ValidationErrorExpired != 0
86+
case ErrTokenNotValidYet:
87+
return e.Errors&ValidationErrorNotValidYet != 0
88+
case ErrTokenUsedBeforeIssued:
89+
return e.Errors&ValidationErrorIssuedAt != 0
90+
}
91+
92+
return false
93+
}

example_test.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package jwt_test
22

33
import (
4+
"errors"
45
"fmt"
56
"time"
67

@@ -94,19 +95,25 @@ func ExampleParseWithClaims_customClaimsType() {
9495

9596
// An example of parsing the error types using bitfield checks
9697
func ExampleParse_errorChecking() {
98+
var (
99+
token *jwt.Token
100+
ve *jwt.ValidationError
101+
err error
102+
)
103+
97104
// Token from another example. This token is expired
98-
var tokenString = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJleHAiOjE1MDAwLCJpc3MiOiJ0ZXN0In0.HE7fK0xOQwFEr4WDgRWj4teRPZ6i3GLwD5YCm6Pwu_c"
105+
tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJleHAiOjE1MDAwLCJpc3MiOiJ0ZXN0In0.HE7fK0xOQwFEr4WDgRWj4teRPZ6i3GLwD5YCm6Pwu_c"
99106

100-
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
107+
token, err = jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
101108
return []byte("AllYourBase"), nil
102109
})
103110

104111
if token.Valid {
105112
fmt.Println("You look nice today")
106-
} else if ve, ok := err.(*jwt.ValidationError); ok {
113+
} else if errors.As(err, &ve) {
107114
if ve.Errors&jwt.ValidationErrorMalformed != 0 {
108115
fmt.Println("That's not even a token")
109-
} else if ve.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0 {
116+
} else if errors.Is(err, jwt.ErrTokenExpired) || errors.Is(err, jwt.ErrTokenNotValidYet) {
110117
// Token is either expired or not active yet
111118
fmt.Println("Timing is everything")
112119
} else {

map_claims.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,16 +126,19 @@ func (m MapClaims) Valid() error {
126126
now := TimeFunc().Unix()
127127

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

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

138140
if !m.VerifyNotBefore(now, false) {
141+
// TODO(oxisto): this should be replaced with ErrTokenNotValidYet
139142
vErr.Inner = errors.New("Token is not valid yet")
140143
vErr.Errors |= ValidationErrorNotValidYet
141144
}

parser_test.go

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"crypto"
55
"crypto/rsa"
66
"encoding/json"
7+
"errors"
78
"fmt"
89
"reflect"
910
"testing"
@@ -325,6 +326,7 @@ func TestParser_Parse(t *testing.T) {
325326

326327
// Parse the token
327328
var token *jwt.Token
329+
var ve *jwt.ValidationError
328330
var err error
329331
var parser = data.parser
330332
if parser == nil {
@@ -361,15 +363,15 @@ func TestParser_Parse(t *testing.T) {
361363
if err == nil {
362364
t.Errorf("[%v] Expecting error. Didn't get one.", data.name)
363365
} else {
364-
365-
ve := err.(*jwt.ValidationError)
366-
// compare the bitfield part of the error
367-
if e := ve.Errors; e != data.errors {
368-
t.Errorf("[%v] Errors don't match expectation. %v != %v", data.name, e, data.errors)
369-
}
370-
371-
if err.Error() == errKeyFuncError.Error() && ve.Inner != errKeyFuncError {
372-
t.Errorf("[%v] Inner error does not match expectation. %v != %v", data.name, ve.Inner, errKeyFuncError)
366+
if errors.As(err, &ve) {
367+
// compare the bitfield part of the error
368+
if e := ve.Errors; e != data.errors {
369+
t.Errorf("[%v] Errors don't match expectation. %v != %v", data.name, e, data.errors)
370+
}
371+
372+
if err.Error() == errKeyFuncError.Error() && ve.Inner != errKeyFuncError {
373+
t.Errorf("[%v] Inner error does not match expectation. %v != %v", data.name, ve.Inner, errKeyFuncError)
374+
}
373375
}
374376
}
375377
}

0 commit comments

Comments
 (0)