• 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

77.4
/provider/custom_server.go
1
package provider
2

3
import (
4
        "context"
5
        "fmt"
6
        "html/template"
7
        "net"
8
        "net/http"
9
        "net/url"
10
        "strings"
11
        "sync"
12
        "time"
13

14
        goauth2 "github.com/go-oauth2/oauth2/v4/server"
15
        "github.com/microcosm-cc/bluemonday"
16
        "golang.org/x/oauth2"
17

18
        "github.com/go-pkgz/auth/avatar"
19
        "github.com/go-pkgz/auth/logger"
20
        "github.com/go-pkgz/auth/token"
21
)
22

23
// CustomHandlerOpt are options to initialize a handler for oauth2 server
24
type CustomHandlerOpt struct {
25
        Endpoint          oauth2.Endpoint
26
        InfoURL           string
27
        MapUserFn         func(UserData, []byte) token.User
28
        BearerTokenHookFn BearerTokenHook
29
        Scopes            []string
30
}
31

32
// CustomServerOpt are options to initialize a custom go-oauth2/oauth2 server
33
type CustomServerOpt struct {
34
        logger.L
35
        URL              string
36
        WithLoginPage    bool
37
        LoginPageHandler func(w http.ResponseWriter, r *http.Request)
38
}
39

40
// NewCustomServer is helper function to initiate a customer server and prefill
41
// options needed for provider registration (see Service.AddCustomProvider)
42
func NewCustomServer(srv *goauth2.Server, sopts CustomServerOpt) *CustomServer {
1✔
43
        copts := CustomHandlerOpt{
1✔
44
                Endpoint: oauth2.Endpoint{
1✔
45
                        AuthURL:  sopts.URL + "/authorize",
1✔
46
                        TokenURL: sopts.URL + "/access_token",
1✔
47
                },
1✔
48
                InfoURL:   sopts.URL + "/user",
1✔
49
                MapUserFn: defaultMapUserFn,
1✔
50
        }
1✔
51

1✔
52
        return &CustomServer{
1✔
53
                L:                sopts.L,
1✔
54
                URL:              sopts.URL,
1✔
55
                WithLoginPage:    sopts.WithLoginPage,
1✔
56
                LoginPageHandler: sopts.LoginPageHandler,
1✔
57
                OauthServer:      srv,
1✔
58
                HandlerOpt:       copts,
1✔
59
        }
1✔
60
}
1✔
61

62
// CustomServer is a wrapper over go-oauth2/oauth2 server running on its own port
63
type CustomServer struct {
64
        logger.L
65
        URL              string                                       // root url for custom oauth2 server
66
        WithLoginPage    bool                                         // redirect to login html page if true
67
        LoginPageHandler func(w http.ResponseWriter, r *http.Request) // handler for user-defined login page
68
        OauthServer      *goauth2.Server                              // an instance of go-oauth2/oauth2 server
69
        HandlerOpt       CustomHandlerOpt
70
        httpServer       *http.Server
71
        lock             sync.Mutex
72
}
73

74
// Run starts serving on port from c.URL
75
func (c *CustomServer) Run(ctx context.Context) {
2✔
76
        c.Logf("[INFO] run local go-oauth2/oauth2 server on %s", c.URL)
2✔
77
        c.lock.Lock()
2✔
78

2✔
79
        u, err := url.Parse(c.URL)
2✔
80
        if err != nil {
2✔
81
                c.Logf("[ERROR] failed to parse service base URL=%s", c.URL)
×
82
                return
×
83
        }
×
84

85
        _, port, err := net.SplitHostPort(u.Host)
2✔
86
        if err != nil {
2✔
87
                c.Logf("[ERROR] failed to get port from URL=%s", c.URL)
×
88
                return
×
89
        }
×
90

91
        c.httpServer = &http.Server{
2✔
92
                Addr:              fmt.Sprintf(":%s", port),
2✔
93
                ReadHeaderTimeout: 5 * time.Second,
2✔
94
                Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
8✔
95
                        switch {
6✔
96
                        case strings.HasSuffix(r.URL.Path, "/authorize"):
3✔
97
                                c.handleAuthorize(w, r)
3✔
98
                        case strings.HasSuffix(r.URL.Path, "/access_token"):
1✔
99
                                if err = c.OauthServer.HandleTokenRequest(w, r); err != nil {
1✔
100
                                        http.Error(w, err.Error(), http.StatusInternalServerError)
×
101
                                }
×
102
                        case strings.HasPrefix(r.URL.Path, "/user"):
1✔
103
                                c.handleUserInfo(w, r)
1✔
104
                        case strings.HasPrefix(r.URL.Path, "/avatar"):
1✔
105
                                c.handleAvatar(w, r)
1✔
106
                        default:
×
107
                                w.WriteHeader(http.StatusBadRequest)
×
108
                                return
×
109
                        }
110
                }),
