Thanks to visit codestin.com
Credit goes to github.com

Skip to content

chore: do not refresh tokens that have already failed refreshing #15608

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

Merged
merged 7 commits into from
Nov 21, 2024

Conversation

Emyrk
Copy link
Member

@Emyrk Emyrk commented Nov 20, 2024

For #14982

What this does

Once a token refresh fails, we remove the oauth_refresh_token from the database. This will prevent the token from hitting the IDP for subsequent refresh attempts.

Without this change, a bad script can cause a failing token to hit a remote IDP repeatedly with each git operation. With this change, after the first hit, subsequent hits will fail locally, and never contact the IDP.

The solution in both cases is to authenticate the external auth link. So the resolution is the same as before.

Note

When thinking about this originally, I added a column to cache the refresh error. That way I could persist the original error on subsequent calls. This method requires 2 columns, one for the error, the other for the (access_token, refresh_token) pair.

It adds more complexity, and in the end, the errors are all very generic "Refresh token is invalid" type errors. Which is no more actionable then our error token expired, refreshing is either disabled or refreshing failed and will not be retried.

So this solution requires no db schema changes, and prevents refresh token spam.

Once a token fails a refresh, discard the refresh token, and never
try again.
@Emyrk Emyrk requested a review from johnstcn November 20, 2024 19:53
Comment on lines +1511 to +1528
var oauthErr *oauth2.RetrieveError
if errors.As(err, &oauthErr) {
if oauthErr.Response.StatusCode != 0 {
status = oauthErr.Response.StatusCode
}

rw.Header().Set("Content-Type", "application/x-www-form-urlencoded; charset=utf-8")
form := url.Values{
"error": {oauthErr.ErrorCode},
"error_description": {oauthErr.ErrorDescription},
"error_uri": {oauthErr.ErrorURI},
}
rw.WriteHeader(status)
_, _ = rw.Write([]byte(form.Encode()))
return
}

http.Error(rw, err.Error(), status)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added this to the fake IDP to construct proper oauth errors. This allows me to send a status code 200 with an oauth error. This is how github does it, which feels kinda strange, but wanted to make sure we support that behavior.

//
// Notes: Provider responses are not uniform. Here are some examples:
// Github
// - Returns a 200 with Code "bad_refresh_token" and Description "The refresh token passed is incorrect or expired."
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thisisfine.jpg

Copy link
Member

@johnstcn johnstcn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested out on personal deployment after cherry-picking on top of 2.17.2, appears to work fine for me :shipit:

@Emyrk Emyrk merged commit 78f9f43 into main Nov 21, 2024
27 checks passed
@Emyrk Emyrk deleted the stevenmasley/refresh_token_spam branch November 21, 2024 02:13
coadler pushed a commit that referenced this pull request Dec 3, 2024
…with dbcrypt (#15721)

#15608 introduced a buggy behaviour
with dbcrypt enabled.
When clearing an oauth refresh token, we had been setting the value to
the empty string.
The database encryption package considers decrypting an empty string to
be an error, as an empty encrypted string value will still have a nonce
associated with it and thus not actually be empty when stored at rest.

Instead of 'deleting' the refresh token, 'update' it to be the empty
string.
This plays nicely with dbcrypt.

It also adds a 'utility test' in the dbcrypt package to help encrypt a
value. This was useful when manually fixing users affected by this bug
on our dogfood instance.
stirby pushed a commit that referenced this pull request Dec 3, 2024
…with dbcrypt (#15721)

#15608 introduced a buggy behaviour
with dbcrypt enabled.
When clearing an oauth refresh token, we had been setting the value to
the empty string.
The database encryption package considers decrypting an empty string to
be an error, as an empty encrypted string value will still have a nonce
associated with it and thus not actually be empty when stored at rest.

Instead of 'deleting' the refresh token, 'update' it to be the empty
string.
This plays nicely with dbcrypt.

It also adds a 'utility test' in the dbcrypt package to help encrypt a
value. This was useful when manually fixing users affected by this bug
on our dogfood instance.

(cherry picked from commit e744cde)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants