diff --git a/CHANGELOG.md b/CHANGELOG.md index 78d64613..1950de06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ - Added JSVM `new Timezone(name)` binding for constructing `time.Location` value ([#6219](https://github.com/pocketbase/pocketbase/discussions/6219)). +- Upgraded to `golang-jwt/jwt/v5`. + ## v0.24.1 diff --git a/core/record_tokens.go b/core/record_tokens.go index e1bf7a95..0eead2b1 100644 --- a/core/record_tokens.go +++ b/core/record_tokens.go @@ -4,7 +4,7 @@ import ( "errors" "time" - "github.com/golang-jwt/jwt/v4" + "github.com/golang-jwt/jwt/v5" "github.com/pocketbase/pocketbase/tools/security" ) diff --git a/forms/apple_client_secret_create.go b/forms/apple_client_secret_create.go index 72a47e95..f07166e6 100644 --- a/forms/apple_client_secret_create.go +++ b/forms/apple_client_secret_create.go @@ -6,7 +6,7 @@ import ( "time" validation "github.com/go-ozzo/ozzo-validation/v4" - "github.com/golang-jwt/jwt/v4" + "github.com/golang-jwt/jwt/v5" "github.com/pocketbase/pocketbase/core" ) diff --git a/forms/apple_client_secret_create_test.go b/forms/apple_client_secret_create_test.go index 7bf552d2..4778d486 100644 --- a/forms/apple_client_secret_create_test.go +++ b/forms/apple_client_secret_create_test.go @@ -9,7 +9,7 @@ import ( "encoding/pem" "testing" - "github.com/golang-jwt/jwt/v4" + "github.com/golang-jwt/jwt/v5" "github.com/pocketbase/pocketbase/forms" "github.com/pocketbase/pocketbase/tests" ) diff --git a/go.mod b/go.mod index ba585902..f5f93a71 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ require ( github.com/gabriel-vasile/mimetype v1.4.8 github.com/ganigeorgiev/fexpr v0.4.1 github.com/go-ozzo/ozzo-validation/v4 v4.3.0 - github.com/golang-jwt/jwt/v4 v4.5.1 + github.com/golang-jwt/jwt/v5 v5.2.1 github.com/pocketbase/dbx v1.11.0 github.com/pocketbase/tygoja v0.0.0-20250103200817-ca580d8c5119 github.com/spf13/cast v1.7.1 diff --git a/go.sum b/go.sum index 50065253..d1d71819 100644 --- a/go.sum +++ b/go.sum @@ -113,8 +113,8 @@ github.com/go-sourcemap/sourcemap v2.1.4+incompatible/go.mod h1:F8jJfvm2KbVjc5Nq github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= -github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= -github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= diff --git a/plugins/jsvm/binds.go b/plugins/jsvm/binds.go index cf2ea05a..ae8184ee 100644 --- a/plugins/jsvm/binds.go +++ b/plugins/jsvm/binds.go @@ -18,7 +18,7 @@ import ( "github.com/dop251/goja" validation "github.com/go-ozzo/ozzo-validation/v4" - "github.com/golang-jwt/jwt/v4" + "github.com/golang-jwt/jwt/v5" "github.com/pocketbase/dbx" "github.com/pocketbase/pocketbase/apis" "github.com/pocketbase/pocketbase/core" diff --git a/tools/auth/apple.go b/tools/auth/apple.go index 8287ee19..0d442fe9 100644 --- a/tools/auth/apple.go +++ b/tools/auth/apple.go @@ -6,7 +6,7 @@ import ( "errors" "strings" - "github.com/golang-jwt/jwt/v4" + "github.com/golang-jwt/jwt/v5" "github.com/pocketbase/pocketbase/tools/types" "github.com/spf13/cast" "golang.org/x/oauth2" @@ -138,19 +138,17 @@ func (p *Apple) parseAndVerifyIdToken(idToken string) (jwt.MapClaims, error) { // validate common claims per https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_rest_api/verifying_a_user#3383769 // --- - err = claims.Valid() // exp, iat, etc. + jwtValidator := jwt.NewValidator( + jwt.WithExpirationRequired(), + jwt.WithIssuedAt(), + jwt.WithIssuer("https://appleid.apple.com"), + jwt.WithAudience(p.clientId), + ) + err = jwtValidator.Validate(claims) if err != nil { return nil, err } - if !claims.VerifyIssuer("https://appleid.apple.com", true) { - return nil, errors.New("iss must be https://appleid.apple.com") - } - - if !claims.VerifyAudience(p.clientId, true) { - return nil, errors.New("aud must be the developer's client_id") - } - // validate id_token signature // // note: this step could be technically considered optional because we trust diff --git a/tools/auth/oidc.go b/tools/auth/oidc.go index bc02524b..cc34acd5 100644 --- a/tools/auth/oidc.go +++ b/tools/auth/oidc.go @@ -12,7 +12,8 @@ import ( "net/http" "strings" - "github.com/golang-jwt/jwt/v4" + "github.com/golang-jwt/jwt/v5" + "github.com/pocketbase/pocketbase/tools/security" "github.com/pocketbase/pocketbase/tools/types" "github.com/spf13/cast" "golang.org/x/oauth2" @@ -128,24 +129,24 @@ func (p *OIDC) parseIdToken(token *oauth2.Token) (jwt.MapClaims, error) { return nil, err } - // validate common claims like exp, iat, etc. - err = claims.Valid() + // validate common claims + jwtValidator := jwt.NewValidator( + jwt.WithIssuedAt(), + jwt.WithAudience(p.clientId), + ) + err = jwtValidator.Validate(claims) if err != nil { return nil, err } - // validate aud - if !claims.VerifyAudience(p.clientId, true) { - return nil, errors.New("aud must be the developer's client_id") - } - // validate iss (if "issuers" extra config is set) issuers := cast.ToStringSlice(p.Extra()["issuers"]) if len(issuers) > 0 { var isIssValid bool + claimIssuer, _ := claims.GetIssuer() for _, issuer := range issuers { - if claims.VerifyIssuer(issuer, true) { + if security.Equal(claimIssuer, issuer) { isIssValid = true break } diff --git a/tools/security/jwt.go b/tools/security/jwt.go index 039755ba..91694686 100644 --- a/tools/security/jwt.go +++ b/tools/security/jwt.go @@ -4,8 +4,7 @@ import ( "errors" "time" - // @todo update to v5 - "github.com/golang-jwt/jwt/v4" + "github.com/golang-jwt/jwt/v5" ) // ParseUnverifiedJWT parses JWT and returns its claims @@ -19,7 +18,7 @@ func ParseUnverifiedJWT(token string) (jwt.MapClaims, error) { _, _, err := parser.ParseUnverified(token, claims) if err == nil { - err = claims.Valid() + err = jwt.NewValidator(jwt.WithIssuedAt()).Validate(claims) } return claims, err diff --git a/tools/security/jwt_test.go b/tools/security/jwt_test.go index bc1b7210..c2b44fa9 100644 --- a/tools/security/jwt_test.go +++ b/tools/security/jwt_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - "github.com/golang-jwt/jwt/v4" + "github.com/golang-jwt/jwt/v5" "github.com/pocketbase/pocketbase/tools/security" ) @@ -21,7 +21,7 @@ func TestParseUnverifiedJWT(t *testing.T) { } // properly formatted JWT with INVALID claims - // {"name": "test", "exp": 1516239022} + // {"name": "test", "exp":1516239022} result2, err2 := security.ParseUnverifiedJWT("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGVzdCIsImV4cCI6MTUxNjIzOTAyMn0.xYHirwESfSEW3Cq2BL47CEASvD_p_ps3QCA54XtNktU") if err2 == nil { t.Error("Expected error got nil") @@ -30,14 +30,24 @@ func TestParseUnverifiedJWT(t *testing.T) { t.Errorf("Expected to have 2 claims, got %v", result2) } - // properly formatted JWT with VALID claims + // properly formatted JWT with VALID claims (missing exp) // {"name": "test"} result3, err3 := security.ParseUnverifiedJWT("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGVzdCJ9.ml0QsTms3K9wMygTu41ZhKlTyjmW9zHQtoS8FUsCCjU") if err3 != nil { t.Error("Expected nil, got", err3) } if len(result3) != 1 || result3["name"] != "test" { - t.Errorf("Expected to have 2 claims, got %v", result3) + t.Errorf("Expected to have 1 claim, got %v", result3) + } + + // properly formatted JWT with VALID claims (valid exp) + // {"name": "test", "exp": 2208985261} + result4, err4 := security.ParseUnverifiedJWT("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGVzdCIsImV4cCI6MjIwODk4NTI2MX0._0KQu60hYNx5wkBIpEaoX35shXRicb0X_0VdWKWb-3k") + if err4 != nil { + t.Error("Expected nil, got", err4) + } + if len(result4) != 2 || result4["name"] != "test" { + t.Errorf("Expected to have 2 claims, got %v", result4) } }