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

mendersoftware / useradm / 1729082127

21 Mar 2025 10:20PM UTC coverage: 70.971%. Remained the same
1729082127

Pull #436

gitlab-ci

web-flow
chore: bump github.com/golang-jwt/jwt/v4 from 4.5.0 to 4.5.2

Bumps [github.com/golang-jwt/jwt/v4](https://github.com/golang-jwt/jwt) from 4.5.0 to 4.5.2.
- [Release notes](https://github.com/golang-jwt/jwt/releases)
- [Changelog](https://github.com/golang-jwt/jwt/blob/main/VERSION_HISTORY.md)
- [Commits](https://github.com/golang-jwt/jwt/compare/v4.5.0...v4.5.2)

---
updated-dependencies:
- dependency-name: github.com/golang-jwt/jwt/v4
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #436: chore: bump github.com/golang-jwt/jwt/v4 from 4.5.0 to 4.5.2

2792 of 3934 relevant lines covered (70.97%)

40.83 hits per line

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

91.19
/api/http/api_useradm.go
1
// Copyright 2023 Northern.tech AS
2
//
3
//        Licensed under the Apache License, Version 2.0 (the "License");
4
//        you may not use this file except in compliance with the License.
5
//        You may obtain a copy of the License at
6
//
7
//            http://www.apache.org/licenses/LICENSE-2.0
8
//
9
//        Unless required by applicable law or agreed to in writing, software
10
//        distributed under the License is distributed on an "AS IS" BASIS,
11
//        WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
//        See the License for the specific language governing permissions and
13
//        limitations under the License.
14
package http
15

16
import (
17
        "context"
18
        "net/http"
19
        "strings"
20
        "time"
21

22
        "github.com/ant0ine/go-json-rest/rest"
23
        "github.com/google/uuid"
24
        "github.com/mendersoftware/go-lib-micro/accesslog"
25
        "github.com/mendersoftware/go-lib-micro/identity"
26
        "github.com/mendersoftware/go-lib-micro/log"
27
        "github.com/mendersoftware/go-lib-micro/requestid"
28
        "github.com/mendersoftware/go-lib-micro/requestlog"
29
        "github.com/mendersoftware/go-lib-micro/rest_utils"
30
        "github.com/mendersoftware/go-lib-micro/routing"
31
        "github.com/pkg/errors"
32

33
        "github.com/mendersoftware/useradm/authz"
34
        "github.com/mendersoftware/useradm/jwt"
35
        "github.com/mendersoftware/useradm/model"
36
        "github.com/mendersoftware/useradm/store"
37
        useradm "github.com/mendersoftware/useradm/user"
38
)
39

40
const (
41
        apiUrlManagementV1       = "/api/management/v1/useradm"
42
        uriManagementAuthLogin   = apiUrlManagementV1 + "/auth/login"
43
        uriManagementAuthLogout  = apiUrlManagementV1 + "/auth/logout"
44
        uriManagementUser        = apiUrlManagementV1 + "/users/#id"
45
        uriManagementUsers       = apiUrlManagementV1 + "/users"
46
        uriManagementSettings    = apiUrlManagementV1 + "/settings"
47
        uriManagementSettingsMe  = apiUrlManagementV1 + "/settings/me"
48
        uriManagementTokens      = apiUrlManagementV1 + "/settings/tokens"
49
        uriManagementToken       = apiUrlManagementV1 + "/settings/tokens/#id"
50
        uriManagementPlans       = apiUrlManagementV1 + "/plans"
51
        uriManagementPlanBinding = apiUrlManagementV1 + "/plan_binding"
52

53
        apiUrlInternalV1  = "/api/internal/v1/useradm"
54
        uriInternalAlive  = apiUrlInternalV1 + "/alive"
55
        uriInternalHealth = apiUrlInternalV1 + "/health"
56

57
        uriInternalAuthVerify  = apiUrlInternalV1 + "/auth/verify"
58
        uriInternalTenants     = apiUrlInternalV1 + "/tenants"
59
        uriInternalTenantUsers = apiUrlInternalV1 + "/tenants/#id/users"
60
        uriInternalTenantUser  = apiUrlInternalV1 + "/tenants/#id/users/#userid"
61
        uriInternalTokens      = apiUrlInternalV1 + "/tokens"
62
)
63

64
const (
65
        defaultTimeout = time.Second * 5
66
        hdrETag        = "ETag"
67
        hdrIfMatch     = "If-Match"
68
)
69

70
var (
71
        ErrAuthHeader   = errors.New("invalid or missing auth header")
72
        ErrUserNotFound = errors.New("user not found")
73
)
74

75
type UserAdmApiHandlers struct {
76
        userAdm useradm.App
77
        db      store.DataStore
78
        jwth    map[int]jwt.Handler
79
        config  Config
80
}
81

82
type Config struct {
83
        // maximum expiration time for Personal Access Token
84
        TokenMaxExpSeconds int
85

86
        JWTFallback jwt.Handler
87
}
88

89
// return an ApiHandler for user administration and authentiacation app
90
func NewUserAdmApiHandlers(
91
        userAdm useradm.App,
92
        db store.DataStore,
93
        jwth map[int]jwt.Handler,
94
        config Config,
95
) ApiHandler {
93✔
96
        return &UserAdmApiHandlers{
93✔
97
                userAdm: userAdm,
93✔
98
                db:      db,
93✔
99
                jwth:    jwth,
93✔
100
                config:  config,
93✔
101
        }
93✔
102
}
93✔
103

104
func wrapMiddleware(middleware rest.Middleware, routes ...*rest.Route) []*rest.Route {
93✔
105
        for _, route := range routes {
2,511✔
106
                route.Func = middleware.MiddlewareFunc(route.Func)
2,418✔
107
        }
2,418✔
108
        return routes
93✔
109
}
110

111
func (i *UserAdmApiHandlers) Build(authorizer authz.Authorizer) (http.Handler, error) {
93✔
112
        api := rest.NewApi()
93✔
113
        api.Use(
93✔
114
                // Apply common middlewares
93✔
115
                &requestlog.RequestLogMiddleware{},
93✔
116
                &accesslog.AccessLogMiddleware{
93✔
117
                        Format: accesslog.SimpleLogFormat,
93✔
118
                        DisableLog: func(statusCode int, r *rest.Request) bool {
332✔
119
                                if (r.URL.Path == uriInternalAlive ||
239✔
120
                                        r.URL.Path == uriInternalHealth) &&
239✔
121
                                        statusCode < 300 {
241✔
122
                                        return true
2✔
123
                                }
2✔
124
                                return false
237✔
125
                        },
126
                },
127
                &requestid.RequestIdMiddleware{},
128
                &rest.ContentTypeCheckerMiddleware{},
129
        )
130
        identityMiddleware := &identity.IdentityMiddleware{
93✔
131
                UpdateLogger: true,
93✔
132
        }
93✔
133
        authzMiddleware := &authz.AuthzMiddleware{
93✔
134
                Authz:              authorizer,
93✔
135
                ResFunc:            ExtractResourceAction,
93✔
136
                JWTHandlers:        i.jwth,
93✔
137
                JWTFallbackHandler: i.config.JWTFallback,
93✔
138
        }
93✔
139
        internalRoutes := []*rest.Route{
93✔
140
                rest.Get(uriInternalAlive, i.AliveHandler),
93✔
141
                rest.Get(uriInternalHealth, i.HealthHandler),
93✔
142

93✔
143
                rest.Get(uriInternalAuthVerify,
93✔
144
                        authzMiddleware.MiddlewareFunc(
93✔
145
                                identityMiddleware.MiddlewareFunc(i.AuthVerifyHandler),
93✔
146
                        ),
93✔
147
                ),
93✔
148
                rest.Post(uriInternalAuthVerify,
93✔
149
                        authzMiddleware.MiddlewareFunc(
93✔
150
                                identityMiddleware.MiddlewareFunc(i.AuthVerifyHandler),
93✔
151
                        ),
93✔
152
                ),
93✔
153
                rest.Post(uriInternalTenants, i.CreateTenantHandler),
93✔
154
                rest.Post(uriInternalTenantUsers, i.CreateTenantUserHandler),
93✔
155
                rest.Delete(uriInternalTenantUser, i.DeleteTenantUserHandler),
93✔
156
                rest.Get(uriInternalTenantUsers, i.GetTenantUsersHandler),
93✔
157
                rest.Delete(uriInternalTokens, i.DeleteTokensHandler),
93✔
158
        }
93✔
159
        mgmtRoutes := routing.AutogenOptionsRoutes([]*rest.Route{
93✔
160
                rest.Post(uriManagementAuthLogin, i.AuthLoginHandler),
93✔
161
                rest.Post(uriManagementAuthLogout, i.AuthLogoutHandler),
93✔
162
                rest.Post(uriManagementUsers, i.AddUserHandler),
93✔
163
                rest.Get(uriManagementUsers, i.GetUsersHandler),
93✔
164
                rest.Get(uriManagementUser, i.GetUserHandler),
93✔
165
                rest.Put(uriManagementUser, i.UpdateUserHandler),
93✔
166
                rest.Delete(uriManagementUser, i.DeleteUserHandler),
93✔
167
                rest.Post(uriManagementSettings, i.SaveSettingsHandler),
93✔
168
                rest.Get(uriManagementSettings, i.GetSettingsHandler),
93✔
169
                rest.Post(uriManagementSettingsMe, i.SaveSettingsMeHandler),
93✔
170
                rest.Get(uriManagementSettingsMe, i.GetSettingsMeHandler),
93✔
171
                rest.Post(uriManagementTokens, i.IssueTokenHandler),
93✔
172
                rest.Get(uriManagementTokens, i.GetTokensHandler),
93✔
173
                rest.Delete(uriManagementToken, i.DeleteTokenHandler),
93✔
174
                // plans
93✔
175
                rest.Get(uriManagementPlans, i.GetPlansHandler),
93✔
176
                rest.Get(uriManagementPlanBinding, i.GetPlanBindingHandler),
93✔
177
        }, routing.AllowHeaderOptionsGenerator)
93✔
178

93✔
179
        // IdentityMiddleware is only applied to public APIs
93✔
180
        mgmtRoutes = wrapMiddleware(identityMiddleware, mgmtRoutes...)
93✔
181

93✔
182
        routes := append(mgmtRoutes, internalRoutes...)
93✔
183
        app, err := rest.MakeRouter(routes...)
93✔
184
        if err != nil {
93✔
185
                return nil, errors.Wrap(err, "failed to create router")
×
186
        }
×
187
        api.SetApp(app)
93✔
188

93✔
189
        return api.MakeHandler(), nil
93✔
190
}
191

192
func (u *UserAdmApiHandlers) AliveHandler(w rest.ResponseWriter, r *rest.Request) {
1✔
193
        w.WriteHeader(http.StatusNoContent)
1✔
194
}
1✔
195

196
func (u *UserAdmApiHandlers) HealthHandler(w rest.ResponseWriter, r *rest.Request) {
2✔
197
        ctx := r.Context()
2✔
198
        l := log.FromContext(ctx)
2✔
199
        ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
2✔
200
        defer cancel()
2✔
201

2✔
202
        err := u.userAdm.HealthCheck(ctx)
2✔
203
        if err != nil {
3✔
204
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusServiceUnavailable)
1✔
205
                return
1✔
206
        }
1✔
207
        w.WriteHeader(http.StatusNoContent)
1✔
208
}
209

210
func (u *UserAdmApiHandlers) AuthLoginHandler(w rest.ResponseWriter, r *rest.Request) {
40✔
211
        ctx := r.Context()
40✔
212

40✔
213
        l := log.FromContext(ctx)
40✔
214

40✔
215
        //parse auth header
40✔
216
        user, pass, ok := r.BasicAuth()
40✔
217
        if !ok {
41✔
218
                rest_utils.RestErrWithLog(w, r, l,
1✔
219
                        ErrAuthHeader, http.StatusUnauthorized)
1✔
220
                return
1✔
221
        }
1✔
222
        email := model.Email(strings.ToLower(user))
39✔
223

39✔
224
        options := &useradm.LoginOptions{}
39✔
225
        err := r.DecodeJsonPayload(options)
39✔
226
        if err != nil && err != rest.ErrJsonPayloadEmpty {
40✔
227
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
1✔
228
                return
1✔
229
        }
1✔
230

231
        token, err := u.userAdm.Login(ctx, email, pass, options)
38✔
232
        if err != nil {
46✔
233
                switch {
8✔
234
                case err == useradm.ErrUnauthorized || err == useradm.ErrTenantAccountSuspended:
6✔
235
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusUnauthorized)
6✔
236
                default:
2✔
237
                        rest_utils.RestErrWithLogInternal(w, r, l, err)
2✔
238
                }
239
                return
8✔
240
        }
241

242
        raw, err := u.userAdm.SignToken(ctx, token)
30✔
243
        if err != nil {
31✔
244
                rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
245
                return
1✔
246
        }
1✔
247

248
        writer := w.(http.ResponseWriter)
29✔
249
        writer.Header().Set("Content-Type", "application/jwt")
29✔
250
        _, _ = writer.Write([]byte(raw))
29✔
251
}
252

253
func (u *UserAdmApiHandlers) AuthLogoutHandler(w rest.ResponseWriter, r *rest.Request) {
4✔
254
        ctx := r.Context()
4✔
255
        l := log.FromContext(ctx)
4✔
256

4✔
257
        if tokenStr, err := authz.ExtractToken(r.Request); err == nil {
8✔
258
                keyId := jwt.GetKeyId(tokenStr)
4✔
259
                if _, ok := u.jwth[keyId]; !ok {
4✔
260
                        rest_utils.RestErrWithLogInternal(w, r, l, errors.New("internal error"))
×
261
                        return
×
262
                }
×
263

264
                token, err := u.jwth[keyId].FromJWT(tokenStr)
4✔
265
                if err != nil {
5✔
266
                        rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
267
                        return
1✔
268
                }
1✔
269
                if err := u.userAdm.Logout(ctx, token); err != nil {
4✔
270
                        rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
271
                        return
1✔
272
                }
1✔
273
        }
274

275
        w.WriteHeader(http.StatusAccepted)
2✔
276
}
277

278
func (u *UserAdmApiHandlers) AuthVerifyHandler(w rest.ResponseWriter, r *rest.Request) {
21✔
279
        ctx := r.Context()
21✔
280

21✔
281
        l := log.FromContext(ctx)
21✔
282

21✔
283
        // note that the request has passed through authz - the token is valid
21✔
284
        token := authz.GetRequestToken(r.Env)
21✔
285

21✔
286
        err := u.userAdm.Verify(ctx, token)
21✔
287
        if err != nil {
30✔
288
                if err == useradm.ErrUnauthorized {
15✔
289
                        rest_utils.RestErrWithLog(w, r, l, useradm.ErrUnauthorized, http.StatusUnauthorized)
6✔
290
                } else {
9✔
291
                        rest_utils.RestErrWithLogInternal(w, r, l, err)
3✔
292
                }
3✔
293
                return
9✔
294
        }
295

296
        w.WriteHeader(http.StatusOK)
12✔
297
}
298

299
func (u *UserAdmApiHandlers) CreateTenantUserHandler(w rest.ResponseWriter, r *rest.Request) {
10✔
300
        ctx := r.Context()
10✔
301

10✔
302
        l := log.FromContext(ctx)
10✔
303

10✔
304
        user, err := parseUserInternal(r)
10✔
305
        if err != nil {
14✔
306
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
4✔
307
                return
4✔
308
        }
4✔
309

310
        tenantId := r.PathParam("id")
6✔
311
        if tenantId == "" {
7✔
312
                rest_utils.RestErrWithLog(w, r, l, errors.New("Entity not found"), http.StatusNotFound)
1✔
313
                return
1✔
314
        }
1✔
315
        ctx = getTenantContext(ctx, tenantId)
5✔
316
        err = u.userAdm.CreateUserInternal(ctx, user)
5✔
317
        if err != nil {
6✔
318
                if err == store.ErrDuplicateEmail {
2✔
319
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusUnprocessableEntity)
1✔
320
                } else {
1✔
321
                        rest_utils.RestErrWithLogInternal(w, r, l, err)
×
322
                }
×
323
                return
1✔
324
        }
325

326
        w.WriteHeader(http.StatusCreated)
4✔
327

328
}
329

330
func (u *UserAdmApiHandlers) AddUserHandler(w rest.ResponseWriter, r *rest.Request) {
13✔
331
        ctx := r.Context()
13✔
332

13✔
333
        l := log.FromContext(ctx)
13✔
334

13✔
335
        user, err := parseUser(r)
13✔
336
        if err != nil {
22✔
337
                if err == model.ErrPasswordTooShort {
12✔
338
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusUnprocessableEntity)
3✔
339
                } else {
9✔
340
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
6✔
341
                }
6✔
342
                return
9✔
343
        }
344

345
        err = u.userAdm.CreateUser(ctx, user)
4✔
346
        if err != nil {
5✔
347
                if err == store.ErrDuplicateEmail {
2✔
348
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusUnprocessableEntity)
1✔
349
                } else {
1✔
350
                        rest_utils.RestErrWithLogInternal(w, r, l, err)
×
351
                }
×
352
                return
1✔
353
        }
