Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: add error codes to refresh token flow #1824

Merged
merged 2 commits into from
Nov 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion internal/api/errorcodes.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ const (
ErrorCodeNoAuthorization ErrorCode = "no_authorization"
ErrorCodeUserNotFound ErrorCode = "user_not_found"
ErrorCodeSessionNotFound ErrorCode = "session_not_found"
ErrorCodeSessionExpired ErrorCode = "session_expired"
ErrorCodeRefreshTokenNotFound ErrorCode = "refresh_token_not_found"
ErrorCodeRefreshTokenAlreadyUsed ErrorCode = "refresh_token_already_used"
ErrorCodeFlowStateNotFound ErrorCode = "flow_state_not_found"
ErrorCodeFlowStateExpired ErrorCode = "flow_state_expired"
ErrorCodeSignupDisabled ErrorCode = "signup_disabled"
Expand Down Expand Up @@ -71,7 +74,7 @@ const (
ErrorCodeOverRequestRateLimit ErrorCode = "over_request_rate_limit"
ErrorCodeOverEmailSendRateLimit ErrorCode = "over_email_send_rate_limit"
ErrorCodeOverSMSSendRateLimit ErrorCode = "over_sms_send_rate_limit"
ErrorBadCodeVerifier ErrorCode = "bad_code_verifier"
ErrorCodeBadCodeVerifier ErrorCode = "bad_code_verifier"
ErrorCodeAnonymousProviderDisabled ErrorCode = "anonymous_provider_disabled"
ErrorCodeHookTimeout ErrorCode = "hook_timeout"
ErrorCodeHookTimeoutAfterRetry ErrorCode = "hook_timeout_after_retry"
Expand Down
2 changes: 1 addition & 1 deletion internal/api/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ func (a *API) PKCE(ctx context.Context, w http.ResponseWriter, r *http.Request)
return err
}
if err := flowState.VerifyPKCE(params.CodeVerifier); err != nil {
return badRequestError(ErrorBadCodeVerifier, err.Error())
return badRequestError(ErrorCodeBadCodeVerifier, err.Error())
}

var token *AccessTokenResponse
Expand Down
12 changes: 6 additions & 6 deletions internal/api/token_refresh.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,13 @@ func (a *API) RefreshTokenGrant(ctx context.Context, w http.ResponseWriter, r *h
user, token, session, err := models.FindUserWithRefreshToken(db, params.RefreshToken, false)
if err != nil {
if models.IsNotFoundError(err) {
return oauthError("invalid_grant", "Invalid Refresh Token: Refresh Token Not Found")
return badRequestError(ErrorCodeRefreshTokenNotFound, "Invalid Refresh Token: Refresh Token Not Found")
}
return internalServerError(err.Error())
}

if user.IsBanned() {
return oauthError("invalid_grant", "Invalid Refresh Token: User Banned")
return badRequestError(ErrorCodeUserBanned, "Invalid Refresh Token: User Banned")
kangmingtay marked this conversation as resolved.
Show resolved Hide resolved
}

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

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

default:
return oauthError("invalid_grant", "Invalid Refresh Token: Session Expired")
return badRequestError(ErrorCodeSessionExpired, "Invalid Refresh Token: Session Expired")
}

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

Expand Down Expand Up @@ -196,7 +196,7 @@ func (a *API) RefreshTokenGrant(ctx context.Context, w http.ResponseWriter, r *h
}
}

return storage.NewCommitWithError(oauthError("invalid_grant", "Invalid Refresh Token: Already Used").WithInternalMessage("Possible abuse attempt: %v", token.ID))
return storage.NewCommitWithError(badRequestError(ErrorCodeRefreshTokenAlreadyUsed, "Invalid Refresh Token: Already Used").WithInternalMessage("Possible abuse attempt: %v", token.ID))
}
}
}
Expand Down
40 changes: 20 additions & 20 deletions internal/api/token_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,13 @@ func (ts *TokenTestSuite) TestSessionTimebox() {
assert.Equal(ts.T(), http.StatusBadRequest, w.Code)

var firstResult struct {
Error string `json:"error"`
ErrorDescription string `json:"error_description"`
ErrorCode string `json:"error_code"`
Message string `json:"msg"`
}

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

func (ts *TokenTestSuite) TestSessionInactivityTimeout() {
Expand Down Expand Up @@ -124,13 +124,13 @@ func (ts *TokenTestSuite) TestSessionInactivityTimeout() {
assert.Equal(ts.T(), http.StatusBadRequest, w.Code)

var firstResult struct {
Error string `json:"error"`
ErrorDescription string `json:"error_description"`
ErrorCode string `json:"error_code"`
Message string `json:"msg"`
}

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

func (ts *TokenTestSuite) TestFailedToSaveRefreshTokenResultCase() {
Expand Down Expand Up @@ -213,13 +213,13 @@ func (ts *TokenTestSuite) TestSingleSessionPerUserNoTags() {
assert.True(ts.T(), ts.API.config.Sessions.SinglePerUser)

var firstResult struct {
Error string `json:"error"`
ErrorDescription string `json:"error_description"`
ErrorCode string `json:"error_code"`
Message string `json:"msg"`
}

assert.NoError(ts.T(), json.NewDecoder(w.Result().Body).Decode(&firstResult))
assert.Equal(ts.T(), "invalid_grant", firstResult.Error)
assert.Equal(ts.T(), "Invalid Refresh Token: Session Expired (Revoked by Newer Login)", firstResult.ErrorDescription)
assert.Equal(ts.T(), ErrorCodeSessionExpired, firstResult.ErrorCode)
assert.Equal(ts.T(), "Invalid Refresh Token: Session Expired (Revoked by Newer Login)", firstResult.Message)
kangmingtay marked this conversation as resolved.
Show resolved Hide resolved
}

func (ts *TokenTestSuite) TestRateLimitTokenRefresh() {
Expand Down Expand Up @@ -428,13 +428,13 @@ func (ts *TokenTestSuite) TestRefreshTokenReuseRevocation() {
assert.Equal(ts.T(), http.StatusBadRequest, w.Code)

var response struct {
Error string `json:"error"`
ErrorDescription string `json:"error_description"`
ErrorCode string `json:"error_code"`
Message string `json:"msg"`
}

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

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

var response struct {
Error string `json:"error"`
ErrorDescription string `json:"error_description"`
ErrorCode string `json:"error_code"`
Message string `json:"msg"`
}

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

Expand Down
Loading