FetchTokenScopes retrieves the OAuth scopes for a token by making an HTTP HEAD request to the GitHub API and parsing the X-OAuth-Scopes header. Returns: - []string: List of scopes (empty if no scopes or fine-grained PAT) - error: Any HTTP or parsing error Note: Fine-grained PATs don't return the X
(ctx context.Context, token string)
| 63 | // Note: Fine-grained PATs don't return the X-OAuth-Scopes header, so an empty |
| 64 | // slice is returned for those tokens. |
| 65 | func (f *Fetcher) FetchTokenScopes(ctx context.Context, token string) ([]string, error) { |
| 66 | apiHostURL, err := f.apiHost.BaseRESTURL(ctx) |
| 67 | if err != nil { |
| 68 | return nil, fmt.Errorf("failed to get API host URL: %w", err) |
| 69 | } |
| 70 | |
| 71 | // Use a lightweight endpoint that requires authentication |
| 72 | endpoint, err := url.JoinPath(apiHostURL.String(), "/") |
| 73 | if err != nil { |
| 74 | return nil, fmt.Errorf("failed to construct API URL: %w", err) |
| 75 | } |
| 76 | |
| 77 | req, err := http.NewRequestWithContext(ctx, http.MethodHead, endpoint, nil) |
| 78 | if err != nil { |
| 79 | return nil, fmt.Errorf("failed to create request: %w", err) |
| 80 | } |
| 81 | |
| 82 | req.Header.Set(headers.AuthorizationHeader, "Bearer "+token) |
| 83 | req.Header.Set(headers.AcceptHeader, "application/vnd.github+json") |
| 84 | req.Header.Set(headers.GitHubAPIVersionHeader, "2022-11-28") |
| 85 | |
| 86 | resp, err := f.client.Do(req) |
| 87 | if err != nil { |
| 88 | return nil, fmt.Errorf("failed to fetch scopes: %w", err) |
| 89 | } |
| 90 | defer resp.Body.Close() |
| 91 | |
| 92 | if resp.StatusCode == http.StatusUnauthorized { |
| 93 | return nil, fmt.Errorf("invalid or expired token") |
| 94 | } |
| 95 | |
| 96 | if resp.StatusCode != http.StatusOK { |
| 97 | return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode) |
| 98 | } |
| 99 | |
| 100 | return ParseScopeHeader(resp.Header.Get(OAuthScopesHeader)), nil |
| 101 | } |
| 102 | |
| 103 | // ParseScopeHeader parses the X-OAuth-Scopes header value into a list of scopes. |
| 104 | // The header contains comma-separated scope names. |