354

355
        w.Header().Add("Location", "users/"+string(user.ID))
3✔
356
        w.WriteHeader(http.StatusCreated)
3✔
357

358
}
359

360
func (u *UserAdmApiHandlers) GetUsersHandler(w rest.ResponseWriter, r *rest.Request) {
38✔
361
        ctx := r.Context()
38✔
362

38✔
363
        l := log.FromContext(ctx)
38✔
364

38✔
365
        if err := r.ParseForm(); err != nil {
40✔
366
                err = errors.Wrap(err, "api: bad form parameters")
2✔
367
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
2✔
368
                return
2✔
369
        }
2✔
370

371
        fltr := model.UserFilter{}
36✔
372
        if err := fltr.ParseForm(r.Form); err != nil {
38✔
373
                err = errors.Wrap(err, "api: invalid form values")
2✔
374
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
2✔
375
                return
2✔
376
        }
2✔
377

378
        users, err := u.userAdm.GetUsers(ctx, fltr)
34✔
379
        if err != nil {
36✔
380
                rest_utils.RestErrWithLogInternal(w, r, l, err)
2✔
381
                return
2✔
382
        }
2✔
383

384
        _ = w.WriteJson(users)
32✔
385
}
386

387
func (u *UserAdmApiHandlers) GetTenantUsersHandler(w rest.ResponseWriter, r *rest.Request) {
5✔
388
        ctx := r.Context()
5✔
389
        ctx = identity.WithContext(ctx, &identity.Identity{
5✔
390
                Tenant: r.PathParam("id"),
5✔
391
        })
5✔
392
        r.Request = r.Request.WithContext(ctx)
5✔
393
        u.GetUsersHandler(w, r)
5✔
394
}
5✔
395

396
func (u *UserAdmApiHandlers) GetUserHandler(w rest.ResponseWriter, r *rest.Request) {
13✔
397
        ctx := r.Context()
13✔
398

13✔
399
        l := log.FromContext(ctx)
13✔
400

13✔
401
        id := r.PathParam("id")
13✔
402
        user, err := u.userAdm.GetUser(ctx, id)
13✔
403
        if err != nil {
14✔
404
                rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
405
                return
1✔
406
        }
1✔
407

408
        if user == nil {
14✔
409
                rest_utils.RestErrWithLog(w, r, l, ErrUserNotFound, 404)
2✔
410
                return
2✔
411
        }
2✔
412

413
        _ = w.WriteJson(user)
10✔
414
}
415

416
func (u *UserAdmApiHandlers) UpdateUserHandler(w rest.ResponseWriter, r *rest.Request) {
18✔
417
        ctx := r.Context()
18✔
418
        idty := identity.FromContext(ctx)
18✔
419
        l := log.FromContext(ctx)
18✔
420

18✔
421
        userUpdate, err := parseUserUpdate(r)
18✔
422
        if err != nil {
22✔
423
                if err == model.ErrPasswordTooShort {
5✔
424
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusUnprocessableEntity)
1✔
425
                } else {
4✔
426
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
3✔
427
                }
3✔
428
                return
4✔
429
        }
430

431
        // extract the token used to update the user
432
        if tokenStr, err := authz.ExtractToken(r.Request); err == nil {
24✔
433
                keyId := jwt.GetKeyId(tokenStr)
10✔
434
                if _, ok := u.jwth[keyId]; !ok {
10✔
435
                        rest_utils.RestErrWithLogInternal(w, r, l, errors.New("internal error"))
×
436
                        return
×
437
                }
×
438

439
                token, err := u.jwth[keyId].FromJWT(tokenStr)
10✔
440
                if err != nil {
10✔
441
                        rest_utils.RestErrWithLogInternal(w, r, l, err)
×
442
                        return
×
443
                }
×
444
                userUpdate.Token = token
10✔
445
        }
446

447
        id := r.PathParam("id")
14✔
448
        if strings.EqualFold(id, "me") {
19✔
449
                id = idty.Subject
5✔
450
        }
5✔
451
        err = u.userAdm.UpdateUser(ctx, id, userUpdate)
14✔
452
        if err != nil {
20✔
453
                switch err {
6✔
454
                case store.ErrDuplicateEmail,
455
                        useradm.ErrCurrentPasswordMismatch,
456
                        useradm.ErrCannotModifyPassword:
4✔
457
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusUnprocessableEntity)
4✔
458
                case store.ErrUserNotFound:
1✔
459
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusNotFound)
1✔
460
                case useradm.ErrETagMismatch:
