From 01cc44246a9a6baaab00baa9971eefd99381aed9 Mon Sep 17 00:00:00 2001 From: "Alessandro (Ale) Segala" <43508+ItalyPaleAle@users.noreply.github.com> Date: Mon, 31 Aug 2026 14:12:35 -0700 Subject: [PATCH] chore: upgrade jwx to v4 (#1730) --- backend/go.mod | 9 +++--- backend/go.sum | 18 +++++------- backend/internal/cmds/key_rotate.go | 2 +- .../controller/well_known_controller_test.go | 2 +- backend/internal/oidc/claims_service_test.go | 2 +- backend/internal/oidc/end_session_service.go | 6 ++-- .../internal/oidc/end_session_service_test.go | 4 +-- .../internal/oidc/federated_client_auth.go | 29 +++++++++---------- .../oidc/federated_client_auth_test.go | 6 ++-- .../oidc/introspection_handler_test.go | 6 ++-- backend/internal/oidc/module.go | 2 +- backend/internal/oidc/preview.go | 2 +- backend/internal/oidc/provider_test.go | 2 +- backend/internal/service/e2etest_service.go | 6 ++-- backend/internal/service/jwt_service.go | 15 ++++------ backend/internal/service/jwt_service_test.go | 24 ++++++++------- backend/internal/utils/jwk/key_provider.go | 2 +- .../utils/jwk/key_provider_database.go | 2 +- .../utils/jwk/key_provider_database_test.go | 6 ++-- backend/internal/utils/jwk/public_key.go | 2 +- backend/internal/utils/jwk/public_key_test.go | 6 ++-- backend/internal/utils/jwk/utils.go | 6 ++-- backend/internal/utils/jwk/utils_test.go | 24 +++++++-------- backend/internal/utils/jwt_util.go | 6 ++-- backend/internal/webauthn/module.go | 2 +- backend/internal/webauthn/service_test.go | 6 ++-- 26 files changed, 94 insertions(+), 103 deletions(-) diff --git a/backend/go.mod b/backend/go.mod index bb9f22f7..9eb00002 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -28,8 +28,9 @@ require ( github.com/jackc/pgx/v5 v5.10.0 github.com/jinzhu/copier v0.4.0 github.com/joho/godotenv v1.5.1 + github.com/jwx-go/jwkfetch/v4 v4.0.4 github.com/lestrrat-go/httprc/v3 v3.0.6 - github.com/lestrrat-go/jwx/v3 v3.2.0 + github.com/lestrrat-go/jwx/v4 v4.4.0 github.com/libtnb/sqlite v1.2.2 github.com/lmittmann/tint v1.2.0 github.com/mattn/go-isatty v0.0.24 @@ -86,7 +87,6 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cloudwego/base64x v0.1.7 // indirect github.com/cristalhq/jwt/v5 v5.4.0 // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1 // indirect github.com/dgraph-io/ristretto/v2 v2.4.0 // indirect github.com/disintegration/gift v1.2.1 // indirect github.com/dsoprea/go-exif v0.0.0-20230826092837-6579e82b732d // indirect @@ -139,10 +139,10 @@ require ( github.com/klauspost/cpuid/v2 v2.4.0 // indirect github.com/leodido/go-urn v1.5.0 // indirect github.com/lestrrat-go/blackmagic v1.0.4 // indirect - github.com/lestrrat-go/dsig v1.3.0 // indirect - github.com/lestrrat-go/dsig-secp256k1 v1.0.0 // indirect + github.com/lestrrat-go/dsig v1.4.0 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect github.com/lestrrat-go/option/v2 v2.0.0 // indirect + github.com/lestrrat-go/option/v3 v3.0.0-alpha1 // indirect github.com/lib/pq v1.12.3 // indirect github.com/mattn/go-sqlite3 v1.14.42 // indirect github.com/mattn/goveralls v0.0.12 // indirect @@ -171,7 +171,6 @@ require ( github.com/robfig/cron/v3 v3.0.1 // indirect github.com/sagikazarmark/locafero v0.12.0 // indirect github.com/seatgeek/logrus-gelf-formatter v0.0.0-20210414080842-5b05eb8ff761 // indirect - github.com/segmentio/asm v1.2.1 // indirect github.com/sirupsen/logrus v1.9.4 // indirect github.com/spf13/afero v1.15.0 // indirect github.com/spf13/cast v1.10.0 // indirect diff --git a/backend/go.sum b/backend/go.sum index d9aa2475..3307e596 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -78,8 +78,6 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1 h1:5RVFMOWjMyRy8cARdy79nAmgYw3hK/4HUq48LQ6Wwqo= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= github.com/dgraph-io/ristretto/v2 v2.4.0 h1:I/w09yLjhdcVD2QV192UJcq8dPBaAJb9pOuMyNy0XlU= github.com/dgraph-io/ristretto/v2 v2.4.0/go.mod h1:0KsrXtXvnv0EqnzyowllbVJB8yBonswa2lTCK2gGo9E= github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38= @@ -282,6 +280,8 @@ github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jwx-go/jwkfetch/v4 v4.0.4 h1:fKgCdegz9WTnBAemDCj1QzPZYIuobg9k2qo+HZkXgmA= +github.com/jwx-go/jwkfetch/v4 v4.0.4/go.mod h1:dTGEkaGuxg9vcCR1F2Dk+TnwmXcZpeUGahNWKtbcKu8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.19.1 h1:VsB4HPswih7mmZ8WleSFQ75c/Ui1M4trX5oAsJnhSlk= @@ -306,18 +306,18 @@ github.com/leodido/go-urn v1.5.0 h1:pLqT2kq1zpHW/1D18QMjMpdtX7cekxqtJJjg5ANyWw0= github.com/leodido/go-urn v1.5.0/go.mod h1:9BORnCDhdPBJNDEX+w1bJisa8yOKYi116VeO96s4ifE= github.com/lestrrat-go/blackmagic v1.0.4 h1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA= github.com/lestrrat-go/blackmagic v1.0.4/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw= -github.com/lestrrat-go/dsig v1.3.0 h1:phjMOCXvYzhuIgn7Voe2rex8z166vGfxRxmqM25P9/Q= -github.com/lestrrat-go/dsig v1.3.0/go.mod h1:RD2eOaidyPvpc7IJQoO3Qq52RWdy8ZcJs8lrOnoa1Kc= -github.com/lestrrat-go/dsig-secp256k1 v1.0.0 h1:JpDe4Aybfl0soBvoVwjqDbp+9S1Y2OM7gcrVVMFPOzY= -github.com/lestrrat-go/dsig-secp256k1 v1.0.0/go.mod h1:CxUgAhssb8FToqbL8NjSPoGQlnO4w3LG1P0qPWQm/NU= +github.com/lestrrat-go/dsig v1.4.0 h1:g7LUjK8cT74A5DzBXJI5HzsJuLhoYN0Wzj4nuOMIrH8= +github.com/lestrrat-go/dsig v1.4.0/go.mod h1:I8Nddg/vN2cUl/h8N7SRRApLnNNeyZPIqLYpvpOtGGo= github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= github.com/lestrrat-go/httprc/v3 v3.0.6 h1:4FpLQ18KK/ypPbVU3NLWJNRvH3kcYiqKqWfKGqNWxxI= github.com/lestrrat-go/httprc/v3 v3.0.6/go.mod h1:mSMtkZW92Z98M5YoNNztbRGxbXHql7tSitCvaxvo9l0= -github.com/lestrrat-go/jwx/v3 v3.2.0 h1:Jb3zBASTSZXz7gzzSAfYqxXF8KejvKC4xWoePLQqXCA= -github.com/lestrrat-go/jwx/v3 v3.2.0/go.mod h1:38vQ8iWKq3qRSbilbzvzdQPuywhowwuR03lhkYskyrw= +github.com/lestrrat-go/jwx/v4 v4.4.0 h1:CzoK8+u++WF7vVEmxx9fB8VaheeXWZ698F6HZbrl6SI= +github.com/lestrrat-go/jwx/v4 v4.4.0/go.mod h1:65utsGK/iSrjgGfu6iqj/TAvSfia6SSXkRpjHcKcTyg= github.com/lestrrat-go/option/v2 v2.0.0 h1:XxrcaJESE1fokHy3FpaQ/cXW8ZsIdWcdFzzLOcID3Ss= github.com/lestrrat-go/option/v2 v2.0.0/go.mod h1:oSySsmzMoR0iRzCDCaUfsCzxQHUEuhOViQObyy7S6Vg= +github.com/lestrrat-go/option/v3 v3.0.0-alpha1 h1:dvdzLwm/Ba5CJUF3jQP7w/iNYSLfy7yyh9XXNa1WjxI= +github.com/lestrrat-go/option/v3 v3.0.0-alpha1/go.mod h1:5KSg20dfsKkNJtjDmaQRLZVXuUrzuCCcz/gbDK0pfKk= github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ= github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= github.com/libtnb/sqlite v1.2.2 h1:Ku5hAPP5B3A4kQcDn4Z3qyNMafKzuAbnFZX4oD9wR4I= @@ -421,8 +421,6 @@ github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88ee github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI= github.com/seatgeek/logrus-gelf-formatter v0.0.0-20210414080842-5b05eb8ff761 h1:0b8DF5kR0PhRoRXDiEEdzrgBc8UqVY4JWLkQJCRsLME= github.com/seatgeek/logrus-gelf-formatter v0.0.0-20210414080842-5b05eb8ff761/go.mod h1:/THDZYi7F/BsVEcYzYPqdcWFQ+1C2InkawTKfLOAnzg= -github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0= -github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= diff --git a/backend/internal/cmds/key_rotate.go b/backend/internal/cmds/key_rotate.go index 077ea2c9..3f8c8f2c 100644 --- a/backend/internal/cmds/key_rotate.go +++ b/backend/internal/cmds/key_rotate.go @@ -7,7 +7,7 @@ import ( "os" "strings" - "github.com/lestrrat-go/jwx/v3/jwa" + "github.com/lestrrat-go/jwx/v4/jwa" "github.com/spf13/cobra" "gorm.io/gorm" diff --git a/backend/internal/controller/well_known_controller_test.go b/backend/internal/controller/well_known_controller_test.go index 516b851c..2e1fa9e7 100644 --- a/backend/internal/controller/well_known_controller_test.go +++ b/backend/internal/controller/well_known_controller_test.go @@ -7,7 +7,7 @@ import ( "testing" "github.com/gin-gonic/gin" - "github.com/lestrrat-go/jwx/v3/jwa" + "github.com/lestrrat-go/jwx/v4/jwa" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/backend/internal/oidc/claims_service_test.go b/backend/internal/oidc/claims_service_test.go index 418fa630..78d9fbba 100644 --- a/backend/internal/oidc/claims_service_test.go +++ b/backend/internal/oidc/claims_service_test.go @@ -4,7 +4,7 @@ import ( "context" "testing" - "github.com/lestrrat-go/jwx/v3/jwa" + "github.com/lestrrat-go/jwx/v4/jwa" "github.com/ory/fosite" "github.com/stretchr/testify/require" "gorm.io/gorm" diff --git a/backend/internal/oidc/end_session_service.go b/backend/internal/oidc/end_session_service.go index d63144bd..6680f76d 100644 --- a/backend/internal/oidc/end_session_service.go +++ b/backend/internal/oidc/end_session_service.go @@ -6,7 +6,7 @@ import ( "net/url" "time" - "github.com/lestrrat-go/jwx/v3/jwt" + "github.com/lestrrat-go/jwx/v4/jwt" "github.com/pocket-id/pocket-id/backend/internal/apperror" "github.com/pocket-id/pocket-id/backend/internal/common" "github.com/pocket-id/pocket-id/backend/internal/dto" @@ -116,8 +116,8 @@ func (s *endSessionService) verifyIDTokenHint(tokenString string) (jwt.Token, er // id_token_hint must be an ID token, never an access token (both are signed with the same // key). An expired ID token is still accepted here, as required by OIDC RP-Initiated Logout. - var tokenType string - if err := token.Get(common.TokenTypeClaim, &tokenType); err != nil || tokenType != idTokenType { + tokenType, err := jwt.Get[string](token, common.TokenTypeClaim) + if err != nil || tokenType != idTokenType { return nil, apperror.TokenInvalid() } diff --git a/backend/internal/oidc/end_session_service_test.go b/backend/internal/oidc/end_session_service_test.go index 60c6a942..09e9a91d 100644 --- a/backend/internal/oidc/end_session_service_test.go +++ b/backend/internal/oidc/end_session_service_test.go @@ -9,8 +9,8 @@ import ( "testing" "time" - "github.com/lestrrat-go/jwx/v3/jwa" - "github.com/lestrrat-go/jwx/v3/jwt" + "github.com/lestrrat-go/jwx/v4/jwa" + "github.com/lestrrat-go/jwx/v4/jwt" "github.com/stretchr/testify/require" "github.com/pocket-id/pocket-id/backend/internal/apperror" diff --git a/backend/internal/oidc/federated_client_auth.go b/backend/internal/oidc/federated_client_auth.go index 74a51ab4..1c75cf21 100644 --- a/backend/internal/oidc/federated_client_auth.go +++ b/backend/internal/oidc/federated_client_auth.go @@ -11,11 +11,12 @@ import ( "strings" "time" + "github.com/jwx-go/jwkfetch/v4" "github.com/lestrrat-go/httprc/v3" "github.com/lestrrat-go/httprc/v3/errsink" - "github.com/lestrrat-go/jwx/v3/jwk" - "github.com/lestrrat-go/jwx/v3/jws" - "github.com/lestrrat-go/jwx/v3/jwt" + "github.com/lestrrat-go/jwx/v4/jwk" + "github.com/lestrrat-go/jwx/v4/jws" + "github.com/lestrrat-go/jwx/v4/jwt" "github.com/ory/fosite" "github.com/pocket-id/pocket-id/backend/internal/model" @@ -38,7 +39,7 @@ type federatedClientStore interface { type federatedClientAuthenticator struct { clients federatedClientStore httpClient *http.Client - jwksCache *jwk.Cache + jwksCache *jwkfetch.Cache defaultAudience string } @@ -58,7 +59,7 @@ func newFederatedClientAuthenticator(ctx context.Context, clients federatedClien return authenticator, nil } -func (a *federatedClientAuthenticator) getJWKCache(ctx context.Context) (*jwk.Cache, error) { +func (a *federatedClientAuthenticator) getJWKCache(ctx context.Context) (*jwkfetch.Cache, error) { // We need to create a custom HTTP client to set a timeout. client := a.httpClient if client == nil { @@ -76,11 +77,12 @@ func (a *federatedClientAuthenticator) getJWKCache(ctx context.Context) (*jwk.Ca client.Transport = transport } - return jwk.NewCache(ctx, + return jwkfetch.NewCache(ctx, httprc.NewClient( httprc.WithErrorSink(errsink.NewSlog(slog.Default())), httprc.WithHTTPClient(client), ), + jwkfetch.WithHTTPClient(client), ) } @@ -224,17 +226,12 @@ func (a *federatedClientAuthenticator) fetchJWKSet(ctx context.Context, jwksURL registerCtx, registerCancel := context.WithTimeout(ctx, 15*time.Second) defer registerCancel() - registerOptions := []jwk.RegisterOption{ - jwk.WithMaxInterval(24 * time.Hour), - jwk.WithMinInterval(15 * time.Minute), - jwk.WithWaitReady(true), - } - if a.httpClient != nil { - registerOptions = append(registerOptions, jwk.WithHTTPClient(a.httpClient)) - } - // We need to register the URL - err := a.jwksCache.Register(registerCtx, jwksURL, registerOptions...) + err := a.jwksCache.Register(registerCtx, jwksURL, + jwkfetch.WithMaxInterval(24*time.Hour), + jwkfetch.WithMinInterval(15*time.Minute), + jwkfetch.WithWaitReady(true), + ) // In case of race conditions (two goroutines calling jwkCache.Register at the same time), it's possible we can get a conflict anyways, so we ignore that error if err != nil && !errors.Is(err, httprc.ErrResourceAlreadyExists()) { return nil, fmt.Errorf("failed to register JWK set: %w", err) diff --git a/backend/internal/oidc/federated_client_auth_test.go b/backend/internal/oidc/federated_client_auth_test.go index 3a173ac7..1cd14b9d 100644 --- a/backend/internal/oidc/federated_client_auth_test.go +++ b/backend/internal/oidc/federated_client_auth_test.go @@ -11,9 +11,9 @@ import ( "testing" "time" - "github.com/lestrrat-go/jwx/v3/jwa" - "github.com/lestrrat-go/jwx/v3/jwk" - "github.com/lestrrat-go/jwx/v3/jwt" + "github.com/lestrrat-go/jwx/v4/jwa" + "github.com/lestrrat-go/jwx/v4/jwk" + "github.com/lestrrat-go/jwx/v4/jwt" "github.com/ory/fosite" "github.com/stretchr/testify/require" diff --git a/backend/internal/oidc/introspection_handler_test.go b/backend/internal/oidc/introspection_handler_test.go index 1d3bd620..fd0bcc9a 100644 --- a/backend/internal/oidc/introspection_handler_test.go +++ b/backend/internal/oidc/introspection_handler_test.go @@ -13,9 +13,9 @@ import ( "time" "github.com/gin-gonic/gin" - "github.com/lestrrat-go/jwx/v3/jwa" - "github.com/lestrrat-go/jwx/v3/jwk" - "github.com/lestrrat-go/jwx/v3/jwt" + "github.com/lestrrat-go/jwx/v4/jwa" + "github.com/lestrrat-go/jwx/v4/jwk" + "github.com/lestrrat-go/jwx/v4/jwt" "github.com/ory/fosite" "github.com/stretchr/testify/require" diff --git a/backend/internal/oidc/module.go b/backend/internal/oidc/module.go index 9de3ef57..b9f58a0e 100644 --- a/backend/internal/oidc/module.go +++ b/backend/internal/oidc/module.go @@ -9,7 +9,7 @@ import ( "github.com/gin-gonic/gin" "github.com/italypaleale/francis/host/local" - "github.com/lestrrat-go/jwx/v3/jwa" + "github.com/lestrrat-go/jwx/v4/jwa" "github.com/pocket-id/pocket-id/backend/internal/model" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" "gorm.io/gorm" diff --git a/backend/internal/oidc/preview.go b/backend/internal/oidc/preview.go index 1cd63483..7f4313b3 100644 --- a/backend/internal/oidc/preview.go +++ b/backend/internal/oidc/preview.go @@ -6,7 +6,7 @@ import ( "net/url" "time" - jwxjwt "github.com/lestrrat-go/jwx/v3/jwt" + jwxjwt "github.com/lestrrat-go/jwx/v4/jwt" "github.com/ory/fosite" "github.com/pocket-id/pocket-id/backend/internal/model" diff --git a/backend/internal/oidc/provider_test.go b/backend/internal/oidc/provider_test.go index 942f61ec..e35ae960 100644 --- a/backend/internal/oidc/provider_test.go +++ b/backend/internal/oidc/provider_test.go @@ -16,7 +16,7 @@ import ( "testing" "time" - "github.com/lestrrat-go/jwx/v3/jwa" + "github.com/lestrrat-go/jwx/v4/jwa" "github.com/ory/fosite" fositeoauth2 "github.com/ory/fosite/handler/oauth2" "github.com/pocket-id/pocket-id/backend/internal/model" diff --git a/backend/internal/service/e2etest_service.go b/backend/internal/service/e2etest_service.go index 939daa9a..2009db63 100644 --- a/backend/internal/service/e2etest_service.go +++ b/backend/internal/service/e2etest_service.go @@ -17,9 +17,9 @@ import ( "github.com/go-webauthn/webauthn/protocol" "github.com/italypaleale/francis/actor" "github.com/italypaleale/francis/host/local" - "github.com/lestrrat-go/jwx/v3/jwa" - "github.com/lestrrat-go/jwx/v3/jwk" - "github.com/lestrrat-go/jwx/v3/jwt" + "github.com/lestrrat-go/jwx/v4/jwa" + "github.com/lestrrat-go/jwx/v4/jwk" + "github.com/lestrrat-go/jwx/v4/jwt" "github.com/ory/fosite" "github.com/ory/fosite/compose" fositejwt "github.com/ory/fosite/token/jwt" diff --git a/backend/internal/service/jwt_service.go b/backend/internal/service/jwt_service.go index 39350a50..7bfb45a1 100644 --- a/backend/internal/service/jwt_service.go +++ b/backend/internal/service/jwt_service.go @@ -8,9 +8,9 @@ import ( "time" "uuid" - "github.com/lestrrat-go/jwx/v3/jwa" - "github.com/lestrrat-go/jwx/v3/jwk" - "github.com/lestrrat-go/jwx/v3/jwt" + "github.com/lestrrat-go/jwx/v4/jwa" + "github.com/lestrrat-go/jwx/v4/jwk" + "github.com/lestrrat-go/jwx/v4/jwt" "gorm.io/gorm" "github.com/pocket-id/pocket-id/backend/internal/common" @@ -294,8 +294,7 @@ func (s *JwtService) GetAuthenticationMethod(token jwt.Token) (string, error) { if !token.Has(common.AuthenticationMethodsClaim) { return "", nil } - var rawAuthenticationMethods []any - err := token.Get(common.AuthenticationMethodsClaim, &rawAuthenticationMethods) + rawAuthenticationMethods, err := jwt.Get[[]any](token, common.AuthenticationMethodsClaim) if err != nil { return "", fmt.Errorf("failed to get '%s' claim from token: %w", common.AuthenticationMethodsClaim, err) } @@ -344,8 +343,7 @@ func SetAudienceString(token jwt.Token, audience string) error { // TokenTypeValidator is a validator function that checks the "type" claim in the token func TokenTypeValidator(expectedTokenType string) jwt.ValidatorFunc { return func(_ context.Context, t jwt.Token) error { - var tokenType string - err := t.Get(TokenTypeClaim, &tokenType) + tokenType, err := jwt.Get[string](t, TokenTypeClaim) if err != nil { return fmt.Errorf("failed to get token type claim: %w", err) } @@ -357,7 +355,6 @@ func TokenTypeValidator(expectedTokenType string) jwt.ValidatorFunc { } func (s *JwtService) GetPrivateKey() any { - var privateKey any - _ = jwk.Export(s.privateKey, &privateKey) + privateKey, _ := jwk.Export[any](s.privateKey) return privateKey } diff --git a/backend/internal/service/jwt_service_test.go b/backend/internal/service/jwt_service_test.go index ca69836e..13d5ed19 100644 --- a/backend/internal/service/jwt_service_test.go +++ b/backend/internal/service/jwt_service_test.go @@ -10,9 +10,9 @@ import ( "testing" "time" - "github.com/lestrrat-go/jwx/v3/jwa" - "github.com/lestrrat-go/jwx/v3/jwk" - "github.com/lestrrat-go/jwx/v3/jwt" + "github.com/lestrrat-go/jwx/v4/jwa" + "github.com/lestrrat-go/jwx/v4/jwk" + "github.com/lestrrat-go/jwx/v4/jwt" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gorm.io/gorm" @@ -177,8 +177,7 @@ func TestJwtService_Init(t *testing.T) { _ = assert.True(t, ok) && assert.Equal(t, jwa.EdDSA().String(), alg.String(), "Loaded key has the incorrect algorithm") - var curve jwa.EllipticCurveAlgorithm - err := svc.privateKey.Get("crv", &curve) + curve, err := jwk.Get[jwa.EllipticCurveAlgorithm](svc.privateKey, "crv") _ = assert.NoError(t, err, "Failed to get 'crv' claim") && assert.Equal(t, jwa.Ed25519().String(), curve.String(), "Curve does not match expected value") @@ -333,7 +332,8 @@ func TestGenerateVerifyAccessToken(t *testing.T) { assert.Equal(t, user.ID, subject, "Token subject should match user ID") isAdmin := false if claims.Has(IsAdminClaim) { - require.NoError(t, claims.Get(IsAdminClaim, &isAdmin), "Failed to get isAdmin claim") + isAdmin, err = jwt.Get[bool](claims, IsAdminClaim) + require.NoError(t, err, "Failed to get isAdmin claim") } assert.False(t, isAdmin, "isAdmin should be false") authenticationMethod, err := service.GetAuthenticationMethod(claims) @@ -370,7 +370,8 @@ func TestGenerateVerifyAccessToken(t *testing.T) { isAdmin := false if claims.Has(IsAdminClaim) { - require.NoError(t, claims.Get(IsAdminClaim, &isAdmin), "Failed to get isAdmin claim") + isAdmin, err = jwt.Get[bool](claims, IsAdminClaim) + require.NoError(t, err, "Failed to get isAdmin claim") } assert.True(t, isAdmin, "isAdmin should be true") subject, ok := claims.Subject() @@ -422,7 +423,8 @@ func TestGenerateVerifyAccessToken(t *testing.T) { assert.Equal(t, user.ID, subject, "Token subject should match user ID") isAdmin := false if claims.Has(IsAdminClaim) { - require.NoError(t, claims.Get(IsAdminClaim, &isAdmin), "Failed to get isAdmin claim") + isAdmin, err = jwt.Get[bool](claims, IsAdminClaim) + require.NoError(t, err, "Failed to get isAdmin claim") } assert.True(t, isAdmin, "isAdmin should be true") @@ -460,7 +462,8 @@ func TestGenerateVerifyAccessToken(t *testing.T) { assert.Equal(t, user.ID, subject, "Token subject should match user ID") isAdmin := false if claims.Has(IsAdminClaim) { - require.NoError(t, claims.Get(IsAdminClaim, &isAdmin), "Failed to get isAdmin claim") + isAdmin, err = jwt.Get[bool](claims, IsAdminClaim) + require.NoError(t, err, "Failed to get isAdmin claim") } assert.True(t, isAdmin, "isAdmin should be true") @@ -498,7 +501,8 @@ func TestGenerateVerifyAccessToken(t *testing.T) { assert.Equal(t, user.ID, subject, "Token subject should match user ID") isAdmin := false if claims.Has(IsAdminClaim) { - require.NoError(t, claims.Get(IsAdminClaim, &isAdmin), "Failed to get isAdmin claim") + isAdmin, err = jwt.Get[bool](claims, IsAdminClaim) + require.NoError(t, err, "Failed to get isAdmin claim") } assert.True(t, isAdmin, "isAdmin should be true") diff --git a/backend/internal/utils/jwk/key_provider.go b/backend/internal/utils/jwk/key_provider.go index e47ec116..f53b69f8 100644 --- a/backend/internal/utils/jwk/key_provider.go +++ b/backend/internal/utils/jwk/key_provider.go @@ -4,7 +4,7 @@ import ( "context" "fmt" - "github.com/lestrrat-go/jwx/v3/jwk" + "github.com/lestrrat-go/jwx/v4/jwk" "gorm.io/gorm" "github.com/pocket-id/pocket-id/backend/internal/common" diff --git a/backend/internal/utils/jwk/key_provider_database.go b/backend/internal/utils/jwk/key_provider_database.go index 940b0db2..d73327e7 100644 --- a/backend/internal/utils/jwk/key_provider_database.go +++ b/backend/internal/utils/jwk/key_provider_database.go @@ -7,7 +7,7 @@ import ( "fmt" "time" - "github.com/lestrrat-go/jwx/v3/jwk" + "github.com/lestrrat-go/jwx/v4/jwk" "gorm.io/gorm" "gorm.io/gorm/clause" diff --git a/backend/internal/utils/jwk/key_provider_database_test.go b/backend/internal/utils/jwk/key_provider_database_test.go index fd92844c..8beb9da4 100644 --- a/backend/internal/utils/jwk/key_provider_database_test.go +++ b/backend/internal/utils/jwk/key_provider_database_test.go @@ -7,7 +7,7 @@ import ( "encoding/base64" "testing" - "github.com/lestrrat-go/jwx/v3/jwk" + "github.com/lestrrat-go/jwx/v4/jwk" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -44,7 +44,7 @@ func TestKeyProviderDatabase_LoadKey(t *testing.T) { pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) require.NoError(t, err) - key, err := jwk.Import(pk) + key, err := jwk.Import[jwk.Key](pk) require.NoError(t, err) t.Run("LoadKey with no existing key", func(t *testing.T) { @@ -213,7 +213,7 @@ func TestKeyProviderDatabase_SaveKey(t *testing.T) { pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) require.NoError(t, err) - key, err := jwk.Import(pk) + key, err := jwk.Import[jwk.Key](pk) require.NoError(t, err) t.Run("SaveKey and verify database record", func(t *testing.T) { diff --git a/backend/internal/utils/jwk/public_key.go b/backend/internal/utils/jwk/public_key.go index e4ef6ff8..8613b787 100644 --- a/backend/internal/utils/jwk/public_key.go +++ b/backend/internal/utils/jwk/public_key.go @@ -6,7 +6,7 @@ import ( "errors" "fmt" - "github.com/lestrrat-go/jwx/v3/jwk" + "github.com/lestrrat-go/jwx/v4/jwk" ) var ( diff --git a/backend/internal/utils/jwk/public_key_test.go b/backend/internal/utils/jwk/public_key_test.go index 490c8664..c8304c26 100644 --- a/backend/internal/utils/jwk/public_key_test.go +++ b/backend/internal/utils/jwk/public_key_test.go @@ -4,8 +4,8 @@ import ( "encoding/json" "testing" - "github.com/lestrrat-go/jwx/v3/jwa" - "github.com/lestrrat-go/jwx/v3/jwk" + "github.com/lestrrat-go/jwx/v4/jwa" + "github.com/lestrrat-go/jwx/v4/jwk" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -54,7 +54,7 @@ func TestParsePublicKey(t *testing.T) { }) t.Run("rejects symmetric keys", func(t *testing.T) { - symmetricKey, err := jwk.Import([]byte("this-is-a-shared-secret")) + symmetricKey, err := jwk.Import[jwk.Key]([]byte("this-is-a-shared-secret")) require.NoError(t, err) require.NoError(t, symmetricKey.Set(jwk.KeyIDKey, "symmetric")) encoded, err := json.Marshal(symmetricKey) diff --git a/backend/internal/utils/jwk/utils.go b/backend/internal/utils/jwk/utils.go index 8e75370a..3c0b7266 100644 --- a/backend/internal/utils/jwk/utils.go +++ b/backend/internal/utils/jwk/utils.go @@ -13,8 +13,8 @@ import ( "fmt" "io" - "github.com/lestrrat-go/jwx/v3/jwa" - "github.com/lestrrat-go/jwx/v3/jwk" + "github.com/lestrrat-go/jwx/v4/jwa" + "github.com/lestrrat-go/jwx/v4/jwk" "github.com/pocket-id/pocket-id/backend/internal/common" "github.com/pocket-id/pocket-id/backend/internal/utils/crypto" @@ -58,7 +58,7 @@ func LoadKeyEncryptionKey(envConfig *common.EnvConfigSchema, instanceID string) // ImportRawKey imports a crypto key in "raw" format (e.g. crypto.PrivateKey) into a jwk.Key. // It also populates additional fields such as the key ID, usage, and alg. func ImportRawKey(rawKey any, alg string, crv string) (jwk.Key, error) { - key, err := jwk.Import(rawKey) + key, err := jwk.Import[jwk.Key](rawKey) if err != nil { return nil, fmt.Errorf("failed to import generated private key: %w", err) } diff --git a/backend/internal/utils/jwk/utils_test.go b/backend/internal/utils/jwk/utils_test.go index 4a87875c..cc2ef919 100644 --- a/backend/internal/utils/jwk/utils_test.go +++ b/backend/internal/utils/jwk/utils_test.go @@ -9,8 +9,8 @@ import ( "encoding/hex" "testing" - "github.com/lestrrat-go/jwx/v3/jwa" - "github.com/lestrrat-go/jwx/v3/jwk" + "github.com/lestrrat-go/jwx/v4/jwa" + "github.com/lestrrat-go/jwx/v4/jwk" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -130,8 +130,7 @@ func TestGenerateKey(t *testing.T) { assert.True(t, ok, "key usage should be set") assert.Equal(t, KeyUsageSigning, usage) - var crv any - _ = key.Get("crv", &crv) + crv, _ := jwk.Get[any](key, "crv") // Verify key type matches expected algorithm switch tt.expectedAlg { @@ -162,7 +161,7 @@ func TestEnsureAlgInKey(t *testing.T) { t.Run("does not change alg already set", func(t *testing.T) { // Import the RSA key - key, err := jwk.Import(rsaKey) + key, err := jwk.Import[jwk.Key](rsaKey) require.NoError(t, err) // Pre-set the algorithm @@ -224,7 +223,7 @@ func TestEnsureAlgInKey(t *testing.T) { rawKey, err := tt.keyGen() require.NoError(t, err) - key, err := jwk.Import(rawKey) + key, err := jwk.Import[jwk.Key](rawKey) require.NoError(t, err) // Ensure no algorithm is set initially @@ -241,8 +240,7 @@ func TestEnsureAlgInKey(t *testing.T) { // Verify curve if expected if tt.expectedCrv != "" { - var crv any - _ = key.Get("crv", &crv) + crv, _ := jwk.Get[any](key, "crv") require.NotNil(t, crv) eca, ok := crv.(jwa.EllipticCurveAlgorithm) require.True(t, ok) @@ -291,7 +289,7 @@ func TestEnsureAlgInKey(t *testing.T) { rawKey, err := tt.keyGen() require.NoError(t, err) - key, err := jwk.Import(rawKey) + key, err := jwk.Import[jwk.Key](rawKey) require.NoError(t, err) // Ensure no algorithm is set initially @@ -308,8 +306,7 @@ func TestEnsureAlgInKey(t *testing.T) { // Verify curve if expected if tt.expectedCrv != "" { - var crv any - _ = key.Get("crv", &crv) + crv, _ := jwk.Get[any](key, "crv") require.NotNil(t, crv) eca, ok := crv.(jwa.EllipticCurveAlgorithm) require.True(t, ok) @@ -323,7 +320,7 @@ func TestEnsureAlgInKey(t *testing.T) { rsaKey, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err) - key, err := jwk.Import(rsaKey) + key, err := jwk.Import[jwk.Key](rsaKey) require.NoError(t, err) // Call EnsureAlgInKey with invalid curve @@ -334,8 +331,7 @@ func TestEnsureAlgInKey(t *testing.T) { require.True(t, ok) assert.Equal(t, jwa.RS256().String(), alg.String()) - var crv any - _ = key.Get("crv", &crv) + crv, _ := jwk.Get[any](key, "crv") assert.Nil(t, crv) }) } diff --git a/backend/internal/utils/jwt_util.go b/backend/internal/utils/jwt_util.go index 950801e1..67a4c4a0 100644 --- a/backend/internal/utils/jwt_util.go +++ b/backend/internal/utils/jwt_util.go @@ -3,15 +3,15 @@ package utils import ( "fmt" - "github.com/lestrrat-go/jwx/v3/jwt" + "github.com/lestrrat-go/jwx/v4/jwt" ) func GetClaimsFromToken(token jwt.Token) (map[string]any, error) { keys := token.Keys() claims := make(map[string]any, len(keys)) for _, key := range keys { - var value any - if err := token.Get(key, &value); err != nil { + value, err := jwt.Get[any](token, key) + if err != nil { return nil, fmt.Errorf("failed to get claim %s: %w", key, err) } claims[key] = value diff --git a/backend/internal/webauthn/module.go b/backend/internal/webauthn/module.go index 0d7b6479..56ce94bb 100644 --- a/backend/internal/webauthn/module.go +++ b/backend/internal/webauthn/module.go @@ -8,7 +8,7 @@ import ( "github.com/gin-gonic/gin" "github.com/italypaleale/francis/host/local" - "github.com/lestrrat-go/jwx/v3/jwt" + "github.com/lestrrat-go/jwx/v4/jwt" "gorm.io/gorm" "github.com/pocket-id/pocket-id/backend/internal/appconfig" diff --git a/backend/internal/webauthn/service_test.go b/backend/internal/webauthn/service_test.go index ebdd90f8..7b3322af 100644 --- a/backend/internal/webauthn/service_test.go +++ b/backend/internal/webauthn/service_test.go @@ -8,7 +8,7 @@ import ( "github.com/go-webauthn/webauthn/protocol" gowebauthn "github.com/go-webauthn/webauthn/webauthn" - "github.com/lestrrat-go/jwx/v3/jwt" + "github.com/lestrrat-go/jwx/v4/jwt" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -62,8 +62,8 @@ func (s *fakeSigner) GetAuthenticationMethod(token jwt.Token) (string, error) { if !token.Has(common.AuthenticationMethodsClaim) { return "", nil } - var methods []string - if err := token.Get(common.AuthenticationMethodsClaim, &methods); err != nil { + methods, err := jwt.Get[[]string](token, common.AuthenticationMethodsClaim) + if err != nil { return "", err } if len(methods) == 0 {