• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

go-pkgz / auth / 7255079117

18 Dec 2023 11:27PM UTC coverage: 82.941%. Remained the same
7255079117

Pull #189

github

web-flow
Bump golang.org/x/crypto from 0.14.0 to 0.17.0 in /_example

Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.14.0 to 0.17.0.
- [Commits](https://github.com/golang/crypto/compare/v0.14.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #189: Bump golang.org/x/crypto from 0.14.0 to 0.17.0 in /_example

2572 of 3101 relevant lines covered (82.94%)

6.93 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

75.15
/provider/oauth2.go
1
package provider
2

3
import (
4
        "context"
5
        "encoding/json"
6
        "fmt"
7
        "io"
8
        "net/http"
9
        "strings"
10
        "time"
11

12
        "github.com/go-pkgz/rest"
13
        "github.com/golang-jwt/jwt"
14
        "golang.org/x/oauth2"
15

16
        "github.com/go-pkgz/auth/logger"
17
        "github.com/go-pkgz/auth/token"
18
)
19

20
// Oauth2Handler implements /login, /callback and /logout handlers from aouth2 flow
21
type Oauth2Handler struct {
22
        Params
23

24
        // all of these fields specific to particular oauth2 provider
25
        name            string
26
        infoURL         string
27
        endpoint        oauth2.Endpoint
28
        scopes          []string
29
        mapUser         func(UserData, []byte) token.User // map info from InfoURL to User
30
        bearerTokenHook BearerTokenHook                   // a way to get a Bearer token received from oauth2-provider
31
        conf            oauth2.Config
32
}
33

34
// Params to make initialized and ready to use provider
35
type Params struct {
36
        logger.L
37
        URL            string
38
        JwtService     TokenService
39
        Cid            string
40
        Csecret        string
41
        Issuer         string
42
        AvatarSaver    AvatarSaver
43
        UserAttributes UserAttributes
44

45
        Port int    // relevant for providers supporting port customization, for example dev oauth2
46
        Host string // relevant for providers supporting host customization, for example dev oauth2
47
}
48

49
// UserData is type for user information returned from oauth2 providers /info API method
50
type UserData map[string]interface{}
51

52
// Value returns value for key or empty string if not found
53
func (u UserData) Value(key string) string {
91✔
54
        // json.Unmarshal converts json "null" value to go's "nil", in this case return empty string
91✔
55
        if val, ok := u[key]; ok && val != nil {
174✔
56
                return fmt.Sprintf("%v", val)
83✔
57
        }
83✔
58
        return ""
8✔
59
}
60

61
// BearerTokenHook accepts provider name, user and token, received during oauth2 authentication
62
type BearerTokenHook func(provider string, user token.User, token oauth2.Token)
63

64
// initOauth2Handler makes oauth2 handler for given provider
65
func initOauth2Handler(p Params, service Oauth2Handler) Oauth2Handler {
24✔
66
        if p.L == nil {
39✔
67
                p.L = logger.NoOp
15✔
68
        }
15✔
69
        p.Logf("[INFO] init oauth2 service %s", service.name)
24✔
70
        service.Params = p
24✔
71
        service.conf = oauth2.Config{
24✔
72
                ClientID:     service.Cid,
24✔
73
                ClientSecret: service.Csecret,
24✔
74
                Scopes:       service.scopes,
24✔
75
                Endpoint:     service.endpoint,
24✔
76
        }
24✔
77

24✔
78
        p.Logf("[DEBUG] created %s oauth2, id=%s, redir=%s, endpoint=%s",
24✔
79
                service.name, service.Cid, service.makeRedirURL("/{route}/"+service.name+"/"), service.endpoint)
24✔
80
        return service
24✔
81
}
82

83
// Name returns provider name
84
func (p Oauth2Handler) Name() string { return p.name }
22✔
85

86
// LoginHandler - GET /login?from=redirect-back-url&[site|aud]=siteID&session=1&noava=1
87
func (p Oauth2Handler) LoginHandler(w http.ResponseWriter, r *http.Request) {
10✔
88

10✔
89
        p.Logf("[DEBUG] login with %s", p.Name())
10✔
90
        // make state (random) and store in session
10✔
91
        state, err := randToken()
10✔
92
        if err != nil {
10✔
93
                rest.SendErrorJSON(w, r, p.L, http.StatusInternalServerError, err, "failed to make oauth2 state")
×
94
                return
×
95
        }
×
96

97
        cid, err := randToken()
10✔
98
        if err != nil {
10✔
99
                rest.SendErrorJSON(w, r, p.L, http.StatusInternalServerError, err, "failed to make claim's id")
×
100
                return
×
101
        }
×
102

103
        aud := r.URL.Query().Get("site") // legacy, for back compat
10✔
104
        if aud == "" {
11✔
105
                aud = r.URL.Query().Get("aud")
1✔
106
        }
1✔
107

108
        claims := token.Claims{
10✔
109
                Handshake: &token.Handshake{
10✔
110
                        State: state,
10✔
111
                        From:  r.URL.Query().Get("from"),
10✔
112
                },
10✔
113
                SessionOnly: r.URL.Query().Get("session") != "" && r.URL.Query().Get("session") != "0",
10✔
114
                StandardClaims: jwt.StandardClaims{
10✔
115
                        Id:        cid,
10✔
116
                        Audience:  aud,
10✔
117
                        ExpiresAt: time.Now().Add(30 * time.Minute).Unix(),
10✔
118
                        NotBefore: time.Now().Add(-1 * time.Minute).Unix(),
10✔
119
                },
10✔
120
                NoAva: r.URL.Query().Get("noava") == "1",
10✔
121
        }
10✔
122

10✔
123
        if _, err := p.JwtService.Set(w, claims); err != nil {
10✔
124
                rest.SendErrorJSON(w, r, p.L, http.StatusInternalServerError, err, "failed to set token")
×
125
                return
×
126
        }
×
127

128
        // setting RedirectURL to rootURL/routingPath/provider/callback
129
        // e.g. http://localhost:8080/auth/github/callback
130
        p.conf.RedirectURL = p.makeRedirURL(r.URL.Path)
10✔
131

10✔
132
        // return login url
10✔
133
        loginURL := p.conf.AuthCodeURL(state)
10✔
134
        p.Logf("[DEBUG] login url %s, claims=%+v", loginURL, claims)
10✔
135

10✔
136
        http.Redirect(w, r, loginURL, http.StatusFound)
10✔
137
}
138

139
// AuthHandler fills user info and redirects to "from" url. This is callback url redirected locally by browser
140
// GET /callback
141
func (p Oauth2Handler) AuthHandler(w http.ResponseWriter, r *http.Request) {
9✔
142
        oauthClaims, _, err := p.JwtService.Get(r)
9✔
143
        if err != nil {
10✔
144
                rest.SendErrorJSON(w, r, p.L, http.StatusInternalServerError, err, "failed to get token")
1✔
145
                return
1✔
146
        }
1✔
147

148
        if oauthClaims.Handshake == nil {
8✔
149
                rest.SendErrorJSON(w, r, p.L, http.StatusForbidden, nil, "invalid handshake token")
×
150
                return
×
151
        }
×
152

153
        retrievedState := oauthClaims.Handshake.State
8✔
154
        if retrievedState == "" || retrievedState != r.URL.Query().Get("state") {
8✔
155
                rest.SendErrorJSON(w, r, p.L, http.StatusForbidden, nil, "unexpected state")
×
156
                return
×
157
        }
×
158

159
        p.conf.RedirectURL = p.makeRedirURL(r.URL.Path)
8✔
160

8✔
161
        p.Logf("[DEBUG] token with state %s", retrievedState)
8✔
162
        tok, err := p.conf.Exchange(context.Background(), r.URL.Query().Get("code"))
8✔
163
        if err != nil {
8✔
164
                rest.SendErrorJSON(w, r, p.L, http.StatusInternalServerError, err, "exchange failed")
×
165
                return
×
166
        }
×
167

168
        client := p.conf.Client(context.Background(), tok)
8✔
169
        uinfo, err := client.Get(p.infoURL)
8✔
170
        if err != nil {
8✔
171
                rest.SendErrorJSON(w, r, p.L, http.StatusServiceUnavailable, err, "failed to get client info")
×
172
                return
×
173
        }
×
174

175
        defer func() {
16✔
176
                if e := uinfo.Body.Close(); e != nil {
8✔
177
                        p.Logf("[WARN] failed to close response body, %s", e)
×
178
                }
×
179
        }()
180

181
        data, err := io.ReadAll(uinfo.Body)
8✔
182
        if err != nil {
8✔
183
                rest.SendErrorJSON(w, r, p.L, http.StatusInternalServerError, err, "failed to read user info")
×
184
                return
×
185
        }
×
186

187
        jData := map[string]interface{}{}
8✔
188
        if e := json.Unmarshal(data, &jData); e != nil {
8✔
189
                rest.SendErrorJSON(w, r, p.L, http.StatusInternalServerError, err, "failed to unmarshal user info")
×
190
                return
×
191
        }
×
192
        p.Logf("[DEBUG] got raw user info %+v", jData)
8✔
193

8✔
194
        u := p.mapUser(jData, data)
8✔
195
        if oauthClaims.NoAva {
9✔
196
                u.Picture = "" // reset picture on no avatar request
1✔
197
        }
1✔
198
        u, err = setAvatar(p.AvatarSaver, u, client)
8✔
199
        if err != nil {
8✔
200
                rest.SendErrorJSON(w, r, p.L, http.StatusInternalServerError, err, "failed to save avatar to proxy")
×
201
                return
×
202
        }
×
203

204
        cid, err := randToken()
8✔
205
        if err != nil {
8✔
206
                rest.SendErrorJSON(w, r, p.L, http.StatusInternalServerError, err, "failed to make claim's id")
×
207
                return
×
208
        }
×
209
        claims := token.Claims{
8✔
210
                User: &u,
8✔
211
                StandardClaims: jwt.StandardClaims{
8✔
212
                        Issuer:   p.Issuer,
8✔
213
                        Id:       cid,
8✔
214
                        Audience: oauthClaims.Audience,
8✔
215
                },
8✔
216
                SessionOnly: oauthClaims.SessionOnly,
8✔
217
                NoAva:       oauthClaims.NoAva,
8✔
218
        }
8✔
219

8✔
220
        if _, err = p.JwtService.Set(w, claims); err != nil {
8✔
221
                rest.SendErrorJSON(w, r, p.L, http.StatusInternalServerError, err, "failed to set token")
×
222
                return
×
223
        }
×
224

225
        if p.bearerTokenHook != nil && tok != nil {
10✔
226
                p.Logf("[DEBUG] pass bearer token %s, %s", p.Name(), tok.TokenType)
2✔
227
                p.bearerTokenHook(p.Name(), u, *tok)
2✔
228
        }
2✔
229

230
        p.Logf("[DEBUG] user info %+v", u)
8✔
231

8✔
232
        // redirect to back url if presented in login query params
8✔
233
        if oauthClaims.Handshake != nil && oauthClaims.Handshake.From != "" {
8✔
234
                http.Redirect(w, r, oauthClaims.Handshake.From, http.StatusTemporaryRedirect)
×
235
                return
×
236
        }
×
237
        rest.RenderJSON(w, &u)
8✔
238
}
239

240
// LogoutHandler - GET /logout
241
func (p Oauth2Handler) LogoutHandler(w http.ResponseWriter, r *http.Request) {
2✔
242
        if _, _, err := p.JwtService.Get(r); err != nil {
3✔
243
                rest.SendErrorJSON(w, r, p.L, http.StatusForbidden, err, "logout not allowed")
1✔
244
                return
1✔
245
        }
1✔
246
        p.JwtService.Reset(w)
1✔
247
}
248

249
func (p Oauth2Handler) makeRedirURL(path string) string {
48✔
250
        elems := strings.Split(path, "/")
48✔
251
        newPath := strings.Join(elems[:len(elems)-1], "/")
48✔
252

48✔
253
        return strings.TrimSuffix(p.URL, "/") + strings.TrimSuffix(newPath, "/") + urlCallbackSuffix
48✔
254
}
48✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc