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

mendersoftware / useradm / 801060822

pending completion
801060822

Pull #354

gitlab-ci

Fabio Tranchitella
feat: add support for returning never-expiring JWT tokens when logging in
Pull Request #354: feat: add support for never expiring PATs

33 of 35 new or added lines in 4 files covered. (94.29%)

85 existing lines in 4 files now uncovered.

2579 of 2883 relevant lines covered (89.46%)

127.77 hits per line

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

91.21
/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
        http.SetCookie(writer, &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
                Expires:  token.ExpiresAt.Time,
132✔
210
        })
132✔
211
        _, _ = writer.Write([]byte(raw))
132✔
212
}
213

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

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

230
        w.WriteHeader(http.StatusAccepted)
3✔
231
}
232

233
func (u *UserAdmApiHandlers) AuthVerifyHandler(w rest.ResponseWriter, r *rest.Request) {
134✔
234
        ctx := r.Context()
134✔
235

134✔
236
        l := log.FromContext(ctx)
134✔
237

134✔
238
        // note that the request has passed through authz - the token is valid
134✔
239
        token := authz.GetRequestToken(r.Env)
134✔
240

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

251
        w.WriteHeader(http.StatusOK)
111✔
252
}
253

254
func (u *UserAdmApiHandlers) CreateTenantUserHandler(w rest.ResponseWriter, r *rest.Request) {
29✔
255
        ctx := r.Context()
29✔
256

29✔
257
        l := log.FromContext(ctx)
29✔
258

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

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

281
        w.WriteHeader(http.StatusCreated)
11✔
282

283
}
284

285
func (u *UserAdmApiHandlers) AddUserHandler(w rest.ResponseWriter, r *rest.Request) {
23✔
286
        ctx := r.Context()
23✔
287

23✔
288
        l := log.FromContext(ctx)
23✔
289

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

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

310
        w.Header().Add("Location", "users/"+string(user.ID))
7✔
311
        w.WriteHeader(http.StatusCreated)
7✔
312

313
}
314

315
func (u *UserAdmApiHandlers) GetUsersHandler(w rest.ResponseWriter, r *rest.Request) {
359✔
316
        ctx := r.Context()
359✔
317

359✔
318
        l := log.FromContext(ctx)
359✔
319

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

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

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

339
        _ = w.WriteJson(users)
347✔
340
}
341

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

351
func (u *UserAdmApiHandlers) GetUserHandler(w rest.ResponseWriter, r *rest.Request) {
36✔
352
        ctx := r.Context()
36✔
353

36✔
354
        l := log.FromContext(ctx)
36✔
355

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

363
        if user == nil {
39✔
364
                rest_utils.RestErrWithLog(w, r, l, ErrUserNotFound, 404)
5✔
365
                return
5✔
366
        }
5✔
367

368
        _ = w.WriteJson(user)
29✔
369
}
370

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

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

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

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

416
        w.WriteHeader(http.StatusNoContent)
33✔
417
}
418

419
func (u *UserAdmApiHandlers) DeleteTenantUserHandler(w rest.ResponseWriter, r *rest.Request) {
6✔
420
        ctx := r.Context()
6✔
421

6✔
422
        tenantId := r.PathParam("id")
6✔
423
        if tenantId != "" {
8✔
424
                ctx = getTenantContext(ctx, tenantId)
2✔
425
        }
2✔
426

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

434
        w.WriteHeader(http.StatusNoContent)
4✔
435
}
436

437
func (u *UserAdmApiHandlers) DeleteUserHandler(w rest.ResponseWriter, r *rest.Request) {
10✔
438
        ctx := r.Context()
10✔
439

10✔
440
        l := log.FromContext(ctx)
10✔
441

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

448
        w.WriteHeader(http.StatusNoContent)
8✔
449
}
450

451
func parseUser(r *rest.Request) (*model.User, error) {
23✔
452
        user := model.User{}
23✔
453

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

460
        if err := user.Validate(); err != nil {
31✔
461
                return nil, err
10✔
462
        }
10✔
463

464
        return &user, nil
11✔
465
}
466

467
func parseUserInternal(r *rest.Request) (*model.UserInternal, error) {
29✔
468
        user := model.UserInternal{}
29✔
469

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

476
        if err := user.Validate(); err != nil {
39✔
477
                return nil, err
12✔
478
        }
12✔
479

480
        return &user, nil
15✔
481
}
482

483
func parseUserUpdate(r *rest.Request) (*model.UserUpdate, error) {
42✔
484
        userUpdate := model.UserUpdate{}
42✔
485

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

492
        if err := userUpdate.Validate(); err != nil {
47✔
493
                return nil, err
7✔
494
        }
7✔
495

496
        return &userUpdate, nil
33✔
497
}
498

499
func (u *UserAdmApiHandlers) CreateTenantHandler(w rest.ResponseWriter, r *rest.Request) {
12✔
500
        ctx := r.Context()
12✔
501

12✔
502
        l := log.FromContext(ctx)
12✔
503

12✔
504
        var newTenant model.NewTenant
12✔
505

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

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

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

524
        w.WriteHeader(http.StatusCreated)
5✔
525
}
526

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

15✔
536
                ctx = identity.WithContext(ctx, id)
15✔
537
        }
15✔
538

539
        return ctx
15✔
540
}
541

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

14✔
544
        ctx := r.Context()
14✔
545

14✔
546
        l := log.FromContext(ctx)
14✔
547

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

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

570
func (u *UserAdmApiHandlers) SaveSettingsHandler(w rest.ResponseWriter, r *rest.Request) {
21✔
571
        u.saveSettingsHandler(w, r, false)
21✔
572
}
21✔
573

574
func (u *UserAdmApiHandlers) SaveSettingsMeHandler(w rest.ResponseWriter, r *rest.Request) {
×
575
        u.saveSettingsHandler(w, r, true)
×
UNCOV
576
}
×
577

578
func (u *UserAdmApiHandlers) saveSettingsHandler(w rest.ResponseWriter, r *rest.Request, me bool) {
21✔
579
        ctx := r.Context()
21✔
580

21✔
581
        l := log.FromContext(ctx)
21✔
582

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

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

601
        ifMatchHeader := r.Header.Get(hdrIfMatch)
14✔
602

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

622
        w.WriteHeader(http.StatusCreated)
10✔
623
}
624

625
func (u *UserAdmApiHandlers) GetSettingsHandler(w rest.ResponseWriter, r *rest.Request) {
15✔
626
        u.getSettingsHandler(w, r, false)
15✔
627
}
15✔
628

629
func (u *UserAdmApiHandlers) GetSettingsMeHandler(w rest.ResponseWriter, r *rest.Request) {
×
630
        u.getSettingsHandler(w, r, true)
×
UNCOV
631
}
×
632

633
func (u *UserAdmApiHandlers) getSettingsHandler(w rest.ResponseWriter, r *rest.Request, me bool) {
15✔
634
        ctx := r.Context()
15✔
635

15✔
636
        l := log.FromContext(ctx)
15✔
637

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

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

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

666
func (u *UserAdmApiHandlers) IssueTokenHandler(w rest.ResponseWriter, r *rest.Request) {
63✔
667
        ctx := r.Context()
63✔
668

63✔
669
        l := log.FromContext(ctx)
63✔
670

63✔
671
        var tokenRequest model.TokenRequest
63✔
672

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

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

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

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

736
        _ = w.WriteJson(tokens)
8✔
737
}
738

739
func (u *UserAdmApiHandlers) DeleteTokenHandler(w rest.ResponseWriter, r *rest.Request) {
3✔
740
        ctx := r.Context()
3✔
741

3✔
742
        l := log.FromContext(ctx)
3✔
743

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

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