diff options
Diffstat (limited to 'auth/github/github.go')
-rw-r--r-- | auth/github/github.go | 127 |
1 files changed, 127 insertions, 0 deletions
diff --git a/auth/github/github.go b/auth/github/github.go new file mode 100644 index 0000000..b403296 --- /dev/null +++ b/auth/github/github.go | |||
@@ -0,0 +1,127 @@ | |||
1 | package github | ||
2 | |||
3 | import ( | ||
4 | "crypto/rand" | ||
5 | "encoding/base64" | ||
6 | "encoding/json" | ||
7 | "fmt" | ||
8 | "io/ioutil" | ||
9 | "net/http" | ||
10 | "net/url" | ||
11 | "strings" | ||
12 | ) | ||
13 | |||
14 | type apiLoginResponse struct { | ||
15 | Username string `json:"login"` | ||
16 | } | ||
17 | |||
18 | type GitHubToken struct { | ||
19 | AccessToken string `json:"access_token"` | ||
20 | RefreshToken string `json:"refresh_token"` | ||
21 | } | ||
22 | |||
23 | type GitHubAuthenticator struct { | ||
24 | ClientId string | ||
25 | ClientSecret string | ||
26 | } | ||
27 | |||
28 | // GetAuthRedirect returns a redirect URL and a state parameter that should be | ||
29 | // stored in the user's browser to correlate the authentication result and | ||
30 | // request. | ||
31 | func (a *GitHubAuthenticator) GetAuthRedirect() (string, string) { | ||
32 | sr := make([]byte, 32) | ||
33 | if _, err := rand.Read(sr); err != nil { | ||
34 | panic(err) // This should only happen if no entropy available | ||
35 | } | ||
36 | random := base64.URLEncoding.EncodeToString(sr) | ||
37 | |||
38 | return (&url.URL{ | ||
39 | Scheme: "https", | ||
40 | Host: "github.com", | ||
41 | Path: "/login/oauth/authorize", | ||
42 | RawQuery: url.Values{ | ||
43 | "client_id": []string{a.ClientId}, | ||
44 | "scope": []string{"read:user"}, | ||
45 | "state": []string{random}, | ||
46 | "allow_signup": []string{"false"}, | ||
47 | }.Encode(), | ||
48 | }).String(), random | ||
49 | } | ||
50 | |||
51 | // GetTokens returns the GitHub Oauth tokens from the service given a token | ||
52 | // request code. The returned tokens are used for authentication to the GitHub | ||
53 | // API. | ||
54 | // | ||
55 | // This relies on a GitHub Oauth application on the GitHub side to act as the | ||
56 | // client for these tokens. | ||
57 | func (a *GitHubAuthenticator) GetTokens(code string) (*GitHubToken, error) { | ||
58 | r, err := (&http.Client{}).Do(&http.Request{ | ||
59 | Method: http.MethodPost, | ||
60 | URL: &url.URL{ | ||
61 | Scheme: "https", | ||
62 | Host: "github.com", | ||
63 | Path: "/login/oauth/access_token", | ||
64 | }, | ||
65 | Header: http.Header{ | ||
66 | "Content-Type": []string{"application/x-www-form-urlencoded"}, | ||
67 | "Accept": []string{"application/json"}, | ||
68 | }, | ||
69 | Body: ioutil.NopCloser(strings.NewReader(url.Values{ | ||
70 | "client_id": []string{a.ClientId}, | ||
71 | "client_secret": []string{a.ClientSecret}, | ||
72 | "code": []string{code}, | ||
73 | }.Encode())), | ||
74 | }) | ||
75 | if err != nil { | ||
76 | return nil, err | ||
77 | } | ||
78 | defer r.Body.Close() | ||
79 | |||
80 | body, err := ioutil.ReadAll(r.Body) | ||
81 | if err != nil { | ||
82 | return nil, err | ||
83 | } | ||
84 | |||
85 | var ret GitHubToken | ||
86 | if err = json.Unmarshal(body, &ret); err != nil { | ||
87 | return nil, err | ||
88 | } | ||
89 | |||
90 | return &ret, nil | ||
91 | } | ||
92 | |||
93 | // GetUsernameWithToken returns the authenticated user's GitHub username given | ||
94 | // an Oauth auth token. | ||
95 | func (a *GitHubAuthenticator) GetUsernameWithToken(accessToken string) (string, error) { | ||
96 | r, err := (&http.Client{}).Do(&http.Request{ | ||
97 | Method: http.MethodGet, | ||
98 | URL: &url.URL{ | ||
99 | Scheme: "https", | ||
100 | Host: "api.github.com", | ||
101 | Path: "/user", | ||
102 | }, | ||
103 | Header: http.Header{ | ||
104 | "Authorization": []string{fmt.Sprintf("token %s", accessToken)}, | ||
105 | }, | ||
106 | }) | ||
107 | if err != nil { | ||
108 | return "", err | ||
109 | } | ||
110 | defer r.Body.Close() | ||
111 | |||
112 | body, err := ioutil.ReadAll(r.Body) | ||
113 | if err != nil { | ||
114 | return "", err | ||
115 | } | ||
116 | |||
117 | var data apiLoginResponse | ||
118 | if err = json.Unmarshal(body, &data); err != nil { | ||
119 | return "", err | ||
120 | } | ||
121 | |||
122 | if data.Username == "" { | ||
123 | return "", fmt.Errorf("No user returned in GitHub response") | ||
124 | } | ||
125 | |||
126 | return data.Username, nil | ||
127 | } | ||