111
        }
112
        c.lock.Unlock()
2✔
113

2✔
114
        go func() {
4✔
115
                <-ctx.Done()
2✔
116
                c.Logf("[DEBUG] cancellation via context, %v", ctx.Err())
2✔
117
                c.Shutdown()
2✔
118
        }()
2✔
119

120
        err = c.httpServer.ListenAndServe()
2✔
121
        c.Logf("[WARN] go-oauth2/oauth2 server terminated, %s", err)
2✔
122
}
123

124
func (c *CustomServer) handleAuthorize(w http.ResponseWriter, r *http.Request) {
3✔
125
        // called for first time, ask for username
3✔
126
        if c.WithLoginPage || c.LoginPageHandler != nil {
6✔
127
                if r.ParseForm() != nil || r.Form.Get("username") == "" {
5✔
128
                        // show default template if user-defined function not specified
2✔
129
                        if c.LoginPageHandler != nil {
3✔
130
                                c.LoginPageHandler(w, r)
1✔
131
                                return
1✔
132
                        }
1✔
133
                        userLoginTmpl, err := template.New("page").Parse(defaultLoginTmpl)
1✔
134
                        if err != nil {
1✔
135
                                c.Logf("[ERROR] can't parse user login template, %s", err)
×
136
                                return
×
137
                        }
×
138

139
                        formData := struct{ Query template.URL }{Query: template.URL(r.URL.RawQuery)} //nolint:gosec // query is safe
1✔
140

1✔
141
                        if err := userLoginTmpl.Execute(w, formData); err != nil {
1✔
142
                                c.Logf("[WARN] can't write, %s", err)
×
143
                        }
×
144
                        return
1✔
145
                }
146
        }
147

148
        err := c.OauthServer.HandleAuthorizeRequest(w, r)
1✔
149
        if err != nil {
1✔
150
                http.Error(w, err.Error(), http.StatusBadRequest)
×
151
                return
×
152
        }
×
153
}
154

155
func (c *CustomServer) handleUserInfo(w http.ResponseWriter, r *http.Request) {
1✔
156
        ti, err := c.OauthServer.ValidationBearerToken(r)
1✔
157
        if err != nil {
1✔
158
                http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
×
159
                return
×
160
        }
×
161
        userID := ti.GetUserID()
1✔
162

1✔
163
        p := bluemonday.UGCPolicy()
1✔
164
        ava := p.Sanitize(fmt.Sprintf(c.URL+"/avatar?user=%s", userID))
1✔
165
        res := fmt.Sprintf(`{
1✔
166
                                        "id": "%s",
1✔
167
                                        "name":"%s",
1✔
168
                                        "picture":"%s"
1✔
169
                                        }`, userID, userID, ava)
1✔
170

1✔
171
        w.Header().Set("Content-Type", "application/json; charset=utf-8")
1✔
172
        if _, err := w.Write([]byte(res)); err != nil {
1✔
173
                w.WriteHeader(http.StatusInternalServerError)
×
174
                return
×
175
        }
×
176
}
177

178
func (c *CustomServer) handleAvatar(w http.ResponseWriter, r *http.Request) {
1✔
179
        user := r.URL.Query().Get("user")
1✔
180
        b, err := avatar.GenerateAvatar(user)
1✔
181
        if err != nil {
1✔
182
                w.WriteHeader(http.StatusNotFound)
×
183
                return
×
184
        }
×
185
        if _, err = w.Write(b); err != nil {
1✔
186
                w.WriteHeader(http.StatusInternalServerError)
×
187
                return
×
188
        }
×
189
}
190

191
// Shutdown go-oauth2/oauth2 server
192
func (c *CustomServer) Shutdown() {
3✔
193
        c.Logf("[WARN] shutdown go-oauth2/oauth2 server")
3✔
194
        ctx, cancel := context.WithTimeout(context.Background(), time.Second)
3✔
195
        defer cancel()
3✔
196
        c.lock.Lock()
3✔
197
        if c.httpServer != nil {
6✔
198
                if err := c.httpServer.Shutdown(ctx); err != nil {
3✔
199
                        c.Logf("[DEBUG] go-oauth2/oauth2 shutdown error, %s", err)
×
200
                }
×
201
        }
202
        c.Logf("[DEBUG] shutdown go-oauth2/oauth2 server completed")
3✔
203
        c.lock.Unlock()
3✔
204
}
205

