Skip to content

Commit

Permalink
Streamline errors (#1220)
Browse files Browse the repository at this point in the history
* jws.Sign / jws.Verify

* edit jws/BUILD.bazel

* jws.Parse

* SplitXXXX functions

* tweak

* get the type / var name correct

* jwt.ParseXXX

* tweak jwt error

* jwt validation

* Tweak jwt/BUILD.bazel

* Update changes

* fix issuer error

* remove unused type

* fix error message

* Update changes

* jwk.Parse

* Add t.Parallel

* jwe.ParseXXX, jwe.Decrypt, jwe.Encrypt

* Add missing file

* Add to BUILD.bazel

* Update Changes

* Remove IsContinueError

---------

Co-authored-by: Daisuke Maki <lestrrat+github@users.noreplay.github.com>
  • Loading branch information
lestrrat and Daisuke Maki authored Oct 25, 2024
1 parent a7e66e3 commit 6eb1219
Show file tree
Hide file tree
Showing 24 changed files with 921 additions and 386 deletions.
29 changes: 28 additions & 1 deletion Changes-v3.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,22 @@ These are changes that are incompatible with the v2.x.x version.
`Get(string, interface{}) error`, where the second argument should be a pointer
to the storage destination of the field.

* All convenience accessors (e.g. `(jwt.Token).Subject`) now return `(T, bool)` instead of
`T`. If you want an accessor that returns a single value, consider using `Get()`

* Most major errors can now be differentiated using `errors.Is`


## JWA

* All string constants have been renamed to equivalent functions that return a struct.
You should rewrite `jwa.RS256` as `jwa.RS256()`

* By default, only known algorithm names are accepted. For example, in our JWK tests,
there are tests that deal with "ECMR" algorithm, but this will now fail by default.
If you want this algorithm to succeed parsing, you need to call `jwa.RegisterXXXX`
functions before using them
functions before using them.

* Previously, unmarshaling unquoted strings used to work (e.g. `var s = "RS256"`),
but now they must conform to the JSON standard and be quoted (e.g. `var s = strconv.Quote("RS256")`)

Expand All @@ -32,6 +41,16 @@ These are changes that are incompatible with the v2.x.x version.
* Validation used to work for `iat`, `nbf`, `exp` fields where these fields were
set to the explicit time.Time{} zero value, but now the _presence_ of these fields matter.

* Error names have been renamed. For example `jwt.ErrInvalidJWT` has been renamed to
`jwt.UnknownPayloadTypeError` to better reflect what the error means. For other errors,
`func ErrXXXX()` have generally been renamed to `func XXXError()`

* Validation errors are now wrapped. While `Validate()` returns a `ValidateError()` type,
it can also be matched against more specific error types such as `TokenExpierdError()`
using `errors.Is`

* `jwt.ErrMissingRequiredClaim` has been removed

## JWS

* Iterators have been completely removed.
Expand All @@ -41,6 +60,10 @@ These are changes that are incompatible with the v2.x.x version.
* All convenience accessors (e.g. `Algorithm`) now return `(T, bool)` instead of
just `T`. If you want a single return value accessor, use `Get(dst) error` instead.

* Errors from `jws.Sign` and `jws.Verify`, as well as `jws.Parse` (and friends)
can now be differentiated by using `errors.Is`. All `jws.IsXXXXError` functions
have been removed.

## JWE

* Iterators have been completely removed.
Expand All @@ -50,6 +73,10 @@ These are changes that are incompatible with the v2.x.x version.
* All convenience accessors (e.g. `Algorithm`) now return `(T, bool)` instead of
just `T`. If you want a single return value accessor, use `Get(dst) error` instead.

* Errors from `jwe.Decrypt` and `jwe.Encrypt`, as well as `jwe.Parse` (and friends)
can now be differentiated by using `errors.Is`. All `jwe.IsXXXXrror` functions
have been removed.

## JWK

* All convenience accessors (e.g. `Algorithm`, `Crv`) now return `(T, bool)` instead
Expand Down
8 changes: 4 additions & 4 deletions examples/jwt_validate_detect_error_type_example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func ExampleJWT_ValidateDetectErrorType() {
return
}

if jwt.IsValidationError(err) {
if errors.Is(err, jwt.ValidateError()) {
fmt.Printf("error should NOT be validation error\n")
return
}
Expand All @@ -51,17 +51,17 @@ func ExampleJWT_ValidateDetectErrorType() {
return
}

if !jwt.IsValidationError(err) {
if !errors.Is(err, jwt.ValidateError()) {
fmt.Printf("error should be validation error\n")
return
}

if !errors.Is(err, jwt.ErrTokenExpired()) {
if !errors.Is(err, jwt.TokenExpiredError()) {
fmt.Printf("error should be of token expired type\n")
return
}
fmt.Printf("%s\n", err)
}
// OUTPUT:
// "exp" not satisfied
// jwt.Parse: failed to parse token: jwt.Validate: validation failed: "exp" not satisfied: token is expired
}
4 changes: 2 additions & 2 deletions examples/jwt_validate_example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,6 @@ func ExampleJWT_Validate() {
fmt.Printf("%s\n", err)
}
// OUTPUT:
// "exp" not satisfied
// "exp" not satisfied
// jwt.Validate: validation failed: "exp" not satisfied: token is expired
// jwt.Parse: failed to parse token: jwt.Validate: validation failed: "exp" not satisfied: token is expired
}
2 changes: 1 addition & 1 deletion examples/jwt_validate_issuer_example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,5 @@ func ExampleJWT_ValidateIssuer() {
}
fmt.Printf("%s\n", err)
// OUTPUT:
// "iss" not satisfied: values do not match
// jwt.Validate: validation failed: "iss" not satisfied: values do not match
}
8 changes: 4 additions & 4 deletions examples/jwt_validate_validator_example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ import (
)

