/
github.go
124 lines (107 loc) · 3.26 KB
/
github.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
package goauth
import (
"context"
"encoding/json"
"io"
"strconv"
"github.com/mstgnz/goauth/config"
"golang.org/x/oauth2"
"golang.org/x/oauth2/github"
)
// gitHubProvider allows authentication via gitHubProvider OAuth2.
type gitHubProvider struct {
*goauth.OAuth2Config
}
// newGithubProvider creates new gitHubProvider provider instance with some defaults.
func newGithubProvider() goauth.Provider {
return &gitHubProvider{&goauth.OAuth2Config{
Ctx: context.Background(),
DisplayName: "GitHub",
AuthUrl: github.Endpoint.AuthURL,
TokenUrl: github.Endpoint.TokenURL,
UserApiUrl: "https://api.github.com/user",
Scopes: []string{"read:user", "user:email"},
Pkce: true,
}}
}
// FetchUser returns a Credential instance based the gitHubProvider's user api.
// API reference: https://docs.github.com/en/rest/reference/users#get-the-authenticated-user
func (g *gitHubProvider) FetchUser(token *oauth2.Token) (*goauth.Credential, error) {
data, err := g.FetchRawData(token)
if err != nil {
return nil, err
}
rawUser := map[string]any{}
if err = json.Unmarshal(data, &rawUser); err != nil {
return nil, err
}
extracted := struct {
Login string `json:"login"`
Id int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
AvatarUrl string `json:"avatar_url"`
}{}
if err = json.Unmarshal(data, &extracted); err != nil {
return nil, err
}
user := &goauth.Credential{
Id: strconv.Itoa(extracted.Id),
Name: extracted.Name,
Username: extracted.Login,
Email: extracted.Email,
AvatarUrl: extracted.AvatarUrl,
RawUser: rawUser,
AccessToken: token.AccessToken,
RefreshToken: token.RefreshToken,
}
// in case user has set "Keep my email address private", send an
// **optional** API request to retrieve the verified primary email
if user.Email == "" {
email, err := g.fetchPrimaryEmail(token)
if err != nil {
return nil, err
}
user.Email = email
}
return user, nil
}
// fetchPrimaryEmail sends an API request to retrieve the verified
// primary email, in case "Keep my email address private" was set.
// NB! This method can succeed and still return an empty email.
// Error responses that are result of insufficient scopes permissions are ignored.
// API reference: https://docs.github.com/en/rest/users/emails?apiVersion=2022-11-28
func (g *gitHubProvider) fetchPrimaryEmail(token *oauth2.Token) (string, error) {
client := g.Client(token)
response, err := client.Get(g.GetUserApiUrl() + "/emails")
if err != nil {
return "", err
}
defer func(Body io.ReadCloser) {
_ = Body.Close()
}(response.Body)
// ignore common http errors caused by insufficient scope permissions
// (the email field is optional, aka. return the auth user without it)
if response.StatusCode == 401 || response.StatusCode == 403 || response.StatusCode == 404 {
return "", nil
}
content, err := io.ReadAll(response.Body)
if err != nil {
return "", err
}
var emails []struct {
Email string
Verified bool
Primary bool
}
if err = json.Unmarshal(content, &emails); err != nil {
return "", err
}
// extract the verified primary email
for _, email := range emails {
if email.Verified && email.Primary {
return email.Email, nil
}
}
return "", nil
}