Skip to content

Commit 0184ec2

Browse files
committed
wip
1 parent aa0ac5b commit 0184ec2

File tree

5 files changed

+67
-43
lines changed

5 files changed

+67
-43
lines changed

internal/models/refresh_token.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,15 @@ import (
1414
"github.com/supabase/auth/internal/utilities"
1515
)
1616

17+
type TODO struct {
18+
SessionID string `json:"s"`
19+
ID int64 `json:"t"`
20+
}
21+
22+
func (TODO) TableName() string {
23+
panic("not a DB model")
24+
}
25+
1726
// RefreshToken is the database model for refresh tokens.
1827
type RefreshToken struct {
1928
ID int64 `db:"id"`

internal/models/sessions.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@ type Session struct {
9191

9292
Tag *string `json:"tag" db:"tag"`
9393
OAuthClientID *uuid.UUID `json:"oauth_client_id" db:"oauth_client_id"`
94+
95+
RefreshTokenHmacKey *string `json:"-" db:"refresh_token_hmac_key"`
96+
RefreshTokenID *int64 `json:"-" db:"refresh_token_id"`
9497
}
9598

9699
func (Session) TableName() string {

internal/models/user.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -634,7 +634,9 @@ func FindUserByID(tx *storage.Connection, id uuid.UUID) (*User, error) {
634634
// the form SELECT ... FOR UPDATE SKIP LOCKED. This means that a FOR UPDATE
635635
// lock will only be acquired if there's no other lock. In case there is a
636636
// lock, a IsNotFound(err) error will be returned.
637-
func FindUserWithRefreshToken(tx *storage.Connection, token string, forUpdate bool) (*User, *RefreshToken, *Session, error) {
637+
//
638+
// Second value returned is either *models.RefreshToken or *models.TODO.
639+
func FindUserWithRefreshToken(tx *storage.Connection, token string, forUpdate bool) (*User, any, *Session, error) {
638640
refreshToken := &RefreshToken{}
639641

640642
if forUpdate {

internal/tokens/service.go

Lines changed: 46 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ func (s *Service) RefreshTokenGrant(ctx context.Context, db *storage.Connection,
130130
for retry && time.Since(retryStart).Seconds() < retryLoopDuration {
131131
retry = false
132132

133-
user, token, session, err := models.FindUserWithRefreshToken(db, params.RefreshToken, false)
133+
user, anyToken, session, err := models.FindUserWithRefreshToken(db, params.RefreshToken, false)
134134
if err != nil {
135135
if models.IsNotFoundError(err) {
136136
return nil, apierrors.NewBadRequestError(apierrors.ErrorCodeRefreshTokenNotFound, "Invalid Refresh Token: Refresh Token Not Found")
@@ -143,9 +143,11 @@ func (s *Service) RefreshTokenGrant(ctx context.Context, db *storage.Connection,
143143
}
144144

145145
if session == nil {
146-
// a refresh token won't have a session if it's created prior to the sessions table introduced
147-
if err := db.Destroy(token); err != nil {
148-
return nil, apierrors.NewInternalServerError("Error deleting refresh token with missing session").WithInternalError(err)
146+
if token, ok := anyToken.(*models.RefreshToken); ok {
147+
// a refresh token won't have a session if it's created prior to the sessions table introduced
148+
if err := db.Destroy(token); err != nil {
149+
return nil, apierrors.NewInternalServerError("Error deleting refresh token with missing session").WithInternalError(err)
150+
}
149151
}
150152
return nil, apierrors.NewBadRequestError(apierrors.ErrorCodeSessionNotFound, "Invalid Refresh Token: No Valid Session Found")
151153
}
@@ -281,53 +283,55 @@ func (s *Service) RefreshTokenGrant(ctx context.Context, db *storage.Connection,
281283

282284
var issuedToken *models.RefreshToken
283285

284-
if token.Revoked {
285-
activeRefreshToken, terr := session.FindCurrentlyActiveRefreshToken(tx)
286-
if terr != nil && !models.IsNotFoundError(terr) {
287-
return apierrors.NewInternalServerError(terr.Error())
288-
}
286+
if token, ok := anyToken.(*models.RefreshToken); ok {
287+
if token.Revoked {
288+
activeRefreshToken, terr := session.FindCurrentlyActiveRefreshToken(tx)
289+
if terr != nil && !models.IsNotFoundError(terr) {
290+
return apierrors.NewInternalServerError(terr.Error())
291+
}
289292

290-
if activeRefreshToken != nil && activeRefreshToken.Parent.String() == token.Token {
291-
// Token was revoked, but it's the
292-
// parent of the currently active one.
293-
// This indicates that the client was
294-
// not able to store the result when it
295-
// refreshed token. This case is
296-
// allowed, provided we return back the
297-
// active refresh token instead of
298-
// creating a new one.
299-
issuedToken = activeRefreshToken
300-
} else {
301-
// For a revoked refresh token to be reused, it
302-
// has to fall within the reuse interval.
303-
reuseUntil := token.UpdatedAt.Add(
304-
time.Second * time.Duration(config.Security.RefreshTokenReuseInterval))
305-
306-
if s.now().After(reuseUntil) {
307-
// not OK to reuse this token
308-
if config.Security.RefreshTokenRotationEnabled {
309-
// Revoke all tokens in token family
310-
if err := models.RevokeTokenFamily(tx, token); err != nil {
311-
return apierrors.NewInternalServerError(err.Error())
293+
if activeRefreshToken != nil && activeRefreshToken.Parent.String() == token.Token {
294+
// Token was revoked, but it's the
295+
// parent of the currently active one.
296+
// This indicates that the client was
297+
// not able to store the result when it
298+
// refreshed token. This case is
299+
// allowed, provided we return back the
300+
// active refresh token instead of
301+
// creating a new one.
302+
issuedToken = activeRefreshToken
303+
} else {
304+
// For a revoked refresh token to be reused, it
305+
// has to fall within the reuse interval.
306+
reuseUntil := token.UpdatedAt.Add(
307+
time.Second * time.Duration(config.Security.RefreshTokenReuseInterval))
308+
309+
if s.now().After(reuseUntil) {
310+
// not OK to reuse this token
311+
if config.Security.RefreshTokenRotationEnabled {
312+
// Revoke all tokens in token family
313+
if err := models.RevokeTokenFamily(tx, token); err != nil {
314+
return apierrors.NewInternalServerError(err.Error())
315+
}
312316
}
313-
}
314317

315-
return storage.NewCommitWithError(apierrors.NewBadRequestError(apierrors.ErrorCodeRefreshTokenAlreadyUsed, "Invalid Refresh Token: Already Used").WithInternalMessage("Possible abuse attempt: %v", token.ID))
318+
return storage.NewCommitWithError(apierrors.NewBadRequestError(apierrors.ErrorCodeRefreshTokenAlreadyUsed, "Invalid Refresh Token: Already Used").WithInternalMessage("Possible abuse attempt: %v", token.ID))
319+
}
316320
}
317321
}
318-
}
319-
320-
if terr := models.NewAuditLogEntry(config.AuditLog, r, tx, user, models.TokenRefreshedAction, "", nil); terr != nil {
321-
return terr
322-
}
323322

324-
if issuedToken == nil {
325-
newToken, terr := models.GrantRefreshTokenSwap(config.AuditLog, r, tx, user, token)
326-
if terr != nil {
323+
if terr := models.NewAuditLogEntry(config.AuditLog, r, tx, user, models.TokenRefreshedAction, "", nil); terr != nil {
327324
return terr
328325
}
329326

330-
issuedToken = newToken
327+
if issuedToken == nil {
328+
newToken, terr := models.GrantRefreshTokenSwap(config.AuditLog, r, tx, user, token)
329+
if terr != nil {
330+
return terr
331+
}
332+
333+
issuedToken = newToken
334+
}
331335
}
332336

333337
tokenString, expiresAt, terr = s.GenerateAccessToken(r, tx, GenerateAccessTokenParams{
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
ALTER TABLE {{ index .Options "Namespace" }}.sessions
2+
ADD COLUMN IF NOT EXISTS refresh_token_hmac_key text,
3+
ADD COLUMN IF NOT EXISTS refresh_token_id bigint;
4+
5+
COMMENT ON COLUMN {{ index .Options "Namespace" }}.refresh_token_hmac_key IS 'Holds a HMAC-SHA256 key used to sign refresh tokens for this session.';
6+
COMMENT ON COLUMN {{ index .Options "Namespace" }}.refresh_token_id IS 'Holds the ID of the last issued refresh token.';

0 commit comments

Comments
 (0)