@@ -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.