1✔
461
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusConflict)
1✔
462
                default:
×
463
                        rest_utils.RestErrWithLogInternal(w, r, l, err)
×
464
                }
465
        }
466

467
        w.WriteHeader(http.StatusNoContent)
14✔
468
}
469

470
func (u *UserAdmApiHandlers) DeleteTenantUserHandler(w rest.ResponseWriter, r *rest.Request) {
3✔
471
        ctx := r.Context()
3✔
472

3✔
473
        tenantId := r.PathParam("id")
3✔
474
        if tenantId != "" {
4✔
475
                ctx = getTenantContext(ctx, tenantId)
1✔
476
        }
1✔
477

478
        l := log.FromContext(ctx)
3✔
479
        err := u.userAdm.DeleteUser(ctx, r.PathParam("userid"))
3✔
480
        if err != nil {
4✔
481
                rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
482
                return
1✔
483
        }
1✔
484

485
        w.WriteHeader(http.StatusNoContent)
2✔
486
}
487

488
func (u *UserAdmApiHandlers) DeleteUserHandler(w rest.ResponseWriter, r *rest.Request) {
4✔
489
        ctx := r.Context()
4✔
490

4✔
491
        l := log.FromContext(ctx)
4✔
492

4✔
493
        err := u.userAdm.DeleteUser(ctx, r.PathParam("id"))
4✔
494
        if err != nil {
5✔
495
                rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
496
                return
1✔
497
        }
1✔
498

499
        w.WriteHeader(http.StatusNoContent)
3✔
500
}
501

