|
7 | 7 | "io" |
8 | 8 | "io/ioutil" |
9 | 9 | "net/http" |
| 10 | + "os" |
10 | 11 | "regexp" |
11 | 12 | "strings" |
12 | 13 |
|
@@ -63,6 +64,46 @@ func ReplaceTripper(tr http.RoundTripper) ClientOption { |
63 | 64 | } |
64 | 65 | } |
65 | 66 |
|
| 67 | +var issuedScopesWarning bool |
| 68 | + |
| 69 | +// CheckScopes checks whether an OAuth scope is present in a response |
| 70 | +func CheckScopes(wantedScope string) ClientOption { |
| 71 | + return func(tr http.RoundTripper) http.RoundTripper { |
| 72 | + return &funcTripper{roundTrip: func(req *http.Request) (*http.Response, error) { |
| 73 | + res, err := tr.RoundTrip(req) |
| 74 | + if err != nil || issuedScopesWarning { |
| 75 | + return res, err |
| 76 | + } |
| 77 | + |
| 78 | + isApp := res.Header.Get("X-Oauth-Client-Id") != "" |
| 79 | + hasScopes := strings.Split(res.Header.Get("X-Oauth-Scopes"), ",") |
| 80 | + |
| 81 | + hasWanted := false |
| 82 | + for _, s := range hasScopes { |
| 83 | + if wantedScope == strings.TrimSpace(s) { |
| 84 | + hasWanted = true |
| 85 | + break |
| 86 | + } |
| 87 | + } |
| 88 | + |
| 89 | + if !hasWanted { |
| 90 | + fmt.Fprintln(os.Stderr, "Warning: gh now requires the `read:org` OAuth scope.") |
| 91 | + // TODO: offer to take the person through the authentication flow again? |
| 92 | + // TODO: retry the original request if it was a read? |
| 93 | + if isApp { |
| 94 | + fmt.Fprintln(os.Stderr, "To re-authenticate, please `rm ~/.config/gh/config.yml` and try again.") |
| 95 | + } else { |
| 96 | + // the person has pasted a Personal Access Token |
| 97 | + fmt.Fprintln(os.Stderr, "Re-generate your token in `rm ~/.config/gh/config.yml` and try again.") |
| 98 | + } |
| 99 | + issuedScopesWarning = true |
| 100 | + } |
| 101 | + |
| 102 | + return res, nil |
| 103 | + }} |
| 104 | + } |
| 105 | +} |
| 106 | + |
66 | 107 | type funcTripper struct { |
67 | 108 | roundTrip func(*http.Request) (*http.Response, error) |
68 | 109 | } |
|
0 commit comments