Skip to content

Commit 91debbb

Browse files
Merge branch 'master' into feat/get-path-should-append-existing-query-strings
2 parents 90c081d + dea5b8e commit 91debbb

25 files changed

+1614
-103
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -597,11 +597,11 @@ Email subject to use for email changed notification. Defaults to `Your email add
597597

598598
`GOTRUE_MAILER_SUBJECTS_MFA_FACTOR_ENROLLED_NOTIFICATION` - `string`
599599

600-
Email subject to use for MFA factor enrolled notification. Defaults to `MFA factor enrolled`.
600+
Email subject to use for MFA factor enrolled notification. Defaults to `A new MFA factor has been enrolled`.
601601

602602
`GOTRUE_MAILER_SUBJECTS_MFA_FACTOR_UNENROLLED_NOTIFICATION` - `string`
603603

604-
Email subject to use for MFA factor unenrolled notification. Defaults to `MFA factor unenrolled`.
604+
Email subject to use for MFA factor unenrolled notification. Defaults to `An MFA factor has been unenrolled`.
605605

606606
`MAILER_TEMPLATES_INVITE` - `string`
607607

example.env

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ GOTRUE_MAILER_SUBJECTS_EMAIL_CHANGED_NOTIFICATION="Your email address has been c
4040
GOTRUE_MAILER_SUBJECTS_PHONE_CHANGED_NOTIFICATION="Your phone number has been changed"
4141
GOTRUE_MAILER_SUBJECTS_IDENTITY_LINKED_NOTIFICATION="A new identity has been linked"
4242
GOTRUE_MAILER_SUBJECTS_IDENTITY_UNLINKED_NOTIFICATION="An identity has been unlinked"
43-
GOTRUE_MAILER_SUBJECTS_MFA_FACTOR_ENROLLED_NOTIFICATION="MFA factor enrolled"
44-
GOTRUE_MAILER_SUBJECTS_MFA_FACTOR_UNENROLLED_NOTIFICATION="MFA factor unenrolled"
43+
GOTRUE_MAILER_SUBJECTS_MFA_FACTOR_ENROLLED_NOTIFICATION="A new MFA factor has been enrolled"
44+
GOTRUE_MAILER_SUBJECTS_MFA_FACTOR_UNENROLLED_NOTIFICATION="An MFA factor has been unenrolled"
4545
GOTRUE_MAILER_SECURE_EMAIL_CHANGE_ENABLED="true"
4646

4747
# Custom mailer template config

internal/api/api.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,8 @@ func NewAPIWithVersion(globalConfig *conf.GlobalConfiguration, db *storage.Conne
174174
}
175175

176176
r.Get("/health", api.HealthCheck)
177-
r.Get("/.well-known/jwks.json", api.Jwks)
177+
r.Get("/.well-known/jwks.json", api.WellKnownJwks)
178+
r.Get("/.well-known/openid-configuration", api.WellKnownOpenID)
178179

179180
if globalConfig.OAuthServer.Enabled {
180181
r.Get("/.well-known/oauth-authorization-server", api.oauthServer.OAuthServerMetadata)

internal/api/jwks.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ type JwksResponse struct {
1111
Keys []jwk.Key `json:"keys"`
1212
}
1313

14-
func (a *API) Jwks(w http.ResponseWriter, r *http.Request) error {
14+
func (a *API) WellKnownJwks(w http.ResponseWriter, r *http.Request) error {
1515
config := a.config
1616
resp := JwksResponse{
1717
Keys: []jwk.Key{},
@@ -28,3 +28,19 @@ func (a *API) Jwks(w http.ResponseWriter, r *http.Request) error {
2828
w.Header().Set("Cache-Control", "public, max-age=600")
2929
return sendJSON(w, http.StatusOK, resp)
3030
}
31+
32+
type OpenIDConfigurationResponse struct {
33+
Issuer string `json:"issuer"`
34+
JWKSURL string `json:"jwks_uri"`
35+
}
36+
37+
func (a *API) WellKnownOpenID(w http.ResponseWriter, r *http.Request) error {
38+
config := a.config
39+
40+
w.Header().Set("Cache-Control", "public, max-age=600")
41+
42+
return sendJSON(w, http.StatusOK, OpenIDConfigurationResponse{
43+
Issuer: config.JWT.Issuer,
44+
JWKSURL: config.JWT.Issuer + "/.well-known/jwks.json",
45+
})
46+
}

internal/api/oauthserver/handlers.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -437,7 +437,7 @@ func (s *Server) handleAuthorizationCodeGrant(ctx context.Context, w http.Respon
437437

438438
// Issue the refresh token and access token
439439
var terr error
440-
tokenResponse, terr = tokenService.IssueRefreshToken(r, tx, user, authMethod, grantParams)
440+
tokenResponse, terr = tokenService.IssueRefreshToken(r, w.Header(), tx, user, authMethod, grantParams)
441441
if terr != nil {
442442
return terr
443443
}
@@ -488,7 +488,7 @@ func (s *Server) handleRefreshTokenGrant(ctx context.Context, w http.ResponseWri
488488
}
489489

490490
db := s.db.WithContext(ctx)
491-
tokenResponse, err := tokenService.RefreshTokenGrant(ctx, db, r, tokens.RefreshTokenGrantParams{
491+
tokenResponse, err := tokenService.RefreshTokenGrant(ctx, db, r, w.Header(), tokens.RefreshTokenGrantParams{
492492
RefreshToken: params.RefreshToken,
493493
ClientID: clientID,
494494
})

internal/api/token.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ func (a *API) ResourceOwnerPasswordGrant(ctx context.Context, w http.ResponseWri
190190
}); terr != nil {
191191
return terr
192192
}
193-
token, terr = a.tokenService.IssueRefreshToken(r, tx, user, models.PasswordGrant, grantParams)
193+
token, terr = a.tokenService.IssueRefreshToken(r, w.Header(), tx, user, models.PasswordGrant, grantParams)
194194
if terr != nil {
195195
return terr
196196
}
@@ -260,7 +260,7 @@ func (a *API) PKCE(ctx context.Context, w http.ResponseWriter, r *http.Request)
260260
}); terr != nil {
261261
return terr
262262
}
263-
token, terr = a.tokenService.IssueRefreshToken(r, tx, user, authMethod, grantParams)
263+
token, terr = a.tokenService.IssueRefreshToken(r, w.Header(), tx, user, authMethod, grantParams)
264264
if terr != nil {
265265
// error type is already handled in issueRefreshToken
266266
return terr
@@ -295,7 +295,7 @@ func (a *API) generateAccessToken(r *http.Request, tx *storage.Connection, user
295295
}
296296

297297
func (a *API) issueRefreshToken(r *http.Request, conn *storage.Connection, user *models.User, authenticationMethod models.AuthenticationMethod, grantParams models.GrantParams) (*tokens.AccessTokenResponse, error) {
298-
return a.tokenService.IssueRefreshToken(r, conn, user, authenticationMethod, grantParams)
298+
return a.tokenService.IssueRefreshToken(r, make(http.Header), conn, user, authenticationMethod, grantParams)
299299
}
300300

301301
func (a *API) updateMFASessionAndClaims(r *http.Request, tx *storage.Connection, user *models.User, authenticationMethod models.AuthenticationMethod, grantParams models.GrantParams) (*tokens.AccessTokenResponse, error) {

internal/api/token_refresh.go

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ package api
33
import (
44
"context"
55
"net/http"
6+
"regexp"
67

8+
"github.com/supabase/auth/internal/api/apierrors"
9+
"github.com/supabase/auth/internal/crypto"
710
"github.com/supabase/auth/internal/tokens"
811
)
912

@@ -12,15 +15,42 @@ type RefreshTokenGrantParams struct {
1215
RefreshToken string `json:"refresh_token"`
1316
}
1417

18+
var legacyRefreshTokenPattern = regexp.MustCompile("^[a-z0-9]{12}$")
19+
20+
func (p *RefreshTokenGrantParams) Validate() error {
21+
if len(p.RefreshToken) < 12 {
22+
return apierrors.NewBadRequestError(apierrors.ErrorCodeValidationFailed, "Refresh token is not valid")
23+
}
24+
25+
if len(p.RefreshToken) == 12 {
26+
if !legacyRefreshTokenPattern.MatchString(p.RefreshToken) {
27+
return apierrors.NewBadRequestError(apierrors.ErrorCodeValidationFailed, "Refresh token is not valid")
28+
}
29+
30+
return nil
31+
}
32+
33+
_, err := crypto.ParseRefreshToken(p.RefreshToken)
34+
if err != nil {
35+
return apierrors.NewBadRequestError(apierrors.ErrorCodeValidationFailed, "Refresh token is not valid").WithInternalError(err)
36+
}
37+
38+
return nil
39+
}
40+
1541
// RefreshTokenGrant implements the refresh_token grant type flow
1642
func (a *API) RefreshTokenGrant(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
1743
params := &RefreshTokenGrantParams{}
1844
if err := retrieveRequestParams(r, params); err != nil {
1945
return err
2046
}
2147

48+
if err := params.Validate(); err != nil {
49+
return err
50+
}
51+
2252
db := a.db.WithContext(ctx)
23-
tokenResponse, err := a.tokenService.RefreshTokenGrant(ctx, db, r, tokens.RefreshTokenGrantParams{
53+
tokenResponse, err := a.tokenService.RefreshTokenGrant(ctx, db, r, w.Header(), tokens.RefreshTokenGrantParams{
2454
RefreshToken: params.RefreshToken,
2555
})
2656
if err != nil {

internal/api/token_test.go

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"github.com/stretchr/testify/suite"
2020
"github.com/supabase/auth/internal/api/apierrors"
2121
"github.com/supabase/auth/internal/conf"
22+
"github.com/supabase/auth/internal/crypto"
2223
"github.com/supabase/auth/internal/models"
2324
)
2425

@@ -435,9 +436,11 @@ func (ts *TokenTestSuite) TestRefreshTokenReuseRevocation() {
435436

436437
// ensure that the 4 refresh tokens are setup correctly
437438
for i, refreshToken := range refreshTokens {
438-
_, token, _, err := models.FindUserWithRefreshToken(ts.API.db, refreshToken, false)
439+
_, anyToken, _, err := models.FindUserWithRefreshToken(ts.API.db, ts.Config.Security.DBEncryption, refreshToken, false)
439440
require.NoError(ts.T(), err)
440441

442+
token := anyToken.(*models.RefreshToken)
443+
441444
if i == len(refreshTokens)-1 {
442445
require.False(ts.T(), token.Revoked)
443446
} else {
@@ -470,9 +473,10 @@ func (ts *TokenTestSuite) TestRefreshTokenReuseRevocation() {
470473

471474
// ensure that the refresh tokens are marked as revoked in the database
472475
for _, refreshToken := range refreshTokens {
473-
_, token, _, err := models.FindUserWithRefreshToken(ts.API.db, refreshToken, false)
476+
_, anyToken, _, err := models.FindUserWithRefreshToken(ts.API.db, ts.Config.Security.DBEncryption, refreshToken, false)
474477
require.NoError(ts.T(), err)
475478

479+
token := anyToken.(*models.RefreshToken)
476480
require.True(ts.T(), token.Revoked)
477481
}
478482

@@ -887,3 +891,26 @@ $$;`
887891
})
888892
}
889893
}
894+
895+
func TestRefreshTokenGrantParamsValidate(t *testing.T) {
896+
examples := []string{
897+
"",
898+
"01234567890",
899+
"AAAAAAAAAAAA",
900+
"------------",
901+
"0000000000000",
902+
}
903+
904+
p := &RefreshTokenGrantParams{}
905+
906+
for _, example := range examples {
907+
p.RefreshToken = example
908+
require.Error(t, p.Validate())
909+
}
910+
911+
p.RefreshToken = "0123456abcde"
912+
require.NoError(t, p.Validate())
913+
914+
p.RefreshToken = (&crypto.RefreshToken{}).Encode(make([]byte, 32))
915+
require.NoError(t, p.Validate())
916+
}