502
func parseUser(r *rest.Request) (*model.User, error) {
13✔
503
        user := model.User{}
13✔
504

13✔
505
        //decode body
13✔
506
        err := r.DecodeJsonPayload(&user)
13✔
507
        if err != nil {
14✔
508
                return nil, errors.Wrap(err, "failed to decode request body")
1✔
509
        }
1✔
510

511
        if err := user.Validate(); err != nil {
20✔
512
                return nil, err
8✔
513
        }
8✔
514

515
        return &user, nil
4✔
516
}
517

518
func parseUserInternal(r *rest.Request) (*model.UserInternal, error) {
10✔
519
        user := model.UserInternal{}
10✔
520

10✔
521
        //decode body
10✔
522
        err := r.DecodeJsonPayload(&user)
10✔
523
        if err != nil {
11✔
524
                return nil, errors.Wrap(err, "failed to decode request body")
1✔
525
        }
1✔
526

527
        if err := user.Validate(); err != nil {
12✔
528
                return nil, err
3✔
529
        }
3✔
530

531
        return &user, nil
6✔
532
}
533

534
func parseUserUpdate(r *rest.Request) (*model.UserUpdate, error) {
18✔
535
        userUpdate := model.UserUpdate{}
18✔
536

18✔
537
        //decode body
18✔
538
        err := r.DecodeJsonPayload(&userUpdate)
18✔
539
        if err != nil {
19✔
540
                return nil, errors.Wrap(err, "failed to decode request body")
1✔
541
        }
1✔
542

543
        if err := userUpdate.Validate(); err != nil {
20✔
544
                return nil, err
3✔
545
        }
3✔
546

547
        return &userUpdate, nil
14✔
548
}
549

550
func (u *UserAdmApiHandlers) CreateTenantHandler(w rest.ResponseWriter, r *rest.Request) {
8✔
551
        ctx := r.Context()
8✔
552

8✔
553
        l := log.FromContext(ctx)
8✔
554

8✔
555
        var newTenant model.NewTenant
8✔
556

8✔
557
        if err := r.DecodeJsonPayload(&newTenant); err != nil {
9✔
558
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
1✔
559
                return
1✔
560
        }
1✔
561

562
        if err := newTenant.Validate(); err != nil {
9✔
563
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
2✔
564
                return
2✔
565
        }
2✔
566

567
        err := u.userAdm.CreateTenant(ctx, model.NewTenant{
5✔
568
                ID: newTenant.ID,
5✔
569
        })
5✔
570
        if err != nil {
6✔
571
                rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
572
                return
1✔
573
        }
1✔
574

575
        w.WriteHeader(http.StatusCreated)
4✔
576
}
577

578
func getTenantContext(ctx context.Context, tenantId string) context.Context {
6✔
579
        if ctx == nil {
6✔
580
                ctx = context.Background()
×
581
        }
×
582
        if tenantId != "" {
12✔
583
                id := &identity.Identity{
6✔
584
                        Tenant: tenantId,
6✔
585
                }
6✔
586

6✔
587
                ctx = identity.WithContext(ctx, id)
6✔
588
        }
6✔
589

590
        return ctx
6✔
591
}
592

593
func (u *UserAdmApiHandlers) DeleteTokensHandler(w rest.ResponseWriter, r *rest.Request) {
4✔
594

4✔
595
        ctx := r.Context()
4✔
596

4✔
597
        l := log.FromContext(ctx)
4✔
598

4✔
599
        tenantId := r.URL.Query().Get("tenant_id")
4✔
600
        if tenantId == "" {
5✔
601
                rest_utils.RestErrWithLog(
1✔
602
                        w,
1✔
603
                        r,
1✔
604
                        l,
1✔
605
                        errors.New("tenant_id must be provided"),
1✔
606
                        http.StatusBadRequest,
1✔
607
                )
1✔
608
                return
1✔
609
        }
1✔
610
        userId := r.URL.Query().Get("user_id")
3✔
611

3✔
612
        err := u.userAdm.DeleteTokens(ctx, tenantId, userId)
3✔
613
        switch err {
3✔
614
        case nil:
2✔
615
                w.WriteHeader(http.StatusNoContent)
2✔
616
        default:
1✔
617
                rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
618
        }
619
}
620

