@@ -16,6 +16,7 @@ import (
16
16
"net/url"
17
17
"strings"
18
18
"sync"
19
+ "time"
19
20
20
21
"golang.org/x/oauth2/internal"
21
22
)
@@ -70,8 +71,9 @@ type TokenSource interface {
70
71
// Endpoint represents an OAuth 2.0 provider's authorization and token
71
72
// endpoint URLs.
72
73
type Endpoint struct {
73
- AuthURL string
74
- TokenURL string
74
+ AuthURL string
75
+ DeviceAuthURL string
76
+ TokenURL string
75
77
76
78
// AuthStyle optionally specifies how the endpoint wants the
77
79
// client ID & client secret sent. The zero value means to
@@ -224,6 +226,62 @@ func (c *Config) Exchange(ctx context.Context, code string, opts ...AuthCodeOpti
224
226
return retrieveToken (ctx , c , v )
225
227
}
226
228
229
+ // AuthDevice returns a device auth struct which contains a device code
230
+ // and authorization information provided for users to enter on another device.
231
+ func (c * Config ) AuthDevice (ctx context.Context , opts ... AuthCodeOption ) (* DeviceAuth , error ) {
232
+ v := url.Values {
233
+ "client_id" : {c .ClientID },
234
+ }
235
+ if len (c .Scopes ) > 0 {
236
+ v .Set ("scope" , strings .Join (c .Scopes , " " ))
237
+ }
238
+ for _ , opt := range opts {
239
+ opt .setValue (v )
240
+ }
241
+ return retrieveDeviceAuth (ctx , c , v )
242
+ }
243
+
244
+ // Poll does a polling to exchange an device code for a token.
245
+ func (c * Config ) Poll (ctx context.Context , da * DeviceAuth , opts ... AuthCodeOption ) (* Token , error ) {
246
+ v := url.Values {
247
+ "client_id" : {c .ClientID },
248
+ "grant_type" : {"urn:ietf:params:oauth:grant-type:device_code" },
249
+ "device_code" : {da .DeviceCode },
250
+ }
251
+ if len (c .Scopes ) > 0 {
252
+ v .Set ("scope" , strings .Join (c .Scopes , " " ))
253
+ }
254
+ for _ , opt := range opts {
255
+ opt .setValue (v )
256
+ }
257
+
258
+ // If no interval was provided, the client MUST use a reasonable default polling interval.
259
+ // See https://tools.ietf.org/html/draft-ietf-oauth-device-flow-07#section-3.5
260
+ interval := da .Interval
261
+ if interval == 0 {
262
+ interval = 5
263
+ }
264
+
265
+ for {
266
+ time .Sleep (time .Duration (interval ) * time .Second )
267
+
268
+ tok , err := retrieveToken (ctx , c , v )
269
+ if err == nil {
270
+ return tok , nil
271
+ }
272
+
273
+ errTyp := parseError (err )
274
+ switch errTyp {
275
+ case errAccessDenied , errExpiredToken :
276
+ return tok , errors .New ("oauth2: " + errTyp )
277
+ case errSlowDown :
278
+ interval += 1
279
+ fallthrough
280
+ case errAuthorizationPending :
281
+ }
282
+ }
283
+ }
284
+
227
285
// Client returns an HTTP client using the provided token.
228
286
// The token will auto-refresh as necessary. The underlying
229
287
// HTTP transport will be obtained using the provided context.
@@ -271,7 +329,6 @@ func (tf *tokenRefresher) Token() (*Token, error) {
271
329
"grant_type" : {"refresh_token" },
272
330
"refresh_token" : {tf .refreshToken },
273
331
})
274
-
275
332
if err != nil {
276
333
return nil , err
277
334
}
0 commit comments