Skip to content

Commit db3ce12

Browse files
authored
chore: extract token generation logic to its own package (#2135)
## Summary Extracted common token operations from the existing `/token` endpoint to a new `internal/tokens` package to enable code reuse for the upcoming `/oauth/token` endpoint implementation. ## Backward Compatibility - Type aliases: AccessTokenClaims = tokens.AccessTokenClaims in `internal/api/token.go` to keep the changes low - Method preservation: All existing API methods work unchanged - Feature parity: AsRedirectURL(), hooks, validation - everything preserved Note on request object dependency: The service requires the HTTP `request` object for audit log entries (`models.NewAuditLogEntry`). While ideally the service would extract only needed context, the current audit logging implementation requires the full request object.
1 parent 64ae659 commit db3ce12

File tree

8 files changed

+637
-548
lines changed

8 files changed

+637
-548
lines changed

internal/api/api.go

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"github.com/supabase/auth/internal/models"
1919
"github.com/supabase/auth/internal/observability"
2020
"github.com/supabase/auth/internal/storage"
21+
"github.com/supabase/auth/internal/tokens"
2122
"github.com/supabase/auth/internal/utilities"
2223
"github.com/supabase/hibp"
2324
)
@@ -36,9 +37,10 @@ type API struct {
3637
config *conf.GlobalConfiguration
3738
version string
3839

39-
hooksMgr *v0hooks.Manager
40-
hibpClient *hibp.PwnedClient
41-
oauthServer *oauthserver.Server
40+
hooksMgr *v0hooks.Manager
41+
hibpClient *hibp.PwnedClient
42+
oauthServer *oauthserver.Server
43+
tokenService *tokens.Service
4244

4345
// overrideTime can be used to override the clock used by handlers. Should only be used in tests!
4446
overrideTime func() time.Time
@@ -48,6 +50,7 @@ type API struct {
4850

4951
func (a *API) GetConfig() *conf.GlobalConfiguration { return a.config }
5052
func (a *API) GetDB() *storage.Connection { return a.db }
53+
func (a *API) GetTokenService() *tokens.Service { return a.tokenService }
5154

5255
func (a *API) Version() string {
5356
return a.version
@@ -100,6 +103,15 @@ func NewAPIWithVersion(globalConfig *conf.GlobalConfiguration, db *storage.Conne
100103
pgfuncDr := hookspgfunc.New(db)
101104
api.hooksMgr = v0hooks.NewManager(globalConfig, httpDr, pgfuncDr)
102105
}
106+
107+
// Initialize token service if not provided via options
108+
if api.tokenService == nil {
109+
api.tokenService = tokens.NewService(globalConfig, api.hooksMgr)
110+
}
111+
112+
// Connect token service to API's time function (supports test overrides)
113+
api.tokenService.SetTimeFunc(api.Now)
114+
103115
if api.config.Password.HIBP.Enabled {
104116
httpClient := &http.Client{
105117
// all HIBP API requests should finish quickly to avoid

internal/api/external.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"github.com/supabase/auth/internal/models"
2020
"github.com/supabase/auth/internal/observability"
2121
"github.com/supabase/auth/internal/storage"
22+
"github.com/supabase/auth/internal/tokens"
2223
"github.com/supabase/auth/internal/utilities"
2324
"golang.org/x/oauth2"
2425
)
@@ -109,7 +110,7 @@ func (a *API) GetExternalProviderRedirectURL(w http.ResponseWriter, r *http.Requ
109110
claims.LinkingTargetID = linkingTargetUser.ID.String()
110111
}
111112

112-
tokenString, err := signJwt(&config.JWT, claims)
113+
tokenString, err := tokens.SignJWT(&config.JWT, claims)
113114
if err != nil {
114115
return "", apierrors.NewInternalServerError("Error creating state").WithInternalError(err)
115116
}

internal/api/jwks.go

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,8 @@ package api
33
import (
44
"net/http"
55

6-
jwt "github.com/golang-jwt/jwt/v5"
76
"github.com/lestrrat-go/jwx/v2/jwa"
87
jwk "github.com/lestrrat-go/jwx/v2/jwk"
9-
"github.com/supabase/auth/internal/conf"
108
)
119

1210
type JwksResponse struct {
@@ -30,32 +28,3 @@ func (a *API) Jwks(w http.ResponseWriter, r *http.Request) error {
3028
w.Header().Set("Cache-Control", "public, max-age=600")
3129
return sendJSON(w, http.StatusOK, resp)
3230
}
33-
34-
func signJwt(config *conf.JWTConfiguration, claims jwt.Claims) (string, error) {
35-
signingJwk, err := conf.GetSigningJwk(config)
36-
if err != nil {
37-
return "", err
38-
}
39-
signingMethod := conf.GetSigningAlg(signingJwk)
40-
token := jwt.NewWithClaims(signingMethod, claims)
41-
if token.Header == nil {
42-
token.Header = make(map[string]interface{})
43-
}
44-
45-
if _, ok := token.Header["kid"]; !ok {
46-
if kid := signingJwk.KeyID(); kid != "" {
47-
token.Header["kid"] = kid
48-
}
49-
}
50-
// this serializes the aud claim to a string
51-
jwt.MarshalSingleStringAsArray = false
52-
signingKey, err := conf.GetSigningKey(signingJwk)
53-
if err != nil {
54-
return "", err
55-
}
56-
signed, err := token.SignedString(signingKey)
57-
if err != nil {
58-
return "", err
59-
}
60-
return signed, nil
61-
}

internal/api/options.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"github.com/didip/tollbooth/v5/limiter"
88
"github.com/supabase/auth/internal/conf"
99
"github.com/supabase/auth/internal/ratelimit"
10+
"github.com/supabase/auth/internal/tokens"
1011
)
1112

1213
type Option interface {
@@ -36,6 +37,19 @@ type LimiterOptions struct {
3637

3738
func (lo *LimiterOptions) apply(a *API) { a.limiterOpts = lo }
3839

40+
// TokenServiceOption allows injecting a custom token service
41+
type TokenServiceOption struct {
42+
service *tokens.Service
43+
}
44+
45+
func WithTokenService(service *tokens.Service) *TokenServiceOption {
46+
return &TokenServiceOption{service: service}
47+
}
48+
49+
func (tso *TokenServiceOption) apply(a *API) {
50+
a.tokenService = tso.service
51+
}
52+
3953
func NewLimiterOptions(gc *conf.GlobalConfiguration) *LimiterOptions {
4054
o := &LimiterOptions{}
4155

internal/api/ssoadmin_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,22 +55,22 @@ func TestE2EAdmin(t *testing.T) {
5555
}
5656
getTestAttributes := func() map[string]models.SAMLAttribute {
5757
return map[string]models.SAMLAttribute{
58-
"TestE2EAdmin": models.SAMLAttribute{
58+
"TestE2EAdmin": {
5959
Default: true,
6060
},
61-
"customAttr": models.SAMLAttribute{
61+
"customAttr": {
6262
Default: "somevalue",
6363
},
64-
"email": models.SAMLAttribute{
64+
"email": {
6565
Name: "user.email",
6666
},
67-
"first_name": models.SAMLAttribute{
67+
"first_name": {
6868
Name: "user.firstName",
6969
},
70-
"last_name": models.SAMLAttribute{
70+
"last_name": {
7171
Name: "user.lastName",
7272
},
73-
"user_name": models.SAMLAttribute{
73+
"user_name": {
7474
Name: "user.login",
7575
},
7676
}

0 commit comments

Comments
 (0)