621
func (u *UserAdmApiHandlers) SaveSettingsHandler(w rest.ResponseWriter, r *rest.Request) {
9✔
622
        u.saveSettingsHandler(w, r, false)
9✔
623
}
9✔
624

625
func (u *UserAdmApiHandlers) SaveSettingsMeHandler(w rest.ResponseWriter, r *rest.Request) {
×
626
        u.saveSettingsHandler(w, r, true)
×
627
}
×
628

629
func (u *UserAdmApiHandlers) saveSettingsHandler(w rest.ResponseWriter, r *rest.Request, me bool) {
9✔
630
        ctx := r.Context()
9✔
631

9✔
632
        l := log.FromContext(ctx)
9✔
633

9✔
634
        settings := &model.Settings{}
9✔
635
        err := r.DecodeJsonPayload(settings)
9✔
636
        if err != nil {
11✔
637
                rest_utils.RestErrWithLog(
2✔
638
                        w,
2✔
639
                        r,
2✔
640
                        l,
2✔
641
                        errors.New("cannot parse request body as json"),
2✔
642
                        http.StatusBadRequest,
2✔
643
                )
2✔
644
                return
2✔
645
        }
2✔
646

647
        if err := settings.Validate(); err != nil {
8✔
648
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
1✔
649
                return
1✔
650
        }
1✔
651

652
        ifMatchHeader := r.Header.Get(hdrIfMatch)
6✔
653

6✔
654
        settings.ETag = uuid.NewString()
6✔
655
        if me {
6✔
656
                id := identity.FromContext(ctx)
×
657
                if id == nil {
×
658
                        rest_utils.RestErrWithLogInternal(w, r, l, errors.New("identity not present"))
×
659
                        return
×
660
                }
×
661
                err = u.db.SaveUserSettings(ctx, id.Subject, settings, ifMatchHeader)
×
662
        } else {
6✔
663
                err = u.db.SaveSettings(ctx, settings, ifMatchHeader)
6✔
664
        }
6✔
665
        if err == store.ErrETagMismatch {
7✔
666
                rest_utils.RestErrWithInfoMsg(w, r, l, err, http.StatusPreconditionFailed, err.Error())
1✔
667
                return
1✔
668
        } else if err != nil {
7✔
669
                rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
670
                return
1✔
671
        }
1✔
672

673
        w.WriteHeader(http.StatusCreated)
4✔
674
}
675

676
func (u *UserAdmApiHandlers) GetSettingsHandler(w rest.ResponseWriter, r *rest.Request) {
7✔
677
        u.getSettingsHandler(w, r, false)
7✔
678
}
7✔
679

680
func (u *UserAdmApiHandlers) GetSettingsMeHandler(w rest.ResponseWriter, r *rest.Request) {
×
681
        u.getSettingsHandler(w, r, true)
×
682
}
×
683

684
func (u *UserAdmApiHandlers) getSettingsHandler(w rest.ResponseWriter, r *rest.Request, me bool) {
7✔
685
        ctx := r.Context()
7✔
686

7✔
687
        l := log.FromContext(ctx)
7✔
688

7✔
689
        var settings *model.Settings
7✔
690
        var err error
7✔
691
        if me {
7✔
692
                id := identity.FromContext(ctx)
×
693
                if id == nil {
×
694
                        rest_utils.RestErrWithLogInternal(w, r, l, errors.New("identity not present"))
×
695
                        return
×
696
                }
×
697
                settings, err = u.db.GetUserSettings(ctx, id.Subject)
×
698
        } else {
7✔
699
                settings, err = u.db.GetSettings(ctx)
7✔
700
        }
7✔
701

702
        if err != nil {
8✔
703
                rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
704
                return
1✔
705
        } else if settings == nil {
8✔
706
                settings = &model.Settings{
1✔
707
                        Values: model.SettingsValues{},
1✔
708
                }
1✔
709
        }
1✔
710

711
        if settings.ETag != "" {
10✔
712
                w.Header().Set(hdrETag, settings.ETag)
4✔
713
        }
4✔
714
        _ = w.WriteJson(settings)
6✔
715
}
716

