@@ -57,12 +57,18 @@ type Token struct {
57
57
}
58
58
59
59
// tokenJSON is the struct representing the HTTP response from OAuth2
60
- // providers returning a token in JSON form.
60
+ // providers returning a token or error in JSON form.
61
+ // https://datatracker.ietf.org/doc/html/rfc6749#section-5.1
61
62
type tokenJSON struct {
62
63
AccessToken string `json:"access_token"`
63
64
TokenType string `json:"token_type"`
64
65
RefreshToken string `json:"refresh_token"`
65
66
ExpiresIn expirationTime `json:"expires_in"` // at least PayPal returns string, while most return number
67
+ // error fields
68
+ // https://datatracker.ietf.org/doc/html/rfc6749#section-5.2
69
+ Error string `json:"error"`
70
+ ErrorDescription string `json:"error_description"`
71
+ ErrorUri string `json:"error_uri"`
66
72
}
67
73
68
74
func (e * tokenJSON ) expiry () (t time.Time ) {
@@ -238,21 +244,29 @@ func doTokenRoundTrip(ctx context.Context, req *http.Request) (*Token, error) {
238
244
if err != nil {
239
245
return nil , fmt .Errorf ("oauth2: cannot fetch token: %v" , err )
240
246
}
241
- if code := r .StatusCode ; code < 200 || code > 299 {
242
- return nil , & RetrieveError {
243
- Response : r ,
244
- Body : body ,
245
- }
247
+
248
+ failureStatus := r .StatusCode < 200 || r .StatusCode > 299
249
+ retrieveError := & RetrieveError {
250
+ Response : r ,
251
+ Body : body ,
252
+ // attempt to populate error detail populated below
246
253
}
247
254
248
255
var token * Token
249
256
content , _ , _ := mime .ParseMediaType (r .Header .Get ("Content-Type" ))
250
257
switch content {
251
258
case "application/x-www-form-urlencoded" , "text/plain" :
259
+ // some endpoints such as GitHub return a query string https://docs.github.com/en/developers/apps/building-oauth-apps/authorizing-oauth-apps#response-1
252
260
vals , err := url .ParseQuery (string (body ))
253
261
if err != nil {
254
- return nil , err
262
+ if failureStatus {
263
+ return nil , retrieveError
264
+ }
265
+ return nil , fmt .Errorf ("oauth2: cannot parse response: %v" , err )
255
266
}
267
+ retrieveError .ErrorCode = vals .Get ("error" )
268
+ retrieveError .ErrorDescription = vals .Get ("error_description" )
269
+ retrieveError .ErrorCode = vals .Get ("error" )
256
270
token = & Token {
257
271
AccessToken : vals .Get ("access_token" ),
258
272
TokenType : vals .Get ("token_type" ),
@@ -265,10 +279,17 @@ func doTokenRoundTrip(ctx context.Context, req *http.Request) (*Token, error) {
265
279
token .Expiry = time .Now ().Add (time .Duration (expires ) * time .Second )
266
280
}
267
281
default :
282
+ // spec says to return JSON https://datatracker.ietf.org/doc/html/rfc6749#section-5.1
268
283
var tj tokenJSON
269
284
if err = json .Unmarshal (body , & tj ); err != nil {
270
- return nil , err
285
+ if failureStatus {
286
+ return nil , retrieveError
287
+ }
288
+ return nil , fmt .Errorf ("oauth2: cannot parse json: %v" , err )
271
289
}
290
+ retrieveError .ErrorCode = tj .Error
291
+ retrieveError .ErrorDescription = tj .ErrorDescription
292
+ retrieveError .ErrorUri = tj .ErrorUri
272
293
token = & Token {
273
294
AccessToken : tj .AccessToken ,
274
295
TokenType : tj .TokenType ,
@@ -278,17 +299,27 @@ func doTokenRoundTrip(ctx context.Context, req *http.Request) (*Token, error) {
278
299
}
279
300
json .Unmarshal (body , & token .Raw ) // no error checks for optional fields
280
301
}
302
+ if failureStatus || retrieveError .ErrorCode != "" {
303
+ return nil , retrieveError
304
+ }
281
305
if token .AccessToken == "" {
282
306
return nil , errors .New ("oauth2: server response missing access_token" )
283
307
}
284
308
return token , nil
285
309
}
286
310
311
+ // mirrors oauth2.RetrieveError
287
312
type RetrieveError struct {
288
- Response * http.Response
289
- Body []byte
313
+ Response * http.Response
314
+ Body []byte
315
+ ErrorCode string
316
+ ErrorDescription string
317
+ ErrorUri string
290
318
}
291
319
292
320
func (r * RetrieveError ) Error () string {
321
+ if r .ErrorCode != "" {
322
+ return fmt .Sprintf ("oauth2: cannot fetch token: %s %s %s" , r .ErrorCode , r .ErrorDescription , r .ErrorUri )
323
+ }
293
324
return fmt .Sprintf ("oauth2: cannot fetch token: %v\n Response: %s" , r .Response .Status , r .Body )
294
325
}
0 commit comments