internal/conf/configuration.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -723,8 +723,10 @@ func (c *DatabaseEncryptionConfiguration) Validate() error {
723723

724724
type SecurityConfiguration struct {
725725
Captcha CaptchaConfiguration `json:"captcha"`
726+
RefreshTokenAlgorithmVersion int `json:"refresh_token_algorithm_version" split_words:"true"`
726727
RefreshTokenRotationEnabled bool `json:"refresh_token_rotation_enabled" split_words:"true" default:"true"`
727728
RefreshTokenReuseInterval int `json:"refresh_token_reuse_interval" split_words:"true"`
729+
RefreshTokenAllowReuse bool `json:"refresh_token_allow_reuse" split_words:"true"`
728730
UpdatePasswordRequireReauthentication bool `json:"update_password_require_reauthentication" split_words:"true"`
729731
ManualLinkingEnabled bool `json:"manual_linking_enabled" split_words:"true" default:"false"`
730732

internal/crypto/crypto.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ func GenerateOtp(digits int) string {
2929

3030
return otp
3131
}
32+
3233
func GenerateTokenHash(emailOrPhone, otp string) string {
3334
return fmt.Sprintf("%x", sha256.Sum224([]byte(emailOrPhone+otp)))
3435
}

0 commit comments

Comments
 (0)