aboutsummaryrefslogtreecommitdiff
path: root/auth/github/github.go
diff options
context:
space:
mode:
Diffstat (limited to 'auth/github/github.go')
-rw-r--r--auth/github/github.go127
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 @@
1package github
2
3import (
4 "crypto/rand"
5 "encoding/base64"
6 "encoding/json"
7 "fmt"
8 "io/ioutil"
9 "net/http"
10 "net/url"
11 "strings"
12)
13
14type apiLoginResponse struct {
15 Username string `json:"login"`
16}
17
18type GitHubToken struct {
19 AccessToken string `json:"access_token"`
20 RefreshToken string `json:"refresh_token"`
21}
22
23type 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.
31func (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.
57func (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.
95func (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}