717
func (u *UserAdmApiHandlers) IssueTokenHandler(w rest.ResponseWriter, r *rest.Request) {
23✔
718
        ctx := r.Context()
23✔
719

23✔
720
        l := log.FromContext(ctx)
23✔
721

23✔
722
        var tokenRequest model.TokenRequest
23✔
723

23✔
724
        if err := r.DecodeJsonPayload(&tokenRequest); err != nil {
23✔
725
                rest_utils.RestErrWithLog(
×
726
                        w,
×
727
                        r,
×
728
                        l,
×
729
                        errors.New("cannot parse request body as json"),
×
730
                        http.StatusBadRequest,
×
731
                )
×
732
                return
×
733
        }
×
734
        if err := tokenRequest.Validate(u.config.TokenMaxExpSeconds); err != nil {
25✔
735
                rest_utils.RestErrWithLog(
2✔
736
                        w,
2✔
737
                        r,
2✔
738
                        l,
2✔
739
                        err,
2✔
740
                        http.StatusBadRequest,
2✔
741
                )
2✔
742
                return
2✔
743
        }
2✔
744

745
        token, err := u.userAdm.IssuePersonalAccessToken(ctx, &tokenRequest)
21✔
746
        switch err {
21✔
747
        case nil:
16✔
748
                writer := w.(http.ResponseWriter)
16✔
749
                writer.Header().Set("Content-Type", "application/jwt")
16✔
750
                _, _ = writer.Write([]byte(token))
16✔
751
        case useradm.ErrTooManyTokens:
2✔
752
                rest_utils.RestErrWithLog(
2✔
753
                        w,
2✔
754
                        r,
2✔
755
                        l,
2✔
756
                        err,
2✔
757
                        http.StatusUnprocessableEntity,
2✔
758
                )
2✔
759
        case useradm.ErrDuplicateTokenName:
3✔
760
                rest_utils.RestErrWithLog(
3✔
761
                        w,
3✔
762
                        r,
3✔
763
                        l,
3✔
764
                        err,
3✔
765
                        http.StatusConflict,
3✔
766
                )
3✔
767
        default:
×
768
                rest_utils.RestErrWithLogInternal(w, r, l, err)
×
769
        }
770
}
771

772
func (u *UserAdmApiHandlers) GetTokensHandler(w rest.ResponseWriter, r *rest.Request) {
4✔
773
        ctx := r.Context()
4✔
774
        l := log.FromContext(ctx)
4✔
775
        id := identity.FromContext(ctx)
4✔
776
        if id == nil {
4✔
777
                rest_utils.RestErrWithLogInternal(w, r, l, errors.New("identity not present"))
×
778
                return
×
779
        }
×
780

781
        tokens, err := u.userAdm.GetPersonalAccessTokens(ctx, id.Subject)
4✔
782
        if err != nil {
5✔
783
                rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
784
                return
1✔
785
        }
1✔
786

787
        _ = w.WriteJson(tokens)
3✔
788
}
789

790
func (u *UserAdmApiHandlers) DeleteTokenHandler(w rest.ResponseWriter, r *rest.Request) {
1✔
791
        ctx := r.Context()
1✔
792

1✔
793
        l := log.FromContext(ctx)
1✔
794

1✔
795
        err := u.userAdm.DeleteToken(ctx, r.PathParam("id"))
1✔
796
        if err != nil {
1✔
797
                rest_utils.RestErrWithLogInternal(w, r, l, err)
×
798
                return
×
799
        }
×
800

801
        w.WriteHeader(http.StatusNoContent)
1✔
802
}
803

804
// plans and plan binding
805

806
func (u *UserAdmApiHandlers) GetPlansHandler(w rest.ResponseWriter, r *rest.Request) {
5✔
807
        ctx := r.Context()
5✔
808
        l := log.FromContext(ctx)
5✔
809
        page, perPage, err := rest_utils.ParsePagination(r)
5✔
810
        if err != nil {
7✔
811
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
2✔
812
                return
2✔
813
        }
2✔
814

815
        plans := u.userAdm.GetPlans(ctx, int((page-1)*perPage), int(perPage))
3✔
816
        if plans == nil {
4✔
817
                plans = []model.Plan{}
1✔
818
        }
1✔
819

820
        _ = w.WriteJson(plans)
3✔
821
}
822

823
func (u *UserAdmApiHandlers) GetPlanBindingHandler(w rest.ResponseWriter, r *rest.Request) {
2✔
824
        ctx := r.Context()
2✔
825
        l := log.FromContext(ctx)
2✔
826

2✔
827
        planBinding, err := u.userAdm.GetPlanBinding(ctx)
2✔
828
        if err != nil {
3✔
829
                rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
830
                return
1✔
831
        }
1✔
832

833
        _ = w.WriteJson(planBinding)
1✔
834
}
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