From c8567b48604a37c8ef9e7f0f5a649e9e044cb7fa Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Thu, 9 Feb 2023 21:02:50 +0100 Subject: [PATCH 1/7] Adding more coverage --- map_claims_test.go | 53 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/map_claims_test.go b/map_claims_test.go index 5c3a5c18..b4af574b 100644 --- a/map_claims_test.go +++ b/map_claims_test.go @@ -134,3 +134,56 @@ func TestMapClaimsVerifyExpiresAtExpire(t *testing.T) { t.Fatalf("Failed to verify claims, wanted: %v got %v", want, (got == nil)) } } + +func TestMapClaims_ParseString(t *testing.T) { + type args struct { + key string + } + tests := []struct { + name string + m MapClaims + args args + want string + wantErr bool + }{ + { + name: "missing key", + m: MapClaims{}, + args: args{ + key: "mykey", + }, + want: "", + wantErr: false, + }, + { + name: "wrong key type", + m: MapClaims{"mykey": 4}, + args: args{ + key: "mykey", + }, + want: "", + wantErr: true, + }, + { + name: "correct key type", + m: MapClaims{"mykey": "mystring"}, + args: args{ + key: "mykey", + }, + want: "mystring", + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.m.ParseString(tt.args.key) + if (err != nil) != tt.wantErr { + t.Errorf("MapClaims.ParseString() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("MapClaims.ParseString() = %v, want %v", got, tt.want) + } + }) + } +} From e11c17d3fb7c2ce0304c3cd17a2ff83a85f71e80 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Thu, 9 Feb 2023 21:28:16 +0100 Subject: [PATCH 2/7] Adding more coverage --- parser_test.go | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/parser_test.go b/parser_test.go index 5cf84e08..306f8b50 100644 --- a/parser_test.go +++ b/parser_test.go @@ -56,6 +56,28 @@ var jwtTestData = []struct { parser *jwt.Parser signingMethod jwt.SigningMethod // The method to sign the JWT token for test purpose }{ + { + "invalid JWT", + "thisisnotreallyajwt", + defaultKeyFunc, + nil, + false, + jwt.ValidationErrorMalformed, + []error{jwt.ErrTokenMalformed}, + nil, + jwt.SigningMethodRS256, + }, + { + "bearer in JWT", + "bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg", + defaultKeyFunc, + nil, + false, + jwt.ValidationErrorMalformed, + []error{jwt.ErrTokenMalformed}, + nil, + jwt.SigningMethodRS256, + }, { "basic", "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg", @@ -371,10 +393,12 @@ func TestParser_Parse(t *testing.T) { token, err = parser.ParseWithClaims(data.tokenString, jwt.MapClaims{}, data.keyfunc) case *jwt.RegisteredClaims: token, err = parser.ParseWithClaims(data.tokenString, &jwt.RegisteredClaims{}, data.keyfunc) + case nil: + token, err = parser.ParseWithClaims(data.tokenString, nil, data.keyfunc) } // Verify result matches expectation - if !reflect.DeepEqual(data.claims, token.Claims) { + if data.claims != nil && !reflect.DeepEqual(data.claims, token.Claims) { t.Errorf("[%v] Claims mismatch. Expecting: %v Got: %v", data.name, data.claims, token.Claims) } @@ -386,7 +410,10 @@ func TestParser_Parse(t *testing.T) { t.Errorf("[%v] Invalid token passed validation", data.name) } - if (err == nil && !token.Valid) || (err != nil && token.Valid) { + // Since the returned token is nil in the ErrTokenMalformed, we + // cannot make the comparison here + if !errors.Is(err, jwt.ErrTokenMalformed) && + ((err == nil && !token.Valid) || (err != nil && token.Valid)) { t.Errorf("[%v] Inconsistent behavior between returned error and token.Valid", data.name) } From ca0f5b0416099a7ba5274584870e14eef20f7b94 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Fri, 17 Feb 2023 22:01:47 +0100 Subject: [PATCH 3/7] Experimental version with generics Following the discussion in #269, this PR serves as a playground for a jwt version with generics aka type parameters. This basically breaks all the tests for now since they are non-typed but the examples in `example_test.go` work. --- example_test.go | 18 +++++++++--------- go.mod | 2 +- parser.go | 26 +++++++++++++------------- parser_option.go | 38 +++++++++++++++++++------------------- token.go | 24 ++++++++++++------------ token_test.go | 4 ++-- validator.go | 4 ++-- 7 files changed, 58 insertions(+), 58 deletions(-) diff --git a/example_test.go b/example_test.go index 650132aa..ab6d9a2f 100644 --- a/example_test.go +++ b/example_test.go @@ -80,11 +80,11 @@ func ExampleParseWithClaims_customClaimsType() { jwt.RegisteredClaims } - token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{}, func(token *jwt.Token) (interface{}, error) { + token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{}, func(token *jwt.Token[*MyCustomClaims]) (interface{}, error) { return []byte("AllYourBase"), nil }) - if claims, ok := token.Claims.(*MyCustomClaims); ok && token.Valid { + if claims := token.Claims; token.Valid { fmt.Printf("%v %v", claims.Foo, claims.RegisteredClaims.Issuer) } else { fmt.Println(err) @@ -103,11 +103,11 @@ func ExampleParseWithClaims_validationOptions() { jwt.RegisteredClaims } - token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{}, func(token *jwt.Token) (interface{}, error) { + token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{}, func(token *jwt.Token[*MyCustomClaims]) (interface{}, error) { return []byte("AllYourBase"), nil - }, jwt.WithLeeway(5*time.Second)) + }, jwt.WithLeeway[*MyCustomClaims](5*time.Second)) - if claims, ok := token.Claims.(*MyCustomClaims); ok && token.Valid { + if claims := token.Claims; token.Valid { fmt.Printf("%v %v", claims.Foo, claims.RegisteredClaims.Issuer) } else { fmt.Println(err) @@ -136,11 +136,11 @@ func (m MyCustomClaims) CustomValidation() error { func ExampleParseWithClaims_customValidation() { tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpc3MiOiJ0ZXN0IiwiYXVkIjoic2luZ2xlIn0.QAWg1vGvnqRuCFTMcPkjZljXHh8U3L_qUjszOtQbeaA" - token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{}, func(token *jwt.Token) (interface{}, error) { + token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{}, func(token *jwt.Token[*MyCustomClaims]) (interface{}, error) { return []byte("AllYourBase"), nil - }, jwt.WithLeeway(5*time.Second)) + }, jwt.WithLeeway[*MyCustomClaims](5*time.Second)) - if claims, ok := token.Claims.(*MyCustomClaims); ok && token.Valid { + if claims := token.Claims; token.Valid { fmt.Printf("%v %v", claims.Foo, claims.RegisteredClaims.Issuer) } else { fmt.Println(err) @@ -154,7 +154,7 @@ func ExampleParse_errorChecking() { // Token from another example. This token is expired var 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[jwt.MapClaims]) (interface{}, error) { return []byte("AllYourBase"), nil }) diff --git a/go.mod b/go.mod index 3b8690b0..7fbfcedd 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module github.com/golang-jwt/jwt/v5 -go 1.16 +go 1.18 diff --git a/parser.go b/parser.go index b9c3ffb5..be9c914d 100644 --- a/parser.go +++ b/parser.go @@ -7,7 +7,7 @@ import ( "strings" ) -type Parser struct { +type Parser[T Claims] struct { // If populated, only these methods will be considered valid. validMethods []string @@ -21,8 +21,8 @@ type Parser struct { } // NewParser creates a new Parser with the specified options -func NewParser(options ...ParserOption) *Parser { - p := &Parser{ +func NewParser[T Claims](options ...ParserOption[T]) *Parser[T] { + p := &Parser[T]{ validator: &validator{}, } @@ -36,9 +36,9 @@ func NewParser(options ...ParserOption) *Parser { // Parse parses, validates, verifies the signature and returns the parsed token. // keyFunc will receive the parsed token and should return the key for validating. -func (p *Parser) Parse(tokenString string, keyFunc Keyfunc) (*Token, error) { +/*func (p *Parser[T]) Parse(tokenString string, keyFunc Keyfunc[T]) (*Token[T], error) { return p.ParseWithClaims(tokenString, MapClaims{}, keyFunc) -} +}*/ // ParseWithClaims parses, validates, and verifies like Parse, but supplies a default object implementing the Claims // interface. This provides default values which can be overridden and allows a caller to use their own type, rather @@ -47,7 +47,7 @@ func (p *Parser) Parse(tokenString string, keyFunc Keyfunc) (*Token, error) { // Note: If you provide a custom claim implementation that embeds one of the standard claims (such as RegisteredClaims), // make sure that a) you either embed a non-pointer version of the claims or b) if you are using a pointer, allocate the // proper memory for it before passing in the overall claims, otherwise you might run into a panic. -func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) { +func (p *Parser[T]) ParseWithClaims(tokenString string, claims T, keyFunc Keyfunc[T]) (*Token[T], error) { token, parts, err := p.ParseUnverified(tokenString, claims) if err != nil { return token, err @@ -89,7 +89,7 @@ func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyf if !p.skipClaimsValidation { // Make sure we have at least a default validator if p.validator == nil { - p.validator = newValidator() + p.validator = newValidator[T]() } if err := p.validator.Validate(claims); err != nil { @@ -124,13 +124,13 @@ func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyf // // It's only ever useful in cases where you know the signature is valid (because it has // been checked previously in the stack) and you want to extract values from it. -func (p *Parser) ParseUnverified(tokenString string, claims Claims) (token *Token, parts []string, err error) { +func (p *Parser[T]) ParseUnverified(tokenString string, claims T) (token *Token[T], parts []string, err error) { parts = strings.Split(tokenString, ".") if len(parts) != 3 { return nil, parts, NewValidationError("token contains an invalid number of segments", ValidationErrorMalformed) } - token = &Token{Raw: tokenString} + token = &Token[T]{Raw: tokenString} // parse Header var headerBytes []byte @@ -156,11 +156,11 @@ func (p *Parser) ParseUnverified(tokenString string, claims Claims) (token *Toke dec.UseNumber() } // JSON Decode. Special case for map type to avoid weird pointer behavior - if c, ok := token.Claims.(MapClaims); ok { + /*if c, ok := token.Claims.(MapClaims); ok { err = dec.Decode(&c) - } else { - err = dec.Decode(&claims) - } + } else {*/ + err = dec.Decode(&claims) + //} // Handle decode error if err != nil { return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed} diff --git a/parser_option.go b/parser_option.go index 0442cdcd..34765ed4 100644 --- a/parser_option.go +++ b/parser_option.go @@ -5,34 +5,34 @@ import "time" // ParserOption 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 *Parser type as input and manipulates its configuration accordingly. -type ParserOption func(*Parser) +type ParserOption[T Claims] func(*Parser[T]) // WithValidMethods is an option to supply algorithm methods that the parser will check. Only those methods will be considered valid. // It is heavily encouraged to use this option in order to prevent attacks such as https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/. -func WithValidMethods(methods []string) ParserOption { - return func(p *Parser) { +func WithValidMethods[T Claims](methods []string) ParserOption[T] { + return func(p *Parser[T]) { p.validMethods = methods } } // WithJSONNumber is an option to configure the underlying JSON parser with UseNumber -func WithJSONNumber() ParserOption { - return func(p *Parser) { +func WithJSONNumber[T Claims]() ParserOption[T] { + return func(p *Parser[T]) { p.useJSONNumber = true } } // WithoutClaimsValidation is an option to disable claims validation. This option should only be used if you exactly know // what you are doing. -func WithoutClaimsValidation() ParserOption { - return func(p *Parser) { +func WithoutClaimsValidation[T Claims]() ParserOption[T] { + return func(p *Parser[T]) { p.skipClaimsValidation = true } } // WithLeeway returns the ParserOption for specifying the leeway window. -func WithLeeway(leeway time.Duration) ParserOption { - return func(p *Parser) { +func WithLeeway[T Claims](leeway time.Duration) ParserOption[T] { + return func(p *Parser[T]) { p.validator.leeway = leeway } } @@ -40,16 +40,16 @@ func WithLeeway(leeway time.Duration) ParserOption { // WithTimeFunc returns the ParserOption for specifying the time func. The // primary use-case for this is testing. If you are looking for a way to account // for clock-skew, WithLeeway should be used instead. -func WithTimeFunc(f func() time.Time) ParserOption { - return func(p *Parser) { +func WithTimeFunc[T Claims](f func() time.Time) ParserOption[T] { + return func(p *Parser[T]) { p.validator.timeFunc = f } } // WithIssuedAt returns the ParserOption to enable verification // of issued-at. -func WithIssuedAt() ParserOption { - return func(p *Parser) { +func WithIssuedAt[T Claims]() ParserOption[T] { + return func(p *Parser[T]) { p.validator.verifyIat = true } } @@ -61,8 +61,8 @@ func WithIssuedAt() ParserOption { // NOTE: While the `aud` claim is OPTIONAL is a JWT, the handling of it is // application-specific. Since this validation API is helping developers in // writing secure application, we decided to REQUIRE the existence of the claim. -func WithAudience(aud string) ParserOption { - return func(p *Parser) { +func WithAudience[T Claims](aud string) ParserOption[T] { + return func(p *Parser[T]) { p.validator.expectedAud = aud } } @@ -74,8 +74,8 @@ func WithAudience(aud string) ParserOption { // NOTE: While the `iss` claim is OPTIONAL is a JWT, the handling of it is // application-specific. Since this validation API is helping developers in // writing secure application, we decided to REQUIRE the existence of the claim. -func WithIssuer(iss string) ParserOption { - return func(p *Parser) { +func WithIssuer[T Claims](iss string) ParserOption[T] { + return func(p *Parser[T]) { p.validator.expectedIss = iss } } @@ -87,8 +87,8 @@ func WithIssuer(iss string) ParserOption { // NOTE: While the `sub` claim is OPTIONAL is a JWT, the handling of it is // application-specific. Since this validation API is helping developers in // writing secure application, we decided to REQUIRE the existence of the claim. -func WithSubject(sub string) ParserOption { - return func(p *Parser) { +func WithSubject[T Claims](sub string) ParserOption[T] { + return func(p *Parser[T]) { p.validator.expectedSub = sub } } diff --git a/token.go b/token.go index d9e5d566..9a05bf57 100644 --- a/token.go +++ b/token.go @@ -23,27 +23,27 @@ var DecodeStrict bool // the key for verification. The function receives the parsed, // but unverified Token. This allows you to use properties in the // Header of the token (such as `kid`) to identify which key to use. -type Keyfunc func(*Token) (interface{}, error) +type Keyfunc[T Claims] func(*Token[T]) (interface{}, error) // Token represents a JWT Token. Different fields will be used depending on whether you're // creating or parsing/verifying a token. -type Token struct { +type Token[T Claims] struct { Raw string // The raw token. Populated when you Parse a token Method SigningMethod // The signing method used or to be used Header map[string]interface{} // The first segment of the token - Claims Claims // The second segment of the token + Claims T // The second segment of the token Signature string // The third segment of the token. Populated when you Parse a token Valid bool // Is the token valid? Populated when you Parse/Verify a token } // New creates a new Token with the specified signing method and an empty map of claims. -func New(method SigningMethod) *Token { +func New(method SigningMethod) *Token[MapClaims] { return NewWithClaims(method, MapClaims{}) } // NewWithClaims creates a new Token with the specified signing method and claims. -func NewWithClaims(method SigningMethod, claims Claims) *Token { - return &Token{ +func NewWithClaims[T Claims](method SigningMethod, claims T) *Token[T] { + return &Token[T]{ Header: map[string]interface{}{ "typ": "JWT", "alg": method.Alg(), @@ -55,7 +55,7 @@ func NewWithClaims(method SigningMethod, claims Claims) *Token { // SignedString creates and returns a complete, signed JWT. // The token is signed using the SigningMethod specified in the token. -func (t *Token) SignedString(key interface{}) (string, error) { +func (t *Token[T]) SignedString(key interface{}) (string, error) { var sig, sstr string var err error if sstr, err = t.SigningString(); err != nil { @@ -71,7 +71,7 @@ func (t *Token) SignedString(key interface{}) (string, error) { // most expensive part of the whole deal. Unless you // need this for something special, just go straight for // the SignedString. -func (t *Token) SigningString() (string, error) { +func (t *Token[T]) SigningString() (string, error) { var err error var jsonValue []byte @@ -95,8 +95,8 @@ func (t *Token) SigningString() (string, error) { // validate the 'alg' claim in the token matches the expected algorithm. // For more details about the importance of validating the 'alg' claim, // see https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/ -func Parse(tokenString string, keyFunc Keyfunc, options ...ParserOption) (*Token, error) { - return NewParser(options...).Parse(tokenString, keyFunc) +func Parse(tokenString string, keyFunc Keyfunc[MapClaims], options ...ParserOption[MapClaims]) (*Token[MapClaims], error) { + return NewParser[MapClaims](options...).ParseWithClaims(tokenString, MapClaims{}, keyFunc) } // ParseWithClaims is a shortcut for NewParser().ParseWithClaims(). @@ -104,8 +104,8 @@ func Parse(tokenString string, keyFunc Keyfunc, options ...ParserOption) (*Token // Note: If you provide a custom claim implementation that embeds one of the standard claims (such as RegisteredClaims), // make sure that a) you either embed a non-pointer version of the claims or b) if you are using a pointer, allocate the // proper memory for it before passing in the overall claims, otherwise you might run into a panic. -func ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc, options ...ParserOption) (*Token, error) { - return NewParser(options...).ParseWithClaims(tokenString, claims, keyFunc) +func ParseWithClaims[T Claims](tokenString string, claims T, keyFunc Keyfunc[T], options ...ParserOption[T]) (*Token[T], error) { + return NewParser[T](options...).ParseWithClaims(tokenString, claims, keyFunc) } // EncodeSegment encodes a JWT specific base64url encoding with padding stripped diff --git a/token_test.go b/token_test.go index 52a00212..bcf57358 100644 --- a/token_test.go +++ b/token_test.go @@ -40,7 +40,7 @@ func TestToken_SigningString(t1 *testing.T) { } for _, tt := range tests { t1.Run(tt.name, func(t1 *testing.T) { - t := &jwt.Token{ + t := &jwt.Token[jwt.RegisteredClaims]{ Raw: tt.fields.Raw, Method: tt.fields.Method, Header: tt.fields.Header, @@ -61,7 +61,7 @@ func TestToken_SigningString(t1 *testing.T) { } func BenchmarkToken_SigningString(b *testing.B) { - t := &jwt.Token{ + t := &jwt.Token[jwt.RegisteredClaims]{ Method: jwt.SigningMethodHS256, Header: map[string]interface{}{ "typ": "JWT", diff --git a/validator.go b/validator.go index 3e512f67..233e2399 100644 --- a/validator.go +++ b/validator.go @@ -48,8 +48,8 @@ type CustomClaims interface { // newValidator can be used to create a stand-alone validator with the supplied // options. This validator can then be used to validate already parsed claims. -func newValidator(opts ...ParserOption) *validator { - p := NewParser(opts...) +func newValidator[T Claims](opts ...ParserOption) *validator { + p := NewParser[T](opts...) return p.validator } From a73d8a19764d479ac0af6fb5e916bd4b78a097fe Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Fri, 17 Feb 2023 22:09:52 +0100 Subject: [PATCH 4/7] Unbreak some stuff --- map_claims_test.go | 18 +++++++++--------- request/request.go | 24 ++++++++++++------------ validator.go | 2 +- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/map_claims_test.go b/map_claims_test.go index b4af574b..37056e70 100644 --- a/map_claims_test.go +++ b/map_claims_test.go @@ -56,13 +56,13 @@ func TestVerifyAud(t *testing.T) { for _, test := range tests { t.Run(test.Name, func(t *testing.T) { - var opts []ParserOption + var opts []ParserOption[MapClaims] if test.Required { - opts = append(opts, WithAudience(test.Comparison)) + opts = append(opts, WithAudience[MapClaims](test.Comparison)) } - validator := newValidator(opts...) + validator := newValidator[MapClaims](opts...) got := validator.Validate(test.MapClaims) if (got == nil) != test.Expected { @@ -77,7 +77,7 @@ func TestMapclaimsVerifyIssuedAtInvalidTypeString(t *testing.T) { "iat": "foo", } want := false - got := newValidator(WithIssuedAt()).Validate(mapClaims) + got := newValidator[MapClaims](WithIssuedAt[MapClaims]()).Validate(mapClaims) if want != (got == nil) { t.Fatalf("Failed to verify claims, wanted: %v got %v", want, (got == nil)) } @@ -88,7 +88,7 @@ func TestMapclaimsVerifyNotBeforeInvalidTypeString(t *testing.T) { "nbf": "foo", } want := false - got := newValidator().Validate(mapClaims) + got := newValidator[MapClaims]().Validate(mapClaims) if want != (got == nil) { t.Fatalf("Failed to verify claims, wanted: %v got %v", want, (got == nil)) } @@ -99,7 +99,7 @@ func TestMapclaimsVerifyExpiresAtInvalidTypeString(t *testing.T) { "exp": "foo", } want := false - got := newValidator().Validate(mapClaims) + got := newValidator[MapClaims]().Validate(mapClaims) if want != (got == nil) { t.Fatalf("Failed to verify claims, wanted: %v got %v", want, (got == nil)) @@ -112,14 +112,14 @@ func TestMapClaimsVerifyExpiresAtExpire(t *testing.T) { "exp": float64(exp.Unix()), } want := false - got := newValidator(WithTimeFunc(func() time.Time { + got := newValidator[MapClaims](WithTimeFunc[MapClaims](func() time.Time { return exp })).Validate(mapClaims) if want != (got == nil) { t.Fatalf("Failed to verify claims, wanted: %v got %v", want, (got == nil)) } - got = newValidator(WithTimeFunc(func() time.Time { + got = newValidator[MapClaims](WithTimeFunc[MapClaims](func() time.Time { return exp.Add(1 * time.Second) })).Validate(mapClaims) if want != (got == nil) { @@ -127,7 +127,7 @@ func TestMapClaimsVerifyExpiresAtExpire(t *testing.T) { } want = true - got = newValidator(WithTimeFunc(func() time.Time { + got = newValidator[MapClaims](WithTimeFunc[MapClaims](func() time.Time { return exp.Add(-1 * time.Second) })).Validate(mapClaims) if want != (got == nil) { diff --git a/request/request.go b/request/request.go index 5723c809..dc848ee1 100644 --- a/request/request.go +++ b/request/request.go @@ -12,9 +12,9 @@ import ( // the logic for extracting a token. Several useful implementations are provided. // // You can provide options to modify parsing behavior -func ParseFromRequest(req *http.Request, extractor Extractor, keyFunc jwt.Keyfunc, options ...ParseFromRequestOption) (token *jwt.Token, err error) { +func ParseFromRequest(req *http.Request, extractor Extractor, keyFunc jwt.Keyfunc[jwt.MapClaims], options ...ParseFromRequestOption[jwt.MapClaims]) (token *jwt.Token[jwt.MapClaims], err error) { // Create basic parser struct - p := &fromRequestParser{req, extractor, nil, nil} + p := &fromRequestParser[jwt.MapClaims]{req, extractor, nil, nil} // Handle options for _, option := range options { @@ -26,7 +26,7 @@ func ParseFromRequest(req *http.Request, extractor Extractor, keyFunc jwt.Keyfun p.claims = jwt.MapClaims{} } if p.parser == nil { - p.parser = &jwt.Parser{} + p.parser = &jwt.Parser[jwt.MapClaims]{} } // perform extract @@ -42,29 +42,29 @@ func ParseFromRequest(req *http.Request, extractor Extractor, keyFunc jwt.Keyfun // ParseFromRequestWithClaims is an alias for ParseFromRequest but with custom Claims type. // // Deprecated: use ParseFromRequest and the WithClaims option -func ParseFromRequestWithClaims(req *http.Request, extractor Extractor, claims jwt.Claims, keyFunc jwt.Keyfunc) (token *jwt.Token, err error) { +func ParseFromRequestWithClaims[T jwt.Claims](req *http.Request, extractor Extractor, claims T, keyFunc jwt.Keyfunc[T]) (token *jwt.Token[T], err error) { return ParseFromRequest(req, extractor, keyFunc, WithClaims(claims)) } -type fromRequestParser struct { +type fromRequestParser[T jwt.Claims] struct { req *http.Request extractor Extractor - claims jwt.Claims - parser *jwt.Parser + claims T + parser *jwt.Parser[T] } -type ParseFromRequestOption func(*fromRequestParser) +type ParseFromRequestOption[T jwt.Claims] func(*fromRequestParser[T]) // WithClaims parses with custom claims -func WithClaims(claims jwt.Claims) ParseFromRequestOption { - return func(p *fromRequestParser) { +func WithClaims[T jwt.Claims](claims T) ParseFromRequestOption[T] { + return func(p *fromRequestParser[T]) { p.claims = claims } } // WithParser parses using a custom parser -func WithParser(parser *jwt.Parser) ParseFromRequestOption { - return func(p *fromRequestParser) { +func WithParser[T jwt.Claims](parser *jwt.Parser[T]) ParseFromRequestOption[T] { + return func(p *fromRequestParser[T]) { p.parser = parser } } diff --git a/validator.go b/validator.go index 233e2399..be4860cc 100644 --- a/validator.go +++ b/validator.go @@ -48,7 +48,7 @@ type CustomClaims interface { // newValidator can be used to create a stand-alone validator with the supplied // options. This validator can then be used to validate already parsed claims. -func newValidator[T Claims](opts ...ParserOption) *validator { +func newValidator[T Claims](opts ...ParserOption[T]) *validator { p := NewParser[T](opts...) return p.validator } From a9fd512a31013227b39b36ef7aa079c7fe6b06bf Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Fri, 17 Feb 2023 22:45:11 +0100 Subject: [PATCH 5/7] Made parser option non-generic --- example_test.go | 4 ++-- parser.go | 4 ++-- parser_option.go | 38 +++++++++++++++++++------------------- token.go | 4 ++-- validator.go | 2 +- 5 files changed, 26 insertions(+), 26 deletions(-) diff --git a/example_test.go b/example_test.go index ab6d9a2f..10ab94ca 100644 --- a/example_test.go +++ b/example_test.go @@ -105,7 +105,7 @@ func ExampleParseWithClaims_validationOptions() { token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{}, func(token *jwt.Token[*MyCustomClaims]) (interface{}, error) { return []byte("AllYourBase"), nil - }, jwt.WithLeeway[*MyCustomClaims](5*time.Second)) + }, jwt.WithLeeway(5*time.Second)) if claims := token.Claims; token.Valid { fmt.Printf("%v %v", claims.Foo, claims.RegisteredClaims.Issuer) @@ -138,7 +138,7 @@ func ExampleParseWithClaims_customValidation() { token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{}, func(token *jwt.Token[*MyCustomClaims]) (interface{}, error) { return []byte("AllYourBase"), nil - }, jwt.WithLeeway[*MyCustomClaims](5*time.Second)) + }, jwt.WithLeeway(5*time.Second)) if claims := token.Claims; token.Valid { fmt.Printf("%v %v", claims.Foo, claims.RegisteredClaims.Issuer) diff --git a/parser.go b/parser.go index be9c914d..15fa4c1e 100644 --- a/parser.go +++ b/parser.go @@ -21,14 +21,14 @@ type Parser[T Claims] struct { } // NewParser creates a new Parser with the specified options -func NewParser[T Claims](options ...ParserOption[T]) *Parser[T] { +func NewParser[T Claims](options ...ParserOption) *Parser[T] { p := &Parser[T]{ validator: &validator{}, } // Loop through our parsing options and apply them for _, option := range options { - option(p) + option((*Parser[Claims])(p)) } return p diff --git a/parser_option.go b/parser_option.go index 34765ed4..d23411c9 100644 --- a/parser_option.go +++ b/parser_option.go @@ -5,34 +5,34 @@ import "time" // ParserOption 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 *Parser type as input and manipulates its configuration accordingly. -type ParserOption[T Claims] func(*Parser[T]) +type ParserOption func(*Parser[Claims]) // WithValidMethods is an option to supply algorithm methods that the parser will check. Only those methods will be considered valid. // It is heavily encouraged to use this option in order to prevent attacks such as https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/. -func WithValidMethods[T Claims](methods []string) ParserOption[T] { - return func(p *Parser[T]) { +func WithValidMethods(methods []string) ParserOption { + return func(p *Parser[Claims]) { p.validMethods = methods } } // WithJSONNumber is an option to configure the underlying JSON parser with UseNumber -func WithJSONNumber[T Claims]() ParserOption[T] { - return func(p *Parser[T]) { +func WithJSONNumber() ParserOption { + return func(p *Parser[Claims]) { p.useJSONNumber = true } } // WithoutClaimsValidation is an option to disable claims validation. This option should only be used if you exactly know // what you are doing. -func WithoutClaimsValidation[T Claims]() ParserOption[T] { - return func(p *Parser[T]) { +func WithoutClaimsValidation() ParserOption { + return func(p *Parser[Claims]) { p.skipClaimsValidation = true } } // WithLeeway returns the ParserOption for specifying the leeway window. -func WithLeeway[T Claims](leeway time.Duration) ParserOption[T] { - return func(p *Parser[T]) { +func WithLeeway(leeway time.Duration) ParserOption { + return func(p *Parser[Claims]) { p.validator.leeway = leeway } } @@ -40,16 +40,16 @@ func WithLeeway[T Claims](leeway time.Duration) ParserOption[T] { // WithTimeFunc returns the ParserOption for specifying the time func. The // primary use-case for this is testing. If you are looking for a way to account // for clock-skew, WithLeeway should be used instead. -func WithTimeFunc[T Claims](f func() time.Time) ParserOption[T] { - return func(p *Parser[T]) { +func WithTimeFunc(f func() time.Time) ParserOption { + return func(p *Parser[Claims]) { p.validator.timeFunc = f } } // WithIssuedAt returns the ParserOption to enable verification // of issued-at. -func WithIssuedAt[T Claims]() ParserOption[T] { - return func(p *Parser[T]) { +func WithIssuedAt() ParserOption { + return func(p *Parser[Claims]) { p.validator.verifyIat = true } } @@ -61,8 +61,8 @@ func WithIssuedAt[T Claims]() ParserOption[T] { // NOTE: While the `aud` claim is OPTIONAL is a JWT, the handling of it is // application-specific. Since this validation API is helping developers in // writing secure application, we decided to REQUIRE the existence of the claim. -func WithAudience[T Claims](aud string) ParserOption[T] { - return func(p *Parser[T]) { +func WithAudience(aud string) ParserOption { + return func(p *Parser[Claims]) { p.validator.expectedAud = aud } } @@ -74,8 +74,8 @@ func WithAudience[T Claims](aud string) ParserOption[T] { // NOTE: While the `iss` claim is OPTIONAL is a JWT, the handling of it is // application-specific. Since this validation API is helping developers in // writing secure application, we decided to REQUIRE the existence of the claim. -func WithIssuer[T Claims](iss string) ParserOption[T] { - return func(p *Parser[T]) { +func WithIssuer(iss string) ParserOption { + return func(p *Parser[Claims]) { p.validator.expectedIss = iss } } @@ -87,8 +87,8 @@ func WithIssuer[T Claims](iss string) ParserOption[T] { // NOTE: While the `sub` claim is OPTIONAL is a JWT, the handling of it is // application-specific. Since this validation API is helping developers in // writing secure application, we decided to REQUIRE the existence of the claim. -func WithSubject[T Claims](sub string) ParserOption[T] { - return func(p *Parser[T]) { +func WithSubject(sub string) ParserOption { + return func(p *Parser[Claims]) { p.validator.expectedSub = sub } } diff --git a/token.go b/token.go index 9a05bf57..bf98d677 100644 --- a/token.go +++ b/token.go @@ -95,7 +95,7 @@ func (t *Token[T]) SigningString() (string, error) { // validate the 'alg' claim in the token matches the expected algorithm. // For more details about the importance of validating the 'alg' claim, // see https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/ -func Parse(tokenString string, keyFunc Keyfunc[MapClaims], options ...ParserOption[MapClaims]) (*Token[MapClaims], error) { +func Parse(tokenString string, keyFunc Keyfunc[MapClaims], options ...ParserOption) (*Token[MapClaims], error) { return NewParser[MapClaims](options...).ParseWithClaims(tokenString, MapClaims{}, keyFunc) } @@ -104,7 +104,7 @@ func Parse(tokenString string, keyFunc Keyfunc[MapClaims], options ...ParserOpti // Note: If you provide a custom claim implementation that embeds one of the standard claims (such as RegisteredClaims), // make sure that a) you either embed a non-pointer version of the claims or b) if you are using a pointer, allocate the // proper memory for it before passing in the overall claims, otherwise you might run into a panic. -func ParseWithClaims[T Claims](tokenString string, claims T, keyFunc Keyfunc[T], options ...ParserOption[T]) (*Token[T], error) { +func ParseWithClaims[T Claims](tokenString string, claims T, keyFunc Keyfunc[T], options ...ParserOption) (*Token[T], error) { return NewParser[T](options...).ParseWithClaims(tokenString, claims, keyFunc) } diff --git a/validator.go b/validator.go index be4860cc..233e2399 100644 --- a/validator.go +++ b/validator.go @@ -48,7 +48,7 @@ type CustomClaims interface { // newValidator can be used to create a stand-alone validator with the supplied // options. This validator can then be used to validate already parsed claims. -func newValidator[T Claims](opts ...ParserOption[T]) *validator { +func newValidator[T Claims](opts ...ParserOption) *validator { p := NewParser[T](opts...) return p.validator } From 003fb0f0a6ac35ddc57350d5cdf33563f67d43d0 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Fri, 17 Feb 2023 23:00:58 +0100 Subject: [PATCH 6/7] Fixed all tests --- cmd/jwt/main.go | 2 +- hmac_example_test.go | 4 +-- http_example_test.go | 14 ++++----- map_claims_test.go | 12 ++++---- parser.go | 8 ++--- parser_test.go | 66 ++++++++++++++++++++--------------------- request/request.go | 24 +++++++-------- request/request_test.go | 2 +- token_test.go | 2 +- 9 files changed, 65 insertions(+), 69 deletions(-) diff --git a/cmd/jwt/main.go b/cmd/jwt/main.go index f1e49a90..ac31ce44 100644 --- a/cmd/jwt/main.go +++ b/cmd/jwt/main.go @@ -128,7 +128,7 @@ func verifyToken() error { } // Parse the token. Load the key from command line option - token, err := jwt.Parse(string(tokData), func(t *jwt.Token) (interface{}, error) { + token, err := jwt.Parse(string(tokData), func(t *jwt.Token[jwt.MapClaims]) (interface{}, error) { if isNone() { return jwt.UnsafeAllowNoneSignatureType, nil } diff --git a/hmac_example_test.go b/hmac_example_test.go index 4b2ff08a..577ca003 100644 --- a/hmac_example_test.go +++ b/hmac_example_test.go @@ -47,7 +47,7 @@ func ExampleParse_hmac() { // useful if you use multiple keys for your application. The standard is to use 'kid' in the // head of the token to identify which key to use, but the parsed token (head and claims) is provided // to the callback, providing flexibility. - token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { + token, err := jwt.Parse(tokenString, func(token *jwt.Token[jwt.MapClaims]) (interface{}, error) { // Don't forget to validate the alg is what you expect: if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) @@ -57,7 +57,7 @@ func ExampleParse_hmac() { return hmacSampleSecret, nil }) - if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { + if claims := token.Claims; token.Valid { fmt.Println(claims["foo"], claims["nbf"]) } else { fmt.Println(err) diff --git a/http_example_test.go b/http_example_test.go index 090aa4f7..b24e7d48 100644 --- a/http_example_test.go +++ b/http_example_test.go @@ -99,15 +99,14 @@ func Example_getTokenViaHTTP() { tokenString := strings.TrimSpace(buf.String()) // Parse the token - token, err := jwt.ParseWithClaims(tokenString, &CustomClaimsExample{}, func(token *jwt.Token) (interface{}, error) { + token, err := jwt.ParseWithClaims(tokenString, &CustomClaimsExample{}, func(token *jwt.Token[*CustomClaimsExample]) (interface{}, error) { // since we only use the one private key to sign the tokens, // we also only use its public counter part to verify return verifyKey, nil }) fatal(err) - claims := token.Claims.(*CustomClaimsExample) - fmt.Println(claims.CustomerInfo.Name) + fmt.Println(token.Claims.CustomerInfo.Name) //Output: test } @@ -138,10 +137,7 @@ func Example_useTokenViaHTTP() { func createToken(user string) (string, error) { // create a signer for rsa 256 - t := jwt.New(jwt.GetSigningMethod("RS256")) - - // set our claims - t.Claims = &CustomClaimsExample{ + t := jwt.NewWithClaims(jwt.GetSigningMethod("RS256"), &CustomClaimsExample{ jwt.RegisteredClaims{ // set the expire time // see https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.4 @@ -149,7 +145,7 @@ func createToken(user string) (string, error) { }, "level1", CustomerInfo{user, "human"}, - } + }) // Creat token string return t.SignedString(signKey) @@ -192,7 +188,7 @@ func authHandler(w http.ResponseWriter, r *http.Request) { // only accessible with a valid token func restrictedHandler(w http.ResponseWriter, r *http.Request) { // Get token from request - token, err := request.ParseFromRequest(r, request.OAuth2Extractor, func(token *jwt.Token) (interface{}, error) { + token, err := request.ParseFromRequest(r, request.OAuth2Extractor, func(token *jwt.Token[jwt.Claims]) (interface{}, error) { // since we only use the one private key to sign the tokens, // we also only use its public counter part to verify return verifyKey, nil diff --git a/map_claims_test.go b/map_claims_test.go index 37056e70..a0af2a88 100644 --- a/map_claims_test.go +++ b/map_claims_test.go @@ -56,10 +56,10 @@ func TestVerifyAud(t *testing.T) { for _, test := range tests { t.Run(test.Name, func(t *testing.T) { - var opts []ParserOption[MapClaims] + var opts []ParserOption if test.Required { - opts = append(opts, WithAudience[MapClaims](test.Comparison)) + opts = append(opts, WithAudience(test.Comparison)) } validator := newValidator[MapClaims](opts...) @@ -77,7 +77,7 @@ func TestMapclaimsVerifyIssuedAtInvalidTypeString(t *testing.T) { "iat": "foo", } want := false - got := newValidator[MapClaims](WithIssuedAt[MapClaims]()).Validate(mapClaims) + got := newValidator[MapClaims](WithIssuedAt()).Validate(mapClaims) if want != (got == nil) { t.Fatalf("Failed to verify claims, wanted: %v got %v", want, (got == nil)) } @@ -112,14 +112,14 @@ func TestMapClaimsVerifyExpiresAtExpire(t *testing.T) { "exp": float64(exp.Unix()), } want := false - got := newValidator[MapClaims](WithTimeFunc[MapClaims](func() time.Time { + got := newValidator[MapClaims](WithTimeFunc(func() time.Time { return exp })).Validate(mapClaims) if want != (got == nil) { t.Fatalf("Failed to verify claims, wanted: %v got %v", want, (got == nil)) } - got = newValidator[MapClaims](WithTimeFunc[MapClaims](func() time.Time { + got = newValidator[MapClaims](WithTimeFunc(func() time.Time { return exp.Add(1 * time.Second) })).Validate(mapClaims) if want != (got == nil) { @@ -127,7 +127,7 @@ func TestMapClaimsVerifyExpiresAtExpire(t *testing.T) { } want = true - got = newValidator[MapClaims](WithTimeFunc[MapClaims](func() time.Time { + got = newValidator[MapClaims](WithTimeFunc(func() time.Time { return exp.Add(-1 * time.Second) })).Validate(mapClaims) if want != (got == nil) { diff --git a/parser.go b/parser.go index 15fa4c1e..7c7f4de0 100644 --- a/parser.go +++ b/parser.go @@ -156,11 +156,11 @@ func (p *Parser[T]) ParseUnverified(tokenString string, claims T) (token *Token[ dec.UseNumber() } // JSON Decode. Special case for map type to avoid weird pointer behavior - /*if c, ok := token.Claims.(MapClaims); ok { + if c, ok := any(token.Claims).(MapClaims); ok { err = dec.Decode(&c) - } else {*/ - err = dec.Decode(&claims) - //} + } else { + err = dec.Decode(&claims) + } // Handle decode error if err != nil { return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed} diff --git a/parser_test.go b/parser_test.go index 306f8b50..7c8b3d20 100644 --- a/parser_test.go +++ b/parser_test.go @@ -22,12 +22,12 @@ var ( jwtTestEC256PublicKey crypto.PublicKey jwtTestEC256PrivateKey crypto.PrivateKey paddedKey crypto.PublicKey - defaultKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return jwtTestDefaultKey, nil } - ecdsaKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return jwtTestEC256PublicKey, nil } - paddedKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return paddedKey, nil } - emptyKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return nil, nil } - errorKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return nil, errKeyFuncError } - nilKeyFunc jwt.Keyfunc = nil + defaultKeyFunc jwt.Keyfunc[jwt.Claims] = func(t *jwt.Token[jwt.Claims]) (interface{}, error) { return jwtTestDefaultKey, nil } + ecdsaKeyFunc jwt.Keyfunc[jwt.Claims] = func(t *jwt.Token[jwt.Claims]) (interface{}, error) { return jwtTestEC256PublicKey, nil } + paddedKeyFunc jwt.Keyfunc[jwt.Claims] = func(t *jwt.Token[jwt.Claims]) (interface{}, error) { return paddedKey, nil } + emptyKeyFunc jwt.Keyfunc[jwt.Claims] = func(t *jwt.Token[jwt.Claims]) (interface{}, error) { return nil, nil } + errorKeyFunc jwt.Keyfunc[jwt.Claims] = func(t *jwt.Token[jwt.Claims]) (interface{}, error) { return nil, errKeyFuncError } + nilKeyFunc jwt.Keyfunc[jwt.Claims] = nil ) func init() { @@ -48,12 +48,12 @@ func init() { var jwtTestData = []struct { name string tokenString string - keyfunc jwt.Keyfunc + keyfunc jwt.Keyfunc[jwt.Claims] claims jwt.Claims valid bool errors uint32 err []error - parser *jwt.Parser + parser *jwt.Parser[jwt.Claims] signingMethod jwt.SigningMethod // The method to sign the JWT token for test purpose }{ { @@ -174,7 +174,7 @@ var jwtTestData = []struct { false, jwt.ValidationErrorSignatureInvalid, []error{jwt.ErrTokenSignatureInvalid}, - jwt.NewParser(jwt.WithValidMethods([]string{"HS256"})), + jwt.NewParser[jwt.Claims](jwt.WithValidMethods([]string{"HS256"})), jwt.SigningMethodRS256, }, { @@ -185,7 +185,7 @@ var jwtTestData = []struct { true, 0, nil, - jwt.NewParser(jwt.WithValidMethods([]string{"RS256", "HS256"})), + jwt.NewParser[jwt.Claims](jwt.WithValidMethods([]string{"RS256", "HS256"})), jwt.SigningMethodRS256, }, { @@ -196,7 +196,7 @@ var jwtTestData = []struct { false, jwt.ValidationErrorSignatureInvalid, []error{jwt.ErrTokenSignatureInvalid}, - jwt.NewParser(jwt.WithValidMethods([]string{"RS256", "HS256"})), + jwt.NewParser[jwt.Claims](jwt.WithValidMethods([]string{"RS256", "HS256"})), jwt.SigningMethodES256, }, { @@ -207,7 +207,7 @@ var jwtTestData = []struct { true, 0, nil, - jwt.NewParser(jwt.WithValidMethods([]string{"HS256", "ES256"})), + jwt.NewParser[jwt.Claims](jwt.WithValidMethods([]string{"HS256", "ES256"})), jwt.SigningMethodES256, }, { @@ -218,7 +218,7 @@ var jwtTestData = []struct { true, 0, nil, - jwt.NewParser(jwt.WithJSONNumber()), + jwt.NewParser[jwt.Claims](jwt.WithJSONNumber()), jwt.SigningMethodRS256, }, { @@ -229,7 +229,7 @@ var jwtTestData = []struct { false, jwt.ValidationErrorExpired, []error{jwt.ErrTokenExpired}, - jwt.NewParser(jwt.WithJSONNumber()), + jwt.NewParser[jwt.Claims](jwt.WithJSONNumber()), jwt.SigningMethodRS256, }, { @@ -240,7 +240,7 @@ var jwtTestData = []struct { false, jwt.ValidationErrorNotValidYet, []error{jwt.ErrTokenNotValidYet}, - jwt.NewParser(jwt.WithJSONNumber()), + jwt.NewParser[jwt.Claims](jwt.WithJSONNumber()), jwt.SigningMethodRS256, }, { @@ -251,7 +251,7 @@ var jwtTestData = []struct { false, jwt.ValidationErrorNotValidYet | jwt.ValidationErrorExpired, []error{jwt.ErrTokenNotValidYet}, - jwt.NewParser(jwt.WithJSONNumber()), + jwt.NewParser[jwt.Claims](jwt.WithJSONNumber()), jwt.SigningMethodRS256, }, { @@ -262,7 +262,7 @@ var jwtTestData = []struct { true, 0, nil, - jwt.NewParser(jwt.WithJSONNumber(), jwt.WithoutClaimsValidation()), + jwt.NewParser[jwt.Claims](jwt.WithJSONNumber(), jwt.WithoutClaimsValidation()), jwt.SigningMethodRS256, }, { @@ -275,7 +275,7 @@ var jwtTestData = []struct { true, 0, nil, - jwt.NewParser(jwt.WithJSONNumber()), + jwt.NewParser[jwt.Claims](jwt.WithJSONNumber()), jwt.SigningMethodRS256, }, { @@ -288,7 +288,7 @@ var jwtTestData = []struct { true, 0, nil, - jwt.NewParser(jwt.WithJSONNumber()), + jwt.NewParser[jwt.Claims](jwt.WithJSONNumber()), jwt.SigningMethodRS256, }, { @@ -301,7 +301,7 @@ var jwtTestData = []struct { true, 0, nil, - jwt.NewParser(jwt.WithJSONNumber()), + jwt.NewParser[jwt.Claims](jwt.WithJSONNumber()), jwt.SigningMethodRS256, }, { @@ -314,7 +314,7 @@ var jwtTestData = []struct { false, jwt.ValidationErrorMalformed, []error{jwt.ErrTokenMalformed}, - jwt.NewParser(jwt.WithJSONNumber()), + jwt.NewParser[jwt.Claims](jwt.WithJSONNumber()), jwt.SigningMethodRS256, }, { @@ -327,7 +327,7 @@ var jwtTestData = []struct { false, jwt.ValidationErrorMalformed, []error{jwt.ErrTokenMalformed}, - jwt.NewParser(jwt.WithJSONNumber()), + jwt.NewParser[jwt.Claims](jwt.WithJSONNumber()), jwt.SigningMethodRS256, }, { @@ -338,7 +338,7 @@ var jwtTestData = []struct { false, jwt.ValidationErrorNotValidYet, []error{jwt.ErrTokenNotValidYet}, - jwt.NewParser(jwt.WithLeeway(time.Minute)), + jwt.NewParser[jwt.Claims](jwt.WithLeeway(time.Minute)), jwt.SigningMethodRS256, }, { @@ -349,7 +349,7 @@ var jwtTestData = []struct { true, 0, nil, - jwt.NewParser(jwt.WithLeeway(2 * time.Minute)), + jwt.NewParser[jwt.Claims](jwt.WithLeeway(2 * time.Minute)), jwt.SigningMethodRS256, }, } @@ -380,12 +380,12 @@ func TestParser_Parse(t *testing.T) { } // Parse the token - var token *jwt.Token + var token *jwt.Token[jwt.Claims] var ve *jwt.ValidationError var err error var parser = data.parser if parser == nil { - parser = jwt.NewParser() + parser = jwt.NewParser[jwt.Claims]() } // Figure out correct claims type switch data.claims.(type) { @@ -478,11 +478,11 @@ func TestParser_ParseUnverified(t *testing.T) { } // Parse the token - var token *jwt.Token + var token *jwt.Token[jwt.Claims] var err error var parser = data.parser if parser == nil { - parser = new(jwt.Parser) + parser = new(jwt.Parser[jwt.Claims]) } // Figure out correct claims type switch data.claims.(type) { @@ -523,7 +523,7 @@ var setPaddingTestData = []struct { paddedDecode bool strictDecode bool signingMethod jwt.SigningMethod - keyfunc jwt.Keyfunc + keyfunc jwt.Keyfunc[jwt.Claims] valid bool }{ { @@ -685,9 +685,9 @@ func TestSetPadding(t *testing.T) { } // Parse the token - var token *jwt.Token + var token *jwt.Token[jwt.Claims] var err error - parser := jwt.NewParser(jwt.WithoutClaimsValidation()) + parser := jwt.NewParser[jwt.Claims](jwt.WithoutClaimsValidation()) // Figure out correct claims type token, err = parser.ParseWithClaims(data.tokenString, jwt.MapClaims{}, data.keyfunc) @@ -718,7 +718,7 @@ func BenchmarkParseUnverified(b *testing.B) { // Parse the token var parser = data.parser if parser == nil { - parser = new(jwt.Parser) + parser = new(jwt.Parser[jwt.Claims]) } // Figure out correct claims type switch data.claims.(type) { @@ -735,7 +735,7 @@ func BenchmarkParseUnverified(b *testing.B) { } // Helper method for benchmarking various parsing methods -func benchmarkParsing(b *testing.B, parser *jwt.Parser, tokenString string, claims jwt.Claims) { +func benchmarkParsing(b *testing.B, parser *jwt.Parser[jwt.Claims], tokenString string, claims jwt.Claims) { b.Helper() b.ReportAllocs() b.ResetTimer() diff --git a/request/request.go b/request/request.go index dc848ee1..5cf32b4b 100644 --- a/request/request.go +++ b/request/request.go @@ -12,9 +12,9 @@ import ( // the logic for extracting a token. Several useful implementations are provided. // // You can provide options to modify parsing behavior -func ParseFromRequest(req *http.Request, extractor Extractor, keyFunc jwt.Keyfunc[jwt.MapClaims], options ...ParseFromRequestOption[jwt.MapClaims]) (token *jwt.Token[jwt.MapClaims], err error) { +func ParseFromRequest(req *http.Request, extractor Extractor, keyFunc jwt.Keyfunc[jwt.Claims], options ...ParseFromRequestOption) (token *jwt.Token[jwt.Claims], err error) { // Create basic parser struct - p := &fromRequestParser[jwt.MapClaims]{req, extractor, nil, nil} + p := &fromRequestParser{req, extractor, nil, nil} // Handle options for _, option := range options { @@ -26,7 +26,7 @@ func ParseFromRequest(req *http.Request, extractor Extractor, keyFunc jwt.Keyfun p.claims = jwt.MapClaims{} } if p.parser == nil { - p.parser = &jwt.Parser[jwt.MapClaims]{} + p.parser = &jwt.Parser[jwt.Claims]{} } // perform extract @@ -42,29 +42,29 @@ func ParseFromRequest(req *http.Request, extractor Extractor, keyFunc jwt.Keyfun // ParseFromRequestWithClaims is an alias for ParseFromRequest but with custom Claims type. // // Deprecated: use ParseFromRequest and the WithClaims option -func ParseFromRequestWithClaims[T jwt.Claims](req *http.Request, extractor Extractor, claims T, keyFunc jwt.Keyfunc[T]) (token *jwt.Token[T], err error) { +func ParseFromRequestWithClaims(req *http.Request, extractor Extractor, claims jwt.Claims, keyFunc jwt.Keyfunc[jwt.Claims]) (token *jwt.Token[jwt.Claims], err error) { return ParseFromRequest(req, extractor, keyFunc, WithClaims(claims)) } -type fromRequestParser[T jwt.Claims] struct { +type fromRequestParser struct { req *http.Request extractor Extractor - claims T - parser *jwt.Parser[T] + claims jwt.Claims + parser *jwt.Parser[jwt.Claims] } -type ParseFromRequestOption[T jwt.Claims] func(*fromRequestParser[T]) +type ParseFromRequestOption func(*fromRequestParser) // WithClaims parses with custom claims -func WithClaims[T jwt.Claims](claims T) ParseFromRequestOption[T] { - return func(p *fromRequestParser[T]) { +func WithClaims[T jwt.Claims](claims T) ParseFromRequestOption { + return func(p *fromRequestParser) { p.claims = claims } } // WithParser parses using a custom parser -func WithParser[T jwt.Claims](parser *jwt.Parser[T]) ParseFromRequestOption[T] { - return func(p *fromRequestParser[T]) { +func WithParser(parser *jwt.Parser[jwt.Claims]) ParseFromRequestOption { + return func(p *fromRequestParser) { p.parser = parser } } diff --git a/request/request_test.go b/request/request_test.go index 0906d1cf..7ad20f87 100644 --- a/request/request_test.go +++ b/request/request_test.go @@ -58,7 +58,7 @@ func TestParseRequest(t *testing.T) { // load keys from disk privateKey := test.LoadRSAPrivateKeyFromDisk("../test/sample_key") publicKey := test.LoadRSAPublicKeyFromDisk("../test/sample_key.pub") - keyfunc := func(*jwt.Token) (interface{}, error) { + keyfunc := func(*jwt.Token[jwt.Claims]) (interface{}, error) { return publicKey, nil } diff --git a/token_test.go b/token_test.go index bcf57358..9187407f 100644 --- a/token_test.go +++ b/token_test.go @@ -40,7 +40,7 @@ func TestToken_SigningString(t1 *testing.T) { } for _, tt := range tests { t1.Run(tt.name, func(t1 *testing.T) { - t := &jwt.Token[jwt.RegisteredClaims]{ + t := &jwt.Token[jwt.Claims]{ Raw: tt.fields.Raw, Method: tt.fields.Method, Header: tt.fields.Header, From 2d08912309f260061787ad26641f3eef1f9a6492 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Fri, 17 Feb 2023 23:19:23 +0100 Subject: [PATCH 7/7] Using Go 1.18 in CI --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cea5f110..697640dd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,7 +25,7 @@ jobs: strategy: fail-fast: false matrix: - go: [1.17, 1.18, 1.19] + go: [1.18, 1.19, 1.20] steps: - name: Checkout uses: actions/checkout@v3