| 265 | } |
| 266 | |
| 267 | func pollGithubForAuthorization(deviceCode string, intervalSec int64) (string, error) { |
| 268 | params := url.Values{} |
| 269 | params.Set("client_id", githubClientID) |
| 270 | params.Set("device_code", deviceCode) |
| 271 | params.Set("grant_type", "urn:ietf:params:oauth:grant-type:device_code") // fixed by RFC 8628 |
| 272 | client := &http.Client{ |
| 273 | Timeout: defaultTimeout, |
| 274 | } |
| 275 | interval := time.Duration(intervalSec * 1100) // milliseconds, add 10% margin |
| 276 | |
| 277 | for { |
| 278 | resp, err := client.PostForm("https://github.com/login/oauth/access_token", params) |
| 279 | if err != nil { |
| 280 | return "", errors.Wrap(err, "error polling the Github API") |
| 281 | } |
| 282 | if resp.StatusCode != http.StatusOK { |
| 283 | _ = resp.Body.Close() |
| 284 | return "", fmt.Errorf("unexpected response status code %d from Github API", resp.StatusCode) |
| 285 | } |
| 286 | |
| 287 | data, err := io.ReadAll(resp.Body) |
| 288 | if err != nil { |
| 289 | _ = resp.Body.Close() |
| 290 | return "", errors.Wrap(err, "error polling the Github API") |
| 291 | } |
| 292 | _ = resp.Body.Close() |
| 293 | |
| 294 | values, err := url.ParseQuery(string(data)) |
| 295 | if err != nil { |
| 296 | return "", errors.Wrap(err, "error decoding Github API response") |
| 297 | } |
| 298 | |
| 299 | if token := values.Get("access_token"); token != "" { |
| 300 | return token, nil |
| 301 | } |
| 302 | |
| 303 | switch apiError := values.Get("error"); apiError { |
| 304 | case "slow_down": |
| 305 | interval += 5500 // add 5 seconds (RFC 8628), plus some margin |
| 306 | time.Sleep(interval * time.Millisecond) |
| 307 | continue |
| 308 | case "authorization_pending": |
| 309 | time.Sleep(interval * time.Millisecond) |
| 310 | continue |
| 311 | case "": |
| 312 | return "", errors.New("unexpected response from Github API") |
| 313 | default: |
| 314 | // apiError should equal one of: "expired_token", "unsupported_grant_type", |
| 315 | // "incorrect_client_credentials", "incorrect_device_code", or "access_denied" |
| 316 | return "", fmt.Errorf("error creating token: %v, %v", apiError, values.Get("error_description")) |
| 317 | } |
| 318 | } |
| 319 | } |
| 320 | |
| 321 | func promptTokenOptions(repo repository.RepoKeyring, login, owner, project string) (auth.Credential, error) { |
| 322 | creds, err := auth.List(repo, |