func ExampleJWT_ValidateValidator() {
validator := jwt.ValidatorFunc(func(_ context.Context, t jwt.Token) jwt.ValidationError {
validator := jwt.ValidatorFunc(func(_ context.Context, t jwt.Token) error {
iat, ok := t.IssuedAt()
if !ok {
return jwt.NewValidationError(errors.New(`token does not have "iat" claim`))
return errors.New(`token does not have "iat" claim`)
}
if iat.Month() != 8 {
return jwt.NewValidationError(errors.New(`tokens are only valid if issued during August!`))
return errors.New(`tokens are only valid if issued during August!`)
}
return nil
})
Expand All @@ -37,5 +37,5 @@ func ExampleJWT_ValidateValidator() {
}
fmt.Printf("%s\n", err)
// OUTPUT:
// tokens are only valid if issued during August!
// jwt.Validate: validation failed: tokens are only valid if issued during August!
}
1 change: 1 addition & 0 deletions jwe/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go_library(
srcs = [
"compress.go",
"decrypt.go",
"errors.go",
"headers.go",
"headers_gen.go",
"interface.go",
Expand Down
90 changes: 90 additions & 0 deletions jwe/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package jwe

import "errors"

type encryptError struct {
error
}

func (e encryptError) Unwrap() error {
return e.error
}

func (encryptError) Is(err error) bool {
_, ok := err.(encryptError)
return ok
}

var errDefaultEncryptError = encryptError{errors.New(`encrypt error`)}

// EncryptError returns an error that can be passed to `errors.Is` to check if the error is an error returned by `jwe.Encrypt`.
func EncryptError() error {
return errDefaultEncryptError
}

type decryptError struct {
error
}

func (e decryptError) Unwrap() error {
return e.error
}

func (decryptError) Is(err error) bool {
_, ok := err.(decryptError)
return ok
}

var errDefaultDecryptError = decryptError{errors.New(`decrypt error`)}

// DecryptError returns an error that can be passed to `errors.Is` to check if the error is an error returned by `jwe.Decrypt`.
func DecryptError() error {
return errDefaultDecryptError
}

type recipientError struct {
error
}

func (e recipientError) Unwrap() error {
return e.error
}

func (recipientError) Is(err error) bool {
_, ok := err.(recipientError)
return ok
}

var errDefaultRecipientError = recipientError{errors.New(`recipient error`)}

// RecipientError returns an error that can be passed to `errors.Is` to check if the error is
// an error that occurred while attempting to decrypt a JWE message for a particular recipient.
//
// For example, if the JWE message failed to parse during `jwe.Decrypt`, it will be a
// `jwe.DecryptError`, but NOT `jwe.RecipientError`. However, if the JWE message could not
// be decrypted for any of the recipients, then it will be a `jwe.RecipientError`
// (actually, it will be _multiple_ `jwe.RecipientError` errors, one for each recipient)
func RecipientError() error {
return errDefaultRecipientError
}

type parseError struct {
error
}

func (e parseError) Unwrap() error {
return e.error
}

func (parseError) Is(err error) bool {
_, ok := err.(parseError)
return ok
}

var errDefaultParseError = parseError{errors.New(`parse error`)}

// ParseError returns an error that can be passed to `errors.Is` to check if the error
// is an error returned by `jwe.Parse` and related functions.
func ParseError() error {
return errDefaultParseError
}
Loading

0 comments on commit 6eb1219

Please sign in to comment.