MCPcopy Index your code
hub / github.com/coder/coder / RefreshToken

Method RefreshToken

coderd/externalauth/externalauth.go:191–388  ·  view source on GitHub ↗

RefreshToken automatically refreshes the token if expired and permitted.

(ctx context.Context, db database.Store, externalAuthLink database.ExternalAuthLink)

Source from the content-addressed store, hash-verified

189
190// RefreshToken automatically refreshes the token if expired and permitted.
191func (c *Config) RefreshToken(ctx context.Context, db database.Store, externalAuthLink database.ExternalAuthLink) (database.ExternalAuthLink, error) {
192 // If the token is expired and refresh is disabled, we prompt
193 // the user to authenticate again.
194 if c.NoRefresh &&
195 // If the time is set to 0, then it should never expire.
196 // This is true for github, which has no expiry.
197 !externalAuthLink.OAuthExpiry.IsZero() &&
198 externalAuthLink.OAuthExpiry.Before(dbtime.Now()) {
199 return externalAuthLink, InvalidTokenError("token expired, refreshing is either disabled or refreshing failed and will not be retried")
200 }
201
202 refreshToken := externalAuthLink.OAuthRefreshToken
203
204 // This is additional defensive programming. Because TokenSource is an interface,
205 // we cannot be sure that the implementation will treat an 'IsZero' time
206 // as "not-expired". The default implementation does, but a custom implementation
207 // might not. Removing the refreshToken will guarantee a refresh will fail.
208 if c.NoRefresh {
209 refreshToken = ""
210 }
211
212 existingToken := &oauth2.Token{
213 AccessToken: externalAuthLink.OAuthAccessToken,
214 RefreshToken: refreshToken,
215 Expiry: externalAuthLink.OAuthExpiry,
216 }
217
218 // Note: The TokenSource(...) method will make no remote HTTP requests if the
219 // token is expired and no refresh token is set. This is important to prevent
220 // spamming the API, consuming rate limits, when the token is known to fail.
221 //
222 // External providers (GitHub in particular) intermittently fail token
223 // refreshes with transient errors such as 5xx responses, network timeouts,
224 // and rate-limited 429s. Retry with exponential backoff before surfacing
225 // the failure so a brief upstream blip does not force users to
226 // re-authenticate. Errors classified as permanent by isFailedRefresh
227 // (e.g. revoked or rotated refresh tokens) are not retried since those
228 // will never succeed and retrying wastes the refresh quota.
229 token, err := c.refreshTokenWithRetry(ctx, existingToken)
230 if err != nil {
231 // TokenSource can fail for numerous reasons. If it fails because of
232 // a bad refresh token, then the refresh token is invalid, and we should
233 // get rid of it. Keeping it around will cause additional refresh
234 // attempts that will fail and cost us api rate limits.
235 //
236 // The error message is saved for debugging purposes.
237 if isFailedRefresh(existingToken, err) {
238 // Before caching the failure, re-read the external auth link
239 // from the database. A concurrent request may have already
240 // refreshed the token successfully, consuming the single-use
241 // refresh token (e.g., GitHub App tokens). In that case our
242 // "bad_refresh_token" error is a false positive from losing
243 // the race, and we should use the winner's updated token
244 // instead of poisoning the database with a cached failure.
245 currentLink, readErr := db.GetExternalAuthLink(ctx, database.GetExternalAuthLinkParams{
246 ProviderID: externalAuthLink.ProviderID,
247 UserID: externalAuthLink.UserID,
248 })

Callers 4

TestRefreshTokenFunction · 0.95
acquireProtoJobMethod · 0.95

Calls 15

refreshTokenWithRetryMethod · 0.95
GenerateTokenExtraMethod · 0.95
ValidateTokenMethod · 0.95
AuthCodeURLMethod · 0.95
NowFunction · 0.92
InvalidTokenErrorTypeAlias · 0.85
isFailedRefreshFunction · 0.85
IsGithubDotComURLFunction · 0.85
GetExternalAuthLinkMethod · 0.65
NewMethod · 0.65

Tested by 1

TestRefreshTokenFunction · 0.76