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

mendersoftware / useradm / 802076887

pending completion
802076887

Pull #354

gitlab-ci

Fabio Tranchitella
feat: limit the number of JWT tokens (sessions) per user
Pull Request #354: feat: add support for never expiring PATs

78 of 84 new or added lines in 6 files covered. (92.86%)

37 existing lines in 1 file now uncovered.

2620 of 2928 relevant lines covered (89.48%)

127.26 hits per line

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

91.26
/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/identity"
25
        "github.com/mendersoftware/go-lib-micro/log"
26
        "github.com/mendersoftware/go-lib-micro/rest_utils"
27
        "github.com/mendersoftware/go-lib-micro/routing"
28
        "github.com/pkg/errors"
29

30
        "github.com/mendersoftware/useradm/authz"
31
        "github.com/mendersoftware/useradm/jwt"
32
        "github.com/mendersoftware/useradm/model"
33
        "github.com/mendersoftware/useradm/store"
34
        useradm "github.com/mendersoftware/useradm/user"
35
)
36

37
const (
38
        apiUrlManagementV1      = "/api/management/v1/useradm"
39
        uriManagementAuthLogin  = apiUrlManagementV1 + "/auth/login"
40
        uriManagementAuthLogout = apiUrlManagementV1 + "/auth/logout"
41
        uriManagementUser       = apiUrlManagementV1 + "/users/#id"
42
        uriManagementUsers      = apiUrlManagementV1 + "/users"
43
        uriManagementSettings   = apiUrlManagementV1 + "/settings"
44
        uriManagementSettingsMe = apiUrlManagementV1 + "/settings/me"
45
        uriManagementTokens     = apiUrlManagementV1 + "/settings/tokens"
46
        uriManagementToken      = apiUrlManagementV1 + "/settings/tokens/#id"
47

48
        apiUrlInternalV1  = "/api/internal/v1/useradm"
49
        uriInternalAlive  = apiUrlInternalV1 + "/alive"
50
        uriInternalHealth = apiUrlInternalV1 + "/health"
51

52
        uriInternalAuthVerify  = apiUrlInternalV1 + "/auth/verify"
53
        uriInternalTenants     = apiUrlInternalV1 + "/tenants"
54
        uriInternalTenantUsers = apiUrlInternalV1 + "/tenants/#id/users"
55
        uriInternalTenantUser  = apiUrlInternalV1 + "/tenants/#id/users/#userid"
56
        uriInternalTokens      = apiUrlInternalV1 + "/tokens"
57
)
58

59
const (
60
        defaultTimeout = time.Second * 5
61
        hdrETag        = "ETag"
62
        hdrIfMatch     = "If-Match"
63
)
64

65
const (
66
        uriUIRoot = "/"
67
)
68

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

74
type UserAdmApiHandlers struct {
75
        userAdm useradm.App
76
        db      store.DataStore
77
        jwth    *jwt.JWTHandlerRS256
78
        config  Config
79
}
80

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

86
// return an ApiHandler for user administration and authentiacation app
87
func NewUserAdmApiHandlers(
88
        userAdm useradm.App,
89
        db store.DataStore,
90
        jwth *jwt.JWTHandlerRS256,
91
        config Config,
92
) ApiHandler {
172✔
93
        return &UserAdmApiHandlers{
172✔
94
                userAdm: userAdm,
172✔
95
                db:      db,
172✔
96
                jwth:    jwth,
172✔
97
                config:  config,
172✔
98
        }
172✔
99
}
172✔
100

101
func (i *UserAdmApiHandlers) GetApp() (rest.App, error) {
172✔
102
        routes := []*rest.Route{
172✔
103
                rest.Get(uriInternalAlive, i.AliveHandler),
172✔
104
                rest.Get(uriInternalHealth, i.HealthHandler),
172✔
105

172✔
106
                rest.Get(uriInternalAuthVerify, i.AuthVerifyHandler),
172✔
107
                rest.Post(uriInternalAuthVerify, i.AuthVerifyHandler),
172✔
108
                rest.Post(uriInternalTenants, i.CreateTenantHandler),
172✔
109
                rest.Post(uriInternalTenantUsers, i.CreateTenantUserHandler),
172✔
110
                rest.Delete(uriInternalTenantUser, i.DeleteTenantUserHandler),
172✔
111
                rest.Get(uriInternalTenantUsers, i.GetTenantUsersHandler),
172✔
112
                rest.Delete(uriInternalTokens, i.DeleteTokensHandler),
172✔
113

172✔
114
                rest.Post(uriManagementAuthLogin, i.AuthLoginHandler),
172✔
115
                rest.Post(uriManagementAuthLogout, i.AuthLogoutHandler),
172✔
116
                rest.Post(uriManagementUsers, i.AddUserHandler),
172✔
117
                rest.Get(uriManagementUsers, i.GetUsersHandler),
172✔
118
                rest.Get(uriManagementUser, i.GetUserHandler),
172✔
119
                rest.Put(uriManagementUser, i.UpdateUserHandler),
172✔
120
                rest.Delete(uriManagementUser, i.DeleteUserHandler),
172✔
121
                rest.Post(uriManagementSettings, i.SaveSettingsHandler),
172✔
122
                rest.Get(uriManagementSettings, i.GetSettingsHandler),
172✔
123
                rest.Post(uriManagementSettingsMe, i.SaveSettingsMeHandler),
172✔
124
                rest.Get(uriManagementSettingsMe, i.GetSettingsMeHandler),
172✔
125
                rest.Post(uriManagementTokens, i.IssueTokenHandler),
172✔
126
                rest.Get(uriManagementTokens, i.GetTokensHandler),
172✔
127
                rest.Delete(uriManagementToken, i.DeleteTokenHandler),
172✔
128
        }
172✔
129

172✔
130
        app, err := rest.MakeRouter(
172✔
131
                // augment routes with OPTIONS handler
172✔
132
                routing.AutogenOptionsRoutes(routes, routing.AllowHeaderOptionsGenerator)...,
172✔
133
        )
172✔
134
        if err != nil {
172✔
135
                return nil, errors.Wrap(err, "failed to create router")
×
136
        }
×
137

138
        return app, nil
172✔
139
}
140

141
func (u *UserAdmApiHandlers) AliveHandler(w rest.ResponseWriter, r *rest.Request) {
2✔
142
        w.WriteHeader(http.StatusNoContent)
2✔
143
}
2✔
144

145
func (u *UserAdmApiHandlers) HealthHandler(w rest.ResponseWriter, r *rest.Request) {
4✔
146
        ctx := r.Context()
4✔
147
        l := log.FromContext(ctx)
4✔
148
        ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
4✔
149
        defer cancel()
4✔
150

4✔
151
        err := u.userAdm.HealthCheck(ctx)
4✔
152
        if err != nil {
6✔
153
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusServiceUnavailable)
2✔
154
                return
2✔
155
        }
2✔
156
        w.WriteHeader(http.StatusNoContent)
2✔
157
}
158

159
type LoginOptions struct {
160
        StayLoggedIn bool `json:"stay_logged_in,omitempty"`
161
}
162

163
func (u *UserAdmApiHandlers) AuthLoginHandler(w rest.ResponseWriter, r *rest.Request) {
156✔
164
        ctx := r.Context()
156✔
165

156✔
166
        l := log.FromContext(ctx)
156✔
167

156✔
168
        //parse auth header
156✔
169
        user, pass, ok := r.BasicAuth()
156✔
170
        if !ok {
158✔
171
                rest_utils.RestErrWithLog(w, r, l,
2✔
172
                        ErrAuthHeader, http.StatusUnauthorized)
2✔
173
                return
2✔
174
        }
2✔
175
        email := model.Email(strings.ToLower(user))
154✔
176

154✔
177
        options := &LoginOptions{}
154✔
178
        err := r.DecodeJsonPayload(&options)
154✔
179
        if err != nil && err != rest.ErrJsonPayloadEmpty {
156✔
180
                rest_utils.RestErrWithLogInternal(w, r, l, err)
2✔
181
                return
2✔
182
        }
2✔
183

184
        token, err := u.userAdm.Login(ctx, email, pass, options.StayLoggedIn)
152✔
185
        if err != nil {
170✔
186
                switch {
18✔
187
                case err == useradm.ErrUnauthorized || err == useradm.ErrTenantAccountSuspended:
14✔
188
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusUnauthorized)
14✔
189
                default:
4✔
190
                        rest_utils.RestErrWithLogInternal(w, r, l, err)
4✔
191
                }
192
                return
18✔
193
        }
194

195
        raw, err := u.userAdm.SignToken(ctx, token)
134✔
196
        if err != nil {
136✔
197
                rest_utils.RestErrWithLogInternal(w, r, l, err)
2✔
198
                return
2✔
199
        }
2✔
200

201
        writer := w.(http.ResponseWriter)
132✔
202
        writer.Header().Set("Content-Type", "application/jwt")
132✔
203
        cookie := &http.Cookie{
132✔
204
                Name:     "JWT",
132✔
205
                Value:    raw,
132✔
206
                Path:     uriUIRoot,
132✔
207
                SameSite: http.SameSiteStrictMode,
132✔
208
                Secure:   true,
132✔
209
        }
132✔
210
        if token.ExpiresAt != nil {
262✔
211
                cookie.Expires = token.ExpiresAt.Time
130✔
212
        }
130✔
213
        http.SetCookie(writer, cookie)
132✔
214
        _, _ = writer.Write([]byte(raw))
132✔
215
}
216

217
func (u *UserAdmApiHandlers) AuthLogoutHandler(w rest.ResponseWriter, r *rest.Request) {
6✔
218
        ctx := r.Context()
6✔
219
        l := log.FromContext(ctx)
6✔
220

6✔
221
        if tokenStr, err := authz.ExtractToken(r.Request); err == nil {
12✔
222
                token, err := u.jwth.FromJWT(tokenStr)
6✔
223
                if err != nil {
7✔
224
                        rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
225
                        return
1✔
226
                }
1✔
227
                if err := u.userAdm.Logout(ctx, token); err != nil {
7✔
228
                        rest_utils.RestErrWithLogInternal(w, r, l, err)
2✔
229
                        return
2✔
230
                }
2✔
231
        }
232

233
        w.WriteHeader(http.StatusAccepted)
3✔
234
}
235

236
func (u *UserAdmApiHandlers) AuthVerifyHandler(w rest.ResponseWriter, r *rest.Request) {
134✔
237
        ctx := r.Context()
134✔
238

134✔
239
        l := log.FromContext(ctx)
134✔
240

134✔
241
        // note that the request has passed through authz - the token is valid
134✔
242
        token := authz.GetRequestToken(r.Env)
134✔
243

134✔
244
        err := u.userAdm.Verify(ctx, token)
134✔
245
        if err != nil {
157✔
246
                if err == useradm.ErrUnauthorized {
40✔
247
                        rest_utils.RestErrWithLog(w, r, l, useradm.ErrUnauthorized, http.StatusUnauthorized)
17✔
248
                } else {
23✔
249
                        rest_utils.RestErrWithLogInternal(w, r, l, err)
6✔
250
                }
6✔
251
                return
23✔
252
        }
253

254
        w.WriteHeader(http.StatusOK)
111✔
255
}
256

257
func (u *UserAdmApiHandlers) CreateTenantUserHandler(w rest.ResponseWriter, r *rest.Request) {
29✔
258
        ctx := r.Context()
29✔
259

29✔
260
        l := log.FromContext(ctx)
29✔
261

29✔
262
        user, err := parseUserInternal(r)
29✔
263
        if err != nil {
43✔
264
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
14✔
265
                return
14✔
266
        }
14✔
267

268
        tenantId := r.PathParam("id")
15✔
269
        if tenantId == "" {
17✔
270
                rest_utils.RestErrWithLog(w, r, l, errors.New("Entity not found"), http.StatusNotFound)
2✔
271
                return
2✔
272
        }
2✔
273
        ctx = getTenantContext(ctx, tenantId)
13✔
274
        err = u.userAdm.CreateUserInternal(ctx, user)
13✔
275
        if err != nil {
15✔
276
                if err == store.ErrDuplicateEmail {
4✔
277
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusUnprocessableEntity)
2✔
278
                } else {
2✔
UNCOV
279
                        rest_utils.RestErrWithLogInternal(w, r, l, err)
×
UNCOV
280
                }
×
281
                return
2✔
282
        }
283

284
        w.WriteHeader(http.StatusCreated)
11✔
285

286
}
287

288
func (u *UserAdmApiHandlers) AddUserHandler(w rest.ResponseWriter, r *rest.Request) {
23✔
289
        ctx := r.Context()
23✔
290

23✔
291
        l := log.FromContext(ctx)
23✔
292

23✔
293
        user, err := parseUser(r)
23✔
294
        if err != nil {
35✔
295
                if err == model.ErrPasswordTooShort {
16✔
296
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusUnprocessableEntity)
4✔
297
                } else {
12✔
298
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
8✔
299
                }
8✔
300
                return
12✔
301
        }
302

303
        err = u.userAdm.CreateUser(ctx, user)
11✔
304
        if err != nil {
15✔
305
                if err == store.ErrDuplicateEmail {
8✔
306
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusUnprocessableEntity)
4✔
307
                } else {
4✔
UNCOV
308
                        rest_utils.RestErrWithLogInternal(w, r, l, err)
×
UNCOV
309
                }
×
310
                return
4✔
311
        }
312

313
        w.Header().Add("Location", "users/"+string(user.ID))
7✔
314
        w.WriteHeader(http.StatusCreated)
7✔
315

316
}
317

318
func (u *UserAdmApiHandlers) GetUsersHandler(w rest.ResponseWriter, r *rest.Request) {
359✔
319
        ctx := r.Context()
359✔
320

359✔
321
        l := log.FromContext(ctx)
359✔
322

359✔
323
        if err := r.ParseForm(); err != nil {
363✔
324
                err = errors.Wrap(err, "api: bad form parameters")
4✔
325
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
4✔
326
                return
4✔
327
        }
4✔
328

329
        fltr := model.UserFilter{}
355✔
330
        if err := fltr.ParseForm(r.Form); err != nil {
359✔
331
                err = errors.Wrap(err, "api: invalid form values")
4✔
332
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
4✔
333
                return
4✔
334
        }
4✔
335

336
        users, err := u.userAdm.GetUsers(ctx, fltr)
351✔
337
        if err != nil {
355✔
338
                rest_utils.RestErrWithLogInternal(w, r, l, err)
4✔
339
                return
4✔
340
        }
4✔
341

342
        _ = w.WriteJson(users)
347✔
343
}
344

345
func (u *UserAdmApiHandlers) GetTenantUsersHandler(w rest.ResponseWriter, r *rest.Request) {
10✔
346
        ctx := r.Context()
10✔
347
        ctx = identity.WithContext(ctx, &identity.Identity{
10✔
348
                Tenant: r.PathParam("id"),
10✔
349
        })
10✔
350
        r.Request = r.Request.WithContext(ctx)
10✔
351
        u.GetUsersHandler(w, r)
10✔
352
}
10✔
353

354
func (u *UserAdmApiHandlers) GetUserHandler(w rest.ResponseWriter, r *rest.Request) {
36✔
355
        ctx := r.Context()
36✔
356

36✔
357
        l := log.FromContext(ctx)
36✔
358

36✔
359
        id := r.PathParam("id")
36✔
360
        user, err := u.userAdm.GetUser(ctx, id)
36✔
361
        if err != nil {
38✔
362
                rest_utils.RestErrWithLogInternal(w, r, l, err)
2✔
363
                return
2✔
364
        }
2✔
365

366
        if user == nil {
39✔
367
                rest_utils.RestErrWithLog(w, r, l, ErrUserNotFound, 404)
5✔
368
                return
5✔
369
        }
5✔
370

371
        _ = w.WriteJson(user)
29✔
372
}
373

374
func (u *UserAdmApiHandlers) UpdateUserHandler(w rest.ResponseWriter, r *rest.Request) {
42✔
375
        ctx := r.Context()
42✔
376
        idty := identity.FromContext(ctx)
42✔
377
        l := log.FromContext(ctx)
42✔
378

42✔
379
        userUpdate, err := parseUserUpdate(r)
42✔
380
        if err != nil {
51✔
381
                if err == model.ErrPasswordTooShort {
11✔
382
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusUnprocessableEntity)
2✔
383
                } else {
9✔
384
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
7✔
385
                }
7✔
386
                return
9✔
387
        }
388

389
        // extract the token used to update the user
390
        if tokenStr, err := authz.ExtractToken(r.Request); err == nil {
58✔
391
                token, err := u.jwth.FromJWT(tokenStr)
25✔
392
                if err != nil {
25✔
UNCOV
393
                        rest_utils.RestErrWithLogInternal(w, r, l, err)
×
UNCOV
394
                        return
×
UNCOV
395
                }
×
396
                userUpdate.Token = token
25✔
397
        }
398

399
        id := r.PathParam("id")
33✔
400
        if strings.EqualFold(id, "me") {
46✔
401
                id = idty.Subject
13✔
402
        }
13✔
403
        err = u.userAdm.UpdateUser(ctx, id, userUpdate)
33✔
404
        if err != nil {
47✔
405
                switch err {
14✔
406
                case store.ErrDuplicateEmail,
407
                        useradm.ErrCurrentPasswordMismatch,
408
                        useradm.ErrCannotModifyPassword:
9✔
409
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusUnprocessableEntity)
9✔
410
                case store.ErrUserNotFound:
3✔
411
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusNotFound)
3✔
412
                case useradm.ErrETagMismatch:
2✔
413
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusConflict)
2✔
UNCOV
414
                default:
×
UNCOV
415
                        rest_utils.RestErrWithLogInternal(w, r, l, err)
×
416
                }
417
        }
418

419
        w.WriteHeader(http.StatusNoContent)
33✔
420
}
421

422
func (u *UserAdmApiHandlers) DeleteTenantUserHandler(w rest.ResponseWriter, r *rest.Request) {
6✔
423
        ctx := r.Context()
6✔
424

6✔
425
        tenantId := r.PathParam("id")
6✔
426
        if tenantId != "" {
8✔
427
                ctx = getTenantContext(ctx, tenantId)
2✔
428
        }
2✔
429

430
        l := log.FromContext(ctx)
6✔
431
        err := u.userAdm.DeleteUser(ctx, r.PathParam("userid"))
6✔
432
        if err != nil {
8✔
433
                rest_utils.RestErrWithLogInternal(w, r, l, err)
2✔
434
                return
2✔
435
        }
2✔
436

437
        w.WriteHeader(http.StatusNoContent)
4✔
438
}
439

440
func (u *UserAdmApiHandlers) DeleteUserHandler(w rest.ResponseWriter, r *rest.Request) {
10✔
441
        ctx := r.Context()
10✔
442

10✔
443
        l := log.FromContext(ctx)
10✔
444

10✔
445
        err := u.userAdm.DeleteUser(ctx, r.PathParam("id"))
10✔
446
        if err != nil {
12✔
447
                rest_utils.RestErrWithLogInternal(w, r, l, err)
2✔
448
                return
2✔
449
        }
2✔
450

451
        w.WriteHeader(http.StatusNoContent)
8✔
452
}
453

454
func parseUser(r *rest.Request) (*model.User, error) {
23✔
455
        user := model.User{}
23✔
456

23✔
457
        //decode body
23✔
458
        err := r.DecodeJsonPayload(&user)
23✔
459
        if err != nil {
25✔
460
                return nil, errors.Wrap(err, "failed to decode request body")
2✔
461
        }
2✔
462

463
        if err := user.Validate(); err != nil {
31✔
464
                return nil, err
10✔
465
        }
10✔
466

467
        return &user, nil
11✔
468
}
469

470
func parseUserInternal(r *rest.Request) (*model.UserInternal, error) {
29✔
471
        user := model.UserInternal{}
29✔
472

29✔
473
        //decode body
29✔
474
        err := r.DecodeJsonPayload(&user)
29✔
475
        if err != nil {
31✔
476
                return nil, errors.Wrap(err, "failed to decode request body")
2✔
477
        }
2✔
478

479
        if err := user.Validate(); err != nil {
39✔
480
                return nil, err
12✔
481
        }
12✔
482

483
        return &user, nil
15✔
484
}
485

486
func parseUserUpdate(r *rest.Request) (*model.UserUpdate, error) {
42✔
487
        userUpdate := model.UserUpdate{}
42✔
488

42✔
489
        //decode body
42✔
490
        err := r.DecodeJsonPayload(&userUpdate)
42✔
491
        if err != nil {
44✔
492
                return nil, errors.Wrap(err, "failed to decode request body")
2✔
493
        }
2✔
494

495
        if err := userUpdate.Validate(); err != nil {
47✔
496
                return nil, err
7✔
497
        }
7✔
498

499
        return &userUpdate, nil
33✔
500
}
501

502
func (u *UserAdmApiHandlers) CreateTenantHandler(w rest.ResponseWriter, r *rest.Request) {
12✔
503
        ctx := r.Context()
12✔
504

12✔
505
        l := log.FromContext(ctx)
12✔
506

12✔
507
        var newTenant model.NewTenant
12✔
508

12✔
509
        if err := r.DecodeJsonPayload(&newTenant); err != nil {
14✔
510
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
2✔
511
                return
2✔
512
        }
2✔
513

514
        if err := newTenant.Validate(); err != nil {
13✔
515
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
3✔
516
                return
3✔
517
        }
3✔
518

519
        err := u.userAdm.CreateTenant(ctx, model.NewTenant{
7✔
520
                ID: newTenant.ID,
7✔
521
        })
7✔
522
        if err != nil {
9✔
523
                rest_utils.RestErrWithLogInternal(w, r, l, err)
2✔
524
                return
2✔
525
        }
2✔
526

527
        w.WriteHeader(http.StatusCreated)
5✔
528
}
529

530
func getTenantContext(ctx context.Context, tenantId string) context.Context {
15✔
531
        if ctx == nil {
15✔
UNCOV
532
                ctx = context.Background()
×
UNCOV
533
        }
×
534
        if tenantId != "" {
30✔
535
                id := &identity.Identity{
15✔
536
                        Tenant: tenantId,
15✔
537
                }
15✔
538

15✔
539
                ctx = identity.WithContext(ctx, id)
15✔
540
        }
15✔
541

542
        return ctx
15✔
543
}
544

545
func (u *UserAdmApiHandlers) DeleteTokensHandler(w rest.ResponseWriter, r *rest.Request) {
14✔
546

14✔
547
        ctx := r.Context()
14✔
548

14✔
549
        l := log.FromContext(ctx)
14✔
550

14✔
551
        tenantId := r.URL.Query().Get("tenant_id")
14✔
552
        if tenantId == "" {
18✔
553
                rest_utils.RestErrWithLog(
4✔
554
                        w,
4✔
555
                        r,
4✔
556
                        l,
4✔
557
                        errors.New("tenant_id must be provided"),
4✔
558
                        http.StatusBadRequest,
4✔
559
                )
4✔
560
                return
4✔
561
        }
4✔
562
        userId := r.URL.Query().Get("user_id")
10✔
563

10✔
564
        err := u.userAdm.DeleteTokens(ctx, tenantId, userId)
10✔
565
        switch err {
10✔
566
        case nil:
8✔
567
                w.WriteHeader(http.StatusNoContent)
8✔
568
        default:
2✔
569
                rest_utils.RestErrWithLogInternal(w, r, l, err)
2✔
570
        }
571
}
572

573
func (u *UserAdmApiHandlers) SaveSettingsHandler(w rest.ResponseWriter, r *rest.Request) {
21✔
574
        u.saveSettingsHandler(w, r, false)
21✔
575
}
21✔
576

UNCOV
577
func (u *UserAdmApiHandlers) SaveSettingsMeHandler(w rest.ResponseWriter, r *rest.Request) {
×
UNCOV
578
        u.saveSettingsHandler(w, r, true)
×
UNCOV
579
}
×
580

581
func (u *UserAdmApiHandlers) saveSettingsHandler(w rest.ResponseWriter, r *rest.Request, me bool) {
21✔
582
        ctx := r.Context()
21✔
583

21✔
584
        l := log.FromContext(ctx)
21✔
585

21✔
586
        settings := &model.Settings{}
21✔
587
        err := r.DecodeJsonPayload(settings)
21✔
588
        if err != nil {
26✔
589
                rest_utils.RestErrWithLog(
5✔
590
                        w,
5✔
591
                        r,
5✔
592
                        l,
5✔
593
                        errors.New("cannot parse request body as json"),
5✔
594
                        http.StatusBadRequest,
5✔
595
                )
5✔
596
                return
5✔
597
        }
5✔
598

599
        if err := settings.Validate(); err != nil {
18✔
600
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
2✔
601
                return
2✔
602
        }
2✔
603

604
        ifMatchHeader := r.Header.Get(hdrIfMatch)
14✔
605

14✔
606
        settings.ETag = uuid.NewString()
14✔
607
        if me {
14✔
608
                id := identity.FromContext(ctx)
×
609
                if id == nil {
×
UNCOV
610
                        rest_utils.RestErrWithLogInternal(w, r, l, errors.New("identity not present"))
×
UNCOV
611
                        return
×
UNCOV
612
                }
×
UNCOV
613
                err = u.db.SaveUserSettings(ctx, id.Subject, settings, ifMatchHeader)
×
614
        } else {
14✔
615
                err = u.db.SaveSettings(ctx, settings, ifMatchHeader)
14✔
616
        }
14✔
617
        if err == store.ErrETagMismatch {
16✔
618
                rest_utils.RestErrWithInfoMsg(w, r, l, err, http.StatusPreconditionFailed, err.Error())
2✔
619
                return
2✔
620
        } else if err != nil {
16✔
621
                rest_utils.RestErrWithLogInternal(w, r, l, err)
2✔
622
                return
2✔
623
        }
2✔
624

625
        w.WriteHeader(http.StatusCreated)
10✔
626
}
627

628
func (u *UserAdmApiHandlers) GetSettingsHandler(w rest.ResponseWriter, r *rest.Request) {
15✔
629
        u.getSettingsHandler(w, r, false)
15✔
630
}
15✔
631

UNCOV
632
func (u *UserAdmApiHandlers) GetSettingsMeHandler(w rest.ResponseWriter, r *rest.Request) {
×
UNCOV
633
        u.getSettingsHandler(w, r, true)
×
UNCOV
634
}
×
635

636
func (u *UserAdmApiHandlers) getSettingsHandler(w rest.ResponseWriter, r *rest.Request, me bool) {
15✔
637
        ctx := r.Context()
15✔
638

15✔
639
        l := log.FromContext(ctx)
15✔
640

15✔
641
        var settings *model.Settings
15✔
642
        var err error
15✔
643
        if me {
15✔
644
                id := identity.FromContext(ctx)
×
645
                if id == nil {
×
UNCOV
646
                        rest_utils.RestErrWithLogInternal(w, r, l, errors.New("identity not present"))
×
UNCOV
647
                        return
×
UNCOV
648
                }
×
UNCOV
649
                settings, err = u.db.GetUserSettings(ctx, id.Subject)
×
650
        } else {
15✔
651
                settings, err = u.db.GetSettings(ctx)
15✔
652
        }
15✔
653

654
        if err != nil {
17✔
655
                rest_utils.RestErrWithLogInternal(w, r, l, err)
2✔
656
                return
2✔
657
        } else if settings == nil {
17✔
658
                settings = &model.Settings{
2✔
659
                        Values: model.SettingsValues{},
2✔
660
                }
2✔
661
        }
2✔
662

663
        if settings.ETag != "" {
22✔
664
                w.Header().Set(hdrETag, settings.ETag)
9✔
665
        }
9✔
666
        _ = w.WriteJson(settings)
13✔
667
}
668

669
func (u *UserAdmApiHandlers) IssueTokenHandler(w rest.ResponseWriter, r *rest.Request) {
63✔
670
        ctx := r.Context()
63✔
671

63✔
672
        l := log.FromContext(ctx)
63✔
673

63✔
674
        var tokenRequest model.TokenRequest
63✔
675

63✔
676
        if err := r.DecodeJsonPayload(&tokenRequest); err != nil {
63✔
677
                rest_utils.RestErrWithLog(
×
678
                        w,
×
679
                        r,
×
680
                        l,
×
681
                        errors.New("cannot parse request body as json"),
×
UNCOV
682
                        http.StatusBadRequest,
×
UNCOV
683
                )
×
UNCOV
684
                return
×
UNCOV
685
        }
×
686
        if err := tokenRequest.Validate(u.config.TokenMaxExpSeconds); err != nil {
67✔
687
                rest_utils.RestErrWithLog(
4✔
688
                        w,
4✔
689
                        r,
4✔
690
                        l,
4✔
691
                        err,
4✔
692
                        http.StatusBadRequest,
4✔
693
                )
4✔
694
                return
4✔
695
        }
4✔
696

697
        token, err := u.userAdm.IssuePersonalAccessToken(ctx, &tokenRequest)
59✔
698
        switch err {
59✔
699
        case nil:
46✔
700
                writer := w.(http.ResponseWriter)
46✔
701
                writer.Header().Set("Content-Type", "application/jwt")
46✔
702
                _, _ = writer.Write([]byte(token))
46✔
703
        case useradm.ErrTooManyTokens:
5✔
704
                rest_utils.RestErrWithLog(
5✔
705
                        w,
5✔
706
                        r,
5✔
707
                        l,
5✔
708
                        err,
5✔
709
                        http.StatusUnprocessableEntity,
5✔
710
                )
5✔
711
        case useradm.ErrDuplicateTokenName:
8✔
712
                rest_utils.RestErrWithLog(
8✔
713
                        w,
8✔
714
                        r,
8✔
715
                        l,
8✔
716
                        err,
8✔
717
                        http.StatusConflict,
8✔
718
                )
8✔
UNCOV
719
        default:
×
UNCOV
720
                rest_utils.RestErrWithLogInternal(w, r, l, err)
×
721
        }
722
}
723

724
func (u *UserAdmApiHandlers) GetTokensHandler(w rest.ResponseWriter, r *rest.Request) {
10✔
725
        ctx := r.Context()
10✔
726
        l := log.FromContext(ctx)
10✔
727
        id := identity.FromContext(ctx)
10✔
728
        if id == nil {
10✔
UNCOV
729
                rest_utils.RestErrWithLogInternal(w, r, l, errors.New("identity not present"))
×
UNCOV
730
                return
×
UNCOV
731
        }
×
732

733
        tokens, err := u.userAdm.GetPersonalAccessTokens(ctx, id.Subject)
10✔
734
        if err != nil {
12✔
735
                rest_utils.RestErrWithLogInternal(w, r, l, err)
2✔
736
                return
2✔
737
        }
2✔
738

739
        _ = w.WriteJson(tokens)
8✔
740
}
741

742
func (u *UserAdmApiHandlers) DeleteTokenHandler(w rest.ResponseWriter, r *rest.Request) {
3✔
743
        ctx := r.Context()
3✔
744

3✔
745
        l := log.FromContext(ctx)
3✔
746

3✔
747
        err := u.userAdm.DeleteToken(ctx, r.PathParam("id"))
3✔
748
        if err != nil {
3✔
UNCOV
749
                rest_utils.RestErrWithLogInternal(w, r, l, err)
×
UNCOV
750
                return
×
UNCOV
751
        }
×
752

753
        w.WriteHeader(http.StatusNoContent)
3✔
754
}
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