@@ -129,36 +129,33 @@ func (c *Config) RefreshToken(ctx context.Context, db database.Store, externalAu
129
129
return externalAuthLink , InvalidTokenError ("token expired, refreshing is either disabled or refreshing failed and will not be retried" )
130
130
}
131
131
132
+ refreshToken := externalAuthLink .OAuthRefreshToken
133
+
132
134
// This is additional defensive programming. Because TokenSource is an interface,
133
135
// we cannot be sure that the implementation will treat an 'IsZero' time
134
136
// as "not-expired". The default implementation does, but a custom implementation
135
137
// might not. Removing the refreshToken will guarantee a refresh will fail.
136
- refreshToken := externalAuthLink .OAuthRefreshToken
137
138
if c .NoRefresh {
138
139
refreshToken = ""
139
140
}
140
141
141
- if externalAuthLink .OauthRefreshFailureReason != "" {
142
- // If the refresh token is invalid, do not try to keep using it. This will
143
- // prevent spamming the IdP with refresh attempts that will fail.
144
- //
145
- // An empty refresh token will cause `TokenSource(...).Token()` to fail
146
- // without sending a request to the IdP if the token is expired.
147
- refreshToken = ""
148
- }
149
-
150
142
existingToken := & oauth2.Token {
151
143
AccessToken : externalAuthLink .OAuthAccessToken ,
152
144
RefreshToken : refreshToken ,
153
145
Expiry : externalAuthLink .OAuthExpiry ,
154
146
}
155
147
148
+ // Note: The TokenSource(...) method will make no remote HTTP requests if the
149
+ // token is expired and no refresh token is set. This is important to prevent
150
+ // spamming the API, consuming rate limits, when the token is known to fail.
156
151
token , err := c .TokenSource (ctx , existingToken ).Token ()
157
152
if err != nil {
158
153
// TokenSource can fail for numerous reasons. If it fails because of
159
154
// a bad refresh token, then the refresh token is invalid, and we should
160
155
// get rid of it. Keeping it around will cause additional refresh
161
156
// attempts that will fail and cost us api rate limits.
157
+ //
158
+ // The error message is saved for debugging purposes.
162
159
if isFailedRefresh (existingToken , err ) {
163
160
reason := err .Error ()
164
161
if len (reason ) > failureReasonLimit {
@@ -169,10 +166,13 @@ func (c *Config) RefreshToken(ctx context.Context, db database.Store, externalAu
169
166
dbExecErr := db .UpdateExternalAuthLinkRefreshToken (ctx , database.UpdateExternalAuthLinkRefreshTokenParams {
170
167
// Adding a reason will prevent further attempts to try and refresh the token.
171
168
OauthRefreshFailureReason : reason ,
172
- OAuthRefreshTokenKeyID : externalAuthLink .OAuthRefreshTokenKeyID .String ,
173
- UpdatedAt : dbtime .Now (),
174
- ProviderID : externalAuthLink .ProviderID ,
175
- UserID : externalAuthLink .UserID ,
169
+ // Remove the invalid refresh token so it is never used again. The cached
170
+ // `reason` can be used to know why this field was zeroed out.
171
+ OAuthRefreshToken : "" ,
172
+ OAuthRefreshTokenKeyID : externalAuthLink .OAuthRefreshTokenKeyID .String ,
173
+ UpdatedAt : dbtime .Now (),
174
+ ProviderID : externalAuthLink .ProviderID ,
175
+ UserID : externalAuthLink .UserID ,
176
176
})
177
177
if dbExecErr != nil {
178
178
// This error should be rare.
@@ -189,10 +189,11 @@ func (c *Config) RefreshToken(ctx context.Context, db database.Store, externalAu
189
189
//
190
190
// This error messages comes from the oauth2 package on our client side.
191
191
// So this check is not against a server generated error message.
192
+ // Error source: https://github.com/golang/oauth2/blob/master/oauth2.go#L277
192
193
if err .Error () == "oauth2: token expired and refresh token is not set" {
193
194
if externalAuthLink .OauthRefreshFailureReason != "" {
194
- // A cached refresh failure error exists. So the refresh token was set, but was invalid.
195
- // Return this cached error for the original refresh attempt. This token will never again be valid.
195
+ // A cached refresh failure error exists. So the refresh token was set, but was invalid, and zeroed out .
196
+ // Return this cached error for the original refresh attempt.
196
197
return externalAuthLink , InvalidTokenError (fmt .Sprintf ("token expired and refreshing failed %s with: %s" ,
197
198
// Do not return the exact time, because then we have to know what timezone the
198
199
// user is in. This approximate time is good enough.
0 commit comments