206
// NewCustom creates a handler for go-oauth2/oauth2 server
207
func NewCustom(name string, p Params, copts CustomHandlerOpt) Oauth2Handler {
1✔
208
        return initOauth2Handler(p, Oauth2Handler{
1✔
209
                name:            name,
1✔
210
                endpoint:        copts.Endpoint,
1✔
211
                scopes:          copts.Scopes,
1✔
212
                infoURL:         copts.InfoURL,
1✔
213
                mapUser:         copts.MapUserFn,
1✔
214
                bearerTokenHook: copts.BearerTokenHookFn,
1✔
215
        })
1✔
216
}
1✔
217

218
func defaultMapUserFn(data UserData, _ []byte) token.User {
1✔
219
        userInfo := token.User{
1✔
220
                ID:      data.Value("id"),
1✔
221
                Name:    data.Value("name"),
1✔
222
                Picture: data.Value("picture"),
1✔
223
        }
1✔
224
        return userInfo
1✔
225
}
1✔
226

227
var defaultLoginTmpl = `
228
<html>
229
        <head>
230
                <title>Dev OAuth</title>
231
                <style>
232
                        body {
233
                                text-align: center;
234
                        }
235

236
                        a {
237
                                color: hsl(200, 50%, 50%);
238
                                text-decoration-color: hsla(200, 50%, 50%, 0.5);
239
                        }
240

241
                        a:hover {
242
                                color: hsl(200, 50%, 70%);
243
                                text-decoration-color: hsla(200, 50%, 70%, 0.5);
244
                        }
245
                        
246
                        form {
247
                                font-family: Helvetica, Arial, sans-serif;
248
                                margin: 100px auto;
249
                                display: inline-block;
250
                                padding: 1em;
251
                                box-shadow: 0 0 0.1rem rgba(0, 0, 0, 0.2), 0 0 0.4rem rgba(0, 0, 0, 0.1);
252
                        }
253

254
                        .form-header {
255
                                text-align: center;
256
                        }
257

258
                        .form-header h1 {
259
                                margin: 0;
260
                        }
261

262
                        .form-header h1 a:not(:hover) {
263
                                text-decoration: none;
264
                        }
265

266
                        .form-header p {
267
                                opacity: 0.6;
268
                                margin-top: 0;
269
                                margin-bottom: 2rem;
270
                        }
271

272
                        .username-label {
273
                                opacity: 0.6;
274
                                font-size: 0.8em;
275
                        }
276

277
                        .username-input {
278
                                font-size: inherit;
279
                                margin: 0;
280
                                width: 100%;
281
                                text-align: inherit;
282
                        }
283

284
                        .form-submit {
285
                                border: none;
286
                                background: hsl(200, 50%, 50%);
287
                                color: white;
288
                                font: inherit;
289
                                padding: 0.4em 0.8em 0.3em 0.8em;
290
                                border-radius: 0.2em;
291
                                width: 100%;
292
                        }
293

294
                        .form-submit:hover,
295
                        .form-submit:focus {
296
                                background-color: hsl(200, 50%, 70%);
297
                        }
298

299
                        .form-submit:active {
300
                                background-color: hsl(200, 80%, 70%);
301
                        }
302

303
                        .username-label,
304
                        .username-input,
305
                        .form-submit {
306
                                display: block;
307
                                margin-bottom: 0.4rem;
308
                        }
309

310
                        .notice {
311
                                margin: 0;
312
                                margin-top: 2rem;
313
                                font-size: 0.8em;
314
                                opacity: 0.6;
315
                        }
316
                </style>
317
        </head>
318
        <body>
319
                <form action="/login/oauth/authorize?{{.Query}}" method="POST">
320
                        <header class="form-header">
321
                                <h1><a href="https://github.com/go-oauth2/oauth2">go-oauth2/oauth2</a></h1>
322
                                <p>Golang OAuth 2.0 Server</p>
323
                        </header>
324
                        <label>
325
                                <span class="username-label">Username</span>
326
                                <input
327
                                        class="username-input"
328
                                        type="text"
329
                                        name="username"
330
                                        value=""
331
                                        autofocus
332
                                />
333
                        </label>
334

335
                        <label>
336
                        <span class="username-label">Password</span>
337
                        <input
338
                                class="username-input"
339
                                type="password"
340
                                name="password"
341
                                value=""
342
                                autofocus
343
                        />
344
                        </label>
345

346
                        <input type="submit" class="form-submit" value="Authorize" />
347
                        <p class="notice"></p>
348
                </form>
349
        </body>
350
        <script>
351
                var input = document.querySelector(".username-input");
352
                input.focus();
353
                input.setSelectionRange(0, input.value.length)
354
        </script>
355
</html>
356
`
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