Skip to content

Commit 4614dc5

Browse files
authored
fix: add error codes to refresh token flow (#1824)
## What kind of change does this PR introduce? * Updates the refresh token flow to use error codes * The `oauthError` originally returns a bad request error (400) so we're keeping the status codes the same in this PR. * Once this is merged, we can update the client lib to start picking up on these new error codes and removing the session locally. ## What is the current behavior? Please link any relevant issues here. ## What is the new behavior? Feel free to include screenshots if it includes visual changes. ## Additional context Add any other context or screenshots.
1 parent a7129df commit 4614dc5

File tree

4 files changed

+31
-28
lines changed

4 files changed

+31
-28
lines changed

internal/api/errorcodes.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ const (
1818
ErrorCodeNoAuthorization ErrorCode = "no_authorization"
1919
ErrorCodeUserNotFound ErrorCode = "user_not_found"
2020
ErrorCodeSessionNotFound ErrorCode = "session_not_found"
21+
ErrorCodeSessionExpired ErrorCode = "session_expired"
22+
ErrorCodeRefreshTokenNotFound ErrorCode = "refresh_token_not_found"
23+
ErrorCodeRefreshTokenAlreadyUsed ErrorCode = "refresh_token_already_used"
2124
ErrorCodeFlowStateNotFound ErrorCode = "flow_state_not_found"
2225
ErrorCodeFlowStateExpired ErrorCode = "flow_state_expired"
2326
ErrorCodeSignupDisabled ErrorCode = "signup_disabled"
@@ -71,7 +74,7 @@ const (
7174
ErrorCodeOverRequestRateLimit ErrorCode = "over_request_rate_limit"
7275
ErrorCodeOverEmailSendRateLimit ErrorCode = "over_email_send_rate_limit"
7376
ErrorCodeOverSMSSendRateLimit ErrorCode = "over_sms_send_rate_limit"
74-
ErrorBadCodeVerifier ErrorCode = "bad_code_verifier"
77+
ErrorCodeBadCodeVerifier ErrorCode = "bad_code_verifier"
7578
ErrorCodeAnonymousProviderDisabled ErrorCode = "anonymous_provider_disabled"
7679
ErrorCodeHookTimeout ErrorCode = "hook_timeout"
7780
ErrorCodeHookTimeoutAfterRetry ErrorCode = "hook_timeout_after_retry"

internal/api/token.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ func (a *API) PKCE(ctx context.Context, w http.ResponseWriter, r *http.Request)
269269
return err
270270
}
271271
if err := flowState.VerifyPKCE(params.CodeVerifier); err != nil {
272-
return badRequestError(ErrorBadCodeVerifier, err.Error())
272+
return badRequestError(ErrorCodeBadCodeVerifier, err.Error())
273273
}
274274

275275
var token *AccessTokenResponse

internal/api/token_refresh.go

+6-6
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,13 @@ func (a *API) RefreshTokenGrant(ctx context.Context, w http.ResponseWriter, r *h
4747
user, token, session, err := models.FindUserWithRefreshToken(db, params.RefreshToken, false)
4848
if err != nil {
4949
if models.IsNotFoundError(err) {
50-
return oauthError("invalid_grant", "Invalid Refresh Token: Refresh Token Not Found")
50+
return badRequestError(ErrorCodeRefreshTokenNotFound, "Invalid Refresh Token: Refresh Token Not Found")
5151
}
5252
return internalServerError(err.Error())
5353
}
5454

5555
if user.IsBanned() {
56-
return oauthError("invalid_grant", "Invalid Refresh Token: User Banned")
56+
return badRequestError(ErrorCodeUserBanned, "Invalid Refresh Token: User Banned")
5757
}
5858

5959
if session == nil {
@@ -71,10 +71,10 @@ func (a *API) RefreshTokenGrant(ctx context.Context, w http.ResponseWriter, r *h
7171
// do nothing
7272

7373
case models.SessionTimedOut:
74-
return oauthError("invalid_grant", "Invalid Refresh Token: Session Expired (Inactivity)")
74+
return badRequestError(ErrorCodeSessionExpired, "Invalid Refresh Token: Session Expired (Inactivity)")
7575

7676
default:
77-
return oauthError("invalid_grant", "Invalid Refresh Token: Session Expired")
77+
return badRequestError(ErrorCodeSessionExpired, "Invalid Refresh Token: Session Expired")
7878
}
7979

8080
// Basic checks above passed, now we need to serialize access
@@ -153,7 +153,7 @@ func (a *API) RefreshTokenGrant(ctx context.Context, w http.ResponseWriter, r *h
153153
if s.LastRefreshedAt(nil).After(session.LastRefreshedAt(&token.UpdatedAt)) {
154154
// session is not the most
155155
// recently active one
156-
return oauthError("invalid_grant", "Invalid Refresh Token: Session Expired (Revoked by Newer Login)")
156+
return badRequestError(ErrorCodeSessionExpired, "Invalid Refresh Token: Session Expired (Revoked by Newer Login)")
157157
}
158158
}
159159

@@ -196,7 +196,7 @@ func (a *API) RefreshTokenGrant(ctx context.Context, w http.ResponseWriter, r *h
196196
}
197197
}
198198

199-
return storage.NewCommitWithError(oauthError("invalid_grant", "Invalid Refresh Token: Already Used").WithInternalMessage("Possible abuse attempt: %v", token.ID))
199+
return storage.NewCommitWithError(badRequestError(ErrorCodeRefreshTokenAlreadyUsed, "Invalid Refresh Token: Already Used").WithInternalMessage("Possible abuse attempt: %v", token.ID))
200200
}
201201
}
202202
}

internal/api/token_test.go

+20-20
Original file line numberDiff line numberDiff line change
@@ -89,13 +89,13 @@ func (ts *TokenTestSuite) TestSessionTimebox() {
8989
assert.Equal(ts.T(), http.StatusBadRequest, w.Code)
9090

9191
var firstResult struct {
92-
Error string `json:"error"`
93-
ErrorDescription string `json:"error_description"`
92+
ErrorCode string `json:"error_code"`
93+
Message string `json:"msg"`
9494
}
9595

9696
assert.NoError(ts.T(), json.NewDecoder(w.Result().Body).Decode(&firstResult))
97-
assert.Equal(ts.T(), "invalid_grant", firstResult.Error)
98-
assert.Equal(ts.T(), "Invalid Refresh Token: Session Expired", firstResult.ErrorDescription)
97+
assert.Equal(ts.T(), ErrorCodeSessionExpired, firstResult.ErrorCode)
98+
assert.Equal(ts.T(), "Invalid Refresh Token: Session Expired", firstResult.Message)
9999
}
100100

101101
func (ts *TokenTestSuite) TestSessionInactivityTimeout() {
@@ -124,13 +124,13 @@ func (ts *TokenTestSuite) TestSessionInactivityTimeout() {
124124
assert.Equal(ts.T(), http.StatusBadRequest, w.Code)
125125

126126
var firstResult struct {
127-
Error string `json:"error"`
128-
ErrorDescription string `json:"error_description"`
127+
ErrorCode string `json:"error_code"`
128+
Message string `json:"msg"`
129129
}
130130

131131
assert.NoError(ts.T(), json.NewDecoder(w.Result().Body).Decode(&firstResult))
132-
assert.Equal(ts.T(), "invalid_grant", firstResult.Error)
133-
assert.Equal(ts.T(), "Invalid Refresh Token: Session Expired (Inactivity)", firstResult.ErrorDescription)
132+
assert.Equal(ts.T(), ErrorCodeSessionExpired, firstResult.ErrorCode)
133+
assert.Equal(ts.T(), "Invalid Refresh Token: Session Expired (Inactivity)", firstResult.Message)
134134
}
135135

136136
func (ts *TokenTestSuite) TestFailedToSaveRefreshTokenResultCase() {
@@ -213,13 +213,13 @@ func (ts *TokenTestSuite) TestSingleSessionPerUserNoTags() {
213213
assert.True(ts.T(), ts.API.config.Sessions.SinglePerUser)
214214

215215
var firstResult struct {
216-
Error string `json:"error"`
217-
ErrorDescription string `json:"error_description"`
216+
ErrorCode string `json:"error_code"`
217+
Message string `json:"msg"`
218218
}
219219

220220
assert.NoError(ts.T(), json.NewDecoder(w.Result().Body).Decode(&firstResult))
221-
assert.Equal(ts.T(), "invalid_grant", firstResult.Error)
222-
assert.Equal(ts.T(), "Invalid Refresh Token: Session Expired (Revoked by Newer Login)", firstResult.ErrorDescription)
221+
assert.Equal(ts.T(), ErrorCodeSessionExpired, firstResult.ErrorCode)
222+
assert.Equal(ts.T(), "Invalid Refresh Token: Session Expired (Revoked by Newer Login)", firstResult.Message)
223223
}
224224

225225
func (ts *TokenTestSuite) TestRateLimitTokenRefresh() {
@@ -428,13 +428,13 @@ func (ts *TokenTestSuite) TestRefreshTokenReuseRevocation() {
428428
assert.Equal(ts.T(), http.StatusBadRequest, w.Code)
429429

430430
var response struct {
431-
Error string `json:"error"`
432-
ErrorDescription string `json:"error_description"`
431+
ErrorCode string `json:"error_code"`
432+
Message string `json:"msg"`
433433
}
434434

435435
require.NoError(ts.T(), json.NewDecoder(w.Body).Decode(&response))
436-
require.Equal(ts.T(), response.Error, "invalid_grant")
437-
require.Equal(ts.T(), response.ErrorDescription, "Invalid Refresh Token: Already Used")
436+
require.Equal(ts.T(), ErrorCodeRefreshTokenAlreadyUsed, response.ErrorCode)
437+
require.Equal(ts.T(), "Invalid Refresh Token: Already Used", response.Message)
438438

439439
// ensure that the refresh tokens are marked as revoked in the database
440440
for _, refreshToken := range refreshTokens {
@@ -461,13 +461,13 @@ func (ts *TokenTestSuite) TestRefreshTokenReuseRevocation() {
461461
assert.Equal(ts.T(), http.StatusBadRequest, w.Code, "For refresh token %d", i)
462462

463463
var response struct {
464-
Error string `json:"error"`
465-
ErrorDescription string `json:"error_description"`
464+
ErrorCode string `json:"error_code"`
465+
Message string `json:"msg"`
466466
}
467467

468468
require.NoError(ts.T(), json.NewDecoder(w.Body).Decode(&response))
469-
require.Equal(ts.T(), response.Error, "invalid_grant", "For refresh token %d", i)
470-
require.Equal(ts.T(), response.ErrorDescription, "Invalid Refresh Token: Already Used", "For refresh token %d", i)
469+
require.Equal(ts.T(), ErrorCodeRefreshTokenAlreadyUsed, response.ErrorCode, "For refresh token %d", i)
470+
require.Equal(ts.T(), "Invalid Refresh Token: Already Used", response.Message, "For refresh token %d", i)
471471
}
472472
}
473473

0 commit comments

Comments
 (0)