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

mendersoftware / useradm / 800725457

pending completion
800725457

Pull #354

gitlab-ci

Fabio Tranchitella
feat: add support for never expiring PATs
Pull Request #354: feat: add support for never expiring PATs

16 of 19 new or added lines in 3 files covered. (84.21%)

1 existing line in 1 file now uncovered.

2573 of 2879 relevant lines covered (89.37%)

127.62 hits per line

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

93.15
/user/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

15
package useradm
16

17
import (
18
        "context"
19
        "time"
20

21
        "github.com/mendersoftware/go-lib-micro/apiclient"
22
        "github.com/mendersoftware/go-lib-micro/identity"
23
        "github.com/mendersoftware/go-lib-micro/log"
24
        "github.com/mendersoftware/go-lib-micro/mongo/oid"
25
        "github.com/pkg/errors"
26
        "golang.org/x/crypto/bcrypt"
27

28
        "github.com/mendersoftware/useradm/client/tenant"
29
        "github.com/mendersoftware/useradm/jwt"
30
        "github.com/mendersoftware/useradm/model"
31
        "github.com/mendersoftware/useradm/scope"
32
        "github.com/mendersoftware/useradm/store"
33
)
34

35
var (
36
        ErrUserNotFound           = store.ErrUserNotFound
37
        ErrDuplicateEmail         = store.ErrDuplicateEmail
38
        ErrETagMismatch           = errors.New("entity tag did not match any records")
39
        ErrUnauthorized           = errors.New("unauthorized")
40
        ErrAuthExpired            = errors.New("token expired")
41
        ErrAuthInvalid            = errors.New("token is invalid")
42
        ErrTenantAccountSuspended = errors.New("tenant account suspended")
43
        ErrInvalidTenantID        = errors.New("invalid tenant id")
44
        ErrTooManyTokens          = errors.New(
45
                "maximum number of personal acess tokens reached for this user")
46
        ErrDuplicateTokenName = errors.New(
47
                "Personal Access Token with a given name already exists")
48
        // password mismatch
49
        ErrCurrentPasswordMismatch = errors.New("current password mismatch")
50
        // modification of other user's password is not allowed
51
        ErrCannotModifyPassword = errors.New("password cannot be modified")
52
)
53

54
const (
55
        TenantStatusSuspended = "suspended"
56
        userIdMe              = "me"
57
)
58

59
//go:generate ../utils/mockgen.sh
60
type App interface {
61
        HealthCheck(ctx context.Context) error
62
        // Login accepts email/password, returns JWT
63
        Login(ctx context.Context, email model.Email, pass string) (*jwt.Token, error)
64
        Logout(ctx context.Context, token *jwt.Token) error
65
        CreateUser(ctx context.Context, u *model.User) error
66
        CreateUserInternal(ctx context.Context, u *model.UserInternal) error
67
        UpdateUser(ctx context.Context, id string, u *model.UserUpdate) error
68
        Verify(ctx context.Context, token *jwt.Token) error
69
        GetUsers(ctx context.Context, fltr model.UserFilter) ([]model.User, error)
70
        GetUser(ctx context.Context, id string) (*model.User, error)
71
        DeleteUser(ctx context.Context, id string) error
72
        SetPassword(ctx context.Context, u model.UserUpdate) error
73

74
        // SignToken generates a signed
75
        // token using configuration & method set up in UserAdmApp
76
        SignToken(ctx context.Context, t *jwt.Token) (string, error)
77
        DeleteToken(ctx context.Context, id string) error
78

79
        // IssuePersonalAccessToken issues Personal Access Token
80
        IssuePersonalAccessToken(ctx context.Context, tr *model.TokenRequest) (string, error)
81
        // GetPersonalAccessTokens returns list of Personal Access Tokens
82
        GetPersonalAccessTokens(ctx context.Context, userID string) ([]model.PersonalAccessToken, error)
83

84
        DeleteTokens(ctx context.Context, tenantId, userId string) error
85

86
        CreateTenant(ctx context.Context, tenant model.NewTenant) error
87
}
88

89
type Config struct {
90
        // token issuer
91
        Issuer string
92
        // token expiration time
93
        ExpirationTime int64
94
        // maximum number of personal access tokens per user
95
        // zero means no limit
96
        LimitTokensPerUser int
97
        // how often we should update personal access token
98
        // with last used timestamp
99
        TokenLastUsedUpdateFreqMinutes int
100
}
101

102
type ApiClientGetter func() apiclient.HttpRunner
103

104
func simpleApiClientGetter() apiclient.HttpRunner {
503✔
105
        return &apiclient.HttpApi{}
503✔
106
}
503✔
107

108
type UserAdm struct {
109
        // JWT serialized/deserializer
110
        jwtHandler   jwt.Handler
111
        db           store.DataStore
112
        config       Config
113
        verifyTenant bool
114
        cTenant      tenant.ClientRunner
115
        clientGetter ApiClientGetter
116
}
117

118
func NewUserAdm(jwtHandler jwt.Handler, db store.DataStore, config Config) *UserAdm {
585✔
119

585✔
120
        return &UserAdm{
585✔
121
                jwtHandler:   jwtHandler,
585✔
122
                db:           db,
585✔
123
                config:       config,
585✔
124
                clientGetter: simpleApiClientGetter,
585✔
125
        }
585✔
126
}
585✔
127

128
func (u *UserAdm) HealthCheck(ctx context.Context) error {
8✔
129
        err := u.db.Ping(ctx)
8✔
130
        if err != nil {
10✔
131
                return errors.Wrap(err, "error reaching MongoDB")
2✔
132
        }
2✔
133

134
        if u.verifyTenant {
10✔
135
                err = u.cTenant.CheckHealth(ctx)
4✔
136
                if err != nil {
6✔
137
                        return errors.Wrap(err, "Tenantadm service unhealthy")
2✔
138
                }
2✔
139
        }
140

141
        return nil
4✔
142
}
143

144
func (u *UserAdm) Login(ctx context.Context, email model.Email, pass string) (*jwt.Token, error) {
156✔
145
        var ident identity.Identity
156✔
146
        l := log.FromContext(ctx)
156✔
147

156✔
148
        if email == "" {
156✔
149
                return nil, ErrUnauthorized
×
150
        }
×
151

152
        if u.verifyTenant {
271✔
153
                // check the user's tenant
115✔
154
                tenant, err := u.cTenant.GetTenant(ctx, string(email), u.clientGetter())
115✔
155

115✔
156
                if err != nil {
117✔
157
                        return nil, errors.Wrap(err, "failed to check user's tenant")
2✔
158
                }
2✔
159

160
                if tenant == nil {
118✔
161
                        return nil, ErrUnauthorized
5✔
162
                }
5✔
163

164
                if tenant.Status == TenantStatusSuspended {
112✔
165
                        return nil, ErrTenantAccountSuspended
4✔
166
                }
4✔
167

168
                ident.Tenant = tenant.ID
104✔
169
                ctx = identity.WithContext(ctx, &ident)
104✔
170
        }
171

172
        //get user
173
        user, err := u.db.GetUserByEmail(ctx, email)
145✔
174

145✔
175
        if user == nil && err == nil {
148✔
176
                return nil, ErrUnauthorized
3✔
177
        }
3✔
178

179
        if err != nil {
144✔
180
                return nil, errors.Wrap(err, "useradm: failed to get user")
2✔
181
        }
2✔
182

183
        //verify password
184
        err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(pass))
140✔
185
        if err != nil {
146✔
186
                return nil, ErrUnauthorized
6✔
187
        }
6✔
188

189
        //generate and save token
190
        t, err := u.generateToken(user.ID, scope.All, ident.Tenant)
134✔
191
        if err != nil {
134✔
192
                return nil, errors.Wrap(err, "useradm: failed to generate token")
×
193
        }
×
194

195
        err = u.db.SaveToken(ctx, t)
134✔
196
        if err != nil {
136✔
197
                return nil, errors.Wrap(err, "useradm: failed to save token")
2✔
198
        }
2✔
199

200
        if err = u.db.UpdateLoginTs(ctx, user.ID); err != nil {
134✔
201
                l.Warnf("failed to update login timestamp: %s", err.Error())
2✔
202
        }
2✔
203

204
        return t, nil
132✔
205
}
206

207
func (u *UserAdm) generateToken(subject, scope, tenant string) (*jwt.Token, error) {
190✔
208
        id := oid.NewUUIDv4()
190✔
209
        subjectID := oid.FromString(subject)
190✔
210
        now := jwt.Time{Time: time.Now()}
190✔
211
        ret := &jwt.Token{Claims: jwt.Claims{
190✔
212
                ID:        id,
190✔
213
                Subject:   subjectID,
190✔
214
                Issuer:    u.config.Issuer,
190✔
215
                IssuedAt:  now,
190✔
216
                NotBefore: now,
190✔
217
                ExpiresAt: &jwt.Time{
190✔
218
                        Time: now.Add(time.Second *
190✔
219
                                time.Duration(u.config.ExpirationTime)),
190✔
220
                },
190✔
221
                Tenant: tenant,
190✔
222
                Scope:  scope,
190✔
223
                User:   true,
190✔
224
        }}
190✔
225
        return ret, ret.Claims.Valid()
190✔
226
}
190✔
227

228
func (u *UserAdm) SignToken(ctx context.Context, t *jwt.Token) (string, error) {
132✔
229
        return u.jwtHandler.ToJWT(t)
132✔
230
}
132✔
231

232
func (u *UserAdm) Logout(ctx context.Context, token *jwt.Token) error {
5✔
233
        return u.db.DeleteToken(ctx, token.Subject, token.ID)
5✔
234
}
5✔
235

236
func (ua *UserAdm) CreateUser(ctx context.Context, u *model.User) error {
432✔
237
        hash, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost)
432✔
238
        if err != nil {
432✔
239
                return errors.Wrap(err, "failed to generate password hash")
×
240
        }
×
241
        u.Password = string(hash)
432✔
242

432✔
243
        return ua.doCreateUser(ctx, u, true)
432✔
244
}
245

246
func (ua *UserAdm) CreateUserInternal(ctx context.Context, u *model.UserInternal) error {
3✔
247
        if u.PasswordHash != "" {
4✔
248
                u.Password = u.PasswordHash
1✔
249
        } else {
3✔
250
                hash, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost)
2✔
251
                if err != nil {
2✔
252
                        return errors.Wrap(err, "failed to generate password hash")
×
253
                }
×
254
                u.Password = string(hash)
2✔
255
        }
256

257
        return ua.doCreateUser(ctx, &u.User, u.ShouldPropagate())
3✔
258
}
259

260
func (ua *UserAdm) doCreateUser(ctx context.Context, u *model.User, propagate bool) error {
461✔
261
        var tenantErr error
461✔
262

461✔
263
        if u.ID == "" {
921✔
264
                //technically one of the functions down the oid.NewUUIDv4() stack can panic,
460✔
265
                //so what we could do is: wrap around it and recover and fall back
460✔
266
                //to non-rand based generation (NewUUIDv5(u.Email)).
460✔
267
                //on the other hand if rand failed maybe it is a good idea t not continue
460✔
268
                //after all.
460✔
269
                //the previous call to NewUUIDv5 produced exact the same ids for same
460✔
270
                //emails, which leads to problems once user changes the email address,
460✔
271
                //and then the old email is used in the new user creation.
460✔
272
                id := oid.NewUUIDv4()
460✔
273
                u.ID = id.String()
460✔
274
        }
460✔
275

276
        id := identity.FromContext(ctx)
461✔
277
        if ua.verifyTenant && propagate {
817✔
278
                tenantErr = ua.cTenant.CreateUser(ctx,
356✔
279
                        &tenant.User{
356✔
280
                                ID:       u.ID,
356✔
281
                                Name:     string(u.Email),
356✔
282
                                TenantID: id.Tenant,
356✔
283
                        },
356✔
284
                        ua.clientGetter())
356✔
285

356✔
286
                if tenantErr != nil && tenantErr != tenant.ErrDuplicateUser {
358✔
287
                        return errors.Wrap(tenantErr, "useradm: failed to create user in tenantadm")
2✔
288
                }
2✔
289
        }
290

291
        if tenantErr == tenant.ErrDuplicateUser {
469✔
292
                // check if the user exists in useradm
10✔
293
                // if the user does not exists then we should try to remove the user from tenantadm
10✔
294
                user, err := ua.db.GetUserByEmail(ctx, u.Email)
10✔
295
                if err != nil {
12✔
296
                        return errors.Wrap(err, "tenant data out of sync: failed to get user from db")
2✔
297
                }
2✔
298
                if user == nil {
12✔
299
                        if compensateErr := ua.compensateTenantUser(
4✔
300
                                ctx,
4✔
301
                                u.ID,
4✔
302
                                id.Tenant,
4✔
303
                        ); compensateErr != nil {
6✔
304
                                tenantErr = compensateErr
2✔
305
                        }
2✔
306
                        return errors.Wrap(tenantErr, "tenant data out of sync")
4✔
307
                }
308
                return store.ErrDuplicateEmail
4✔
309
        }
310

311
        if err := ua.db.CreateUser(ctx, u); err != nil {
459✔
312
                if err == store.ErrDuplicateEmail {
14✔
313
                        return err
4✔
314
                }
4✔
315
                if ua.verifyTenant && propagate {
10✔
316
                        // if the user could not be created in the useradm database
4✔
317
                        // try to remove the user from tenantadm
4✔
318
                        if compensateErr := ua.compensateTenantUser(
4✔
319
                                ctx,
4✔
320
                                u.ID,
4✔
321
                                id.Tenant,
4✔
322
                        ); compensateErr != nil {
6✔
323
                                err = errors.Wrap(err, compensateErr.Error())
2✔
324
                        }
2✔
325
                }
326

327
                return errors.Wrap(err, "useradm: failed to create user in the db")
6✔
328
        }
329

330
        return nil
439✔
331
}
332

333
func (ua *UserAdm) compensateTenantUser(ctx context.Context, userId, tenantId string) error {
8✔
334
        err := ua.cTenant.DeleteUser(ctx, tenantId, userId, ua.clientGetter())
8✔
335

8✔
336
        if err != nil {
12✔
337
                return errors.Wrap(err, "faield to delete tenant user")
4✔
338
        }
4✔
339
        return nil
4✔
340
}
341

342
func (ua *UserAdm) validateUserUpdate(
343
        ctx context.Context,
344
        user *model.User,
345
        u *model.UserUpdate,
346
        me bool,
347
) error {
48✔
348
        // user can change own password only
48✔
349
        if !me {
61✔
350
                if len(u.Password) > 0 {
18✔
351
                        return ErrCannotModifyPassword
5✔
352
                }
5✔
353
        } else {
35✔
354
                // when changing own password or email address
35✔
355
                // user has to provide current password
35✔
356
                if len(u.Password) > 0 || (u.Email != "" && u.Email != user.Email) {
68✔
357
                        if err := bcrypt.CompareHashAndPassword(
33✔
358
                                []byte(user.Password),
33✔
359
                                []byte(u.CurrentPassword),
33✔
360
                        ); err != nil {
41✔
361
                                return ErrCurrentPasswordMismatch
8✔
362
                        }
8✔
363
                }
364
        }
365
        return nil
35✔
366
}
367

368
func (ua *UserAdm) deleteAndInvalidateUserTokens(
369
        ctx context.Context,
370
        userID string,
371
        token *jwt.Token,
372
) error {
21✔
373
        var err error
21✔
374
        if token != nil {
38✔
375
                err = ua.db.DeleteTokensByUserIdExceptCurrentOne(ctx, userID, token.ID)
17✔
376
        } else {
21✔
377
                err = ua.db.DeleteTokensByUserId(ctx, userID)
4✔
378
        }
4✔
379
        return err
21✔
380
}
381

382
func (ua *UserAdm) UpdateUser(ctx context.Context, id string, userUpdate *model.UserUpdate) error {
55✔
383
        idty := identity.FromContext(ctx)
55✔
384
        me := idty.Subject == id
55✔
385
        user, err := ua.db.GetUserAndPasswordById(ctx, id)
55✔
386
        if err != nil {
57✔
387
                return errors.Wrap(err, "useradm: failed to get user")
2✔
388
        } else if user == nil {
60✔
389
                return store.ErrUserNotFound
5✔
390
        }
5✔
391

392
        if err := ua.validateUserUpdate(ctx, user, userUpdate, me); err != nil {
61✔
393
                return err
13✔
394
        }
13✔
395

396
        if userUpdate.ETag == nil {
66✔
397
                // Update without the support for etags.
31✔
398
                next := user.NextETag()
31✔
399
                userUpdate.ETagUpdate = &next
31✔
400
                userUpdate.ETag = user.ETag
31✔
401
                if user.ETag == nil {
62✔
402
                        // If the ETag field is not set, assign the etag to nil
31✔
403
                        user.ETag = &model.ETagNil
31✔
404
                }
31✔
405
        } else if user.ETag == nil || *userUpdate.ETag != *user.ETag {
6✔
406
                return ErrETagMismatch
2✔
407
        }
2✔
408

409
        if len(userUpdate.Email) > 0 && userUpdate.Email != user.Email {
61✔
410
                if ua.verifyTenant {
46✔
411
                        err := ua.cTenant.UpdateUser(ctx,
18✔
412
                                idty.Tenant,
18✔
413
                                id,
18✔
414
                                &tenant.UserUpdate{
18✔
415
                                        Name: string(userUpdate.Email),
18✔
416
                                },
18✔
417
                                ua.clientGetter())
18✔
418

18✔
419
                        switch err {
18✔
420
                        case nil:
12✔
421
                                break
12✔
422
                        case tenant.ErrDuplicateUser:
2✔
423
                                return store.ErrDuplicateEmail
2✔
424
                        case tenant.ErrUserNotFound:
2✔
425
                                return store.ErrUserNotFound
2✔
426
                        default:
2✔
427
                                return errors.Wrap(err, "useradm: failed to update user in tenantadm")
2✔
428
                        }
429
                }
430
        }
431

432
        _, err = ua.db.UpdateUser(ctx, id, userUpdate)
27✔
433
        switch err {
27✔
434
        case nil:
21✔
435
                // invalidate the JWT tokens but the one used to update the user
21✔
436
                err = ua.deleteAndInvalidateUserTokens(ctx, id, userUpdate.Token)
21✔
437
                err = errors.Wrap(err, "useradm: failed to invalidate tokens")
21✔
438

439
        case store.ErrUserNotFound:
2✔
440
                // We matched the user earlier, the ETag must have changed in
2✔
441
                // the meantime
2✔
442
                err = ErrETagMismatch
2✔
443
        case store.ErrDuplicateEmail:
2✔
444
                break
2✔
445

446
        default:
2✔
447
                err = errors.Wrap(err, "useradm: failed to update user information")
2✔
448
        }
449

450
        return err
27✔
451
}
452

453
func (ua *UserAdm) Verify(ctx context.Context, token *jwt.Token) error {
130✔
454

130✔
455
        if token == nil {
130✔
456
                return ErrUnauthorized
×
457
        }
×
458

459
        l := log.FromContext(ctx)
130✔
460

130✔
461
        if !token.Claims.User {
132✔
462
                l.Errorf("not a user token")
2✔
463
                return ErrUnauthorized
2✔
464
        }
2✔
465

466
        if ua.verifyTenant {
232✔
467
                if token.Claims.Tenant == "" {
104✔
468
                        l.Errorf("Token has no tenant claim")
×
469
                        return jwt.ErrTokenInvalid
×
470
                }
×
471
        } else if token.Claims.Tenant != "" {
24✔
472
                l.Errorf("Unexpected tenant claim: %s in the token", token.Claims.Tenant)
×
473
                return jwt.ErrTokenInvalid
×
474
        }
×
475

476
        user, err := ua.db.GetUserById(ctx, token.Claims.Subject.String())
128✔
477
        if user == nil && err == nil {
132✔
478
                return ErrUnauthorized
4✔
479
        }
4✔
480
        if err != nil {
126✔
481
                return errors.Wrap(err, "useradm: failed to get user")
2✔
482
        }
2✔
483

484
        dbToken, err := ua.db.GetTokenById(ctx, token.ID)
122✔
485
        if dbToken == nil && err == nil {
135✔
486
                return ErrUnauthorized
13✔
487
        }
13✔
488
        if err != nil {
111✔
489
                return errors.Wrap(err, "useradm: failed to get token")
2✔
490
        }
2✔
491

492
        // in case the token is a personal access token, update last used timestam
493
        // to not overload the database with writes to tokens collection, we do not
494
        // update the timestamp every time, but instead we wait some configurable
495
        // amount of time between updates
496
        if dbToken.TokenName != nil && ua.config.TokenLastUsedUpdateFreqMinutes > 0 {
110✔
497
                t := time.Now().Add(
3✔
498
                        (-time.Minute * time.Duration(ua.config.TokenLastUsedUpdateFreqMinutes)))
3✔
499
                if dbToken.LastUsed == nil || dbToken.LastUsed.Before(t) {
6✔
500
                        if err := ua.db.UpdateTokenLastUsed(ctx, token.ID); err != nil {
3✔
501
                                return err
×
502
                        }
×
503
                }
504
        }
505

506
        return nil
107✔
507
}
508

509
func (ua *UserAdm) GetUsers(ctx context.Context, fltr model.UserFilter) ([]model.User, error) {
345✔
510
        users, err := ua.db.GetUsers(ctx, fltr)
345✔
511
        if err != nil {
347✔
512
                return nil, errors.Wrap(err, "useradm: failed to get users")
2✔
513
        }
2✔
514

515
        return users, nil
343✔
516
}
517

518
func (ua *UserAdm) GetUser(ctx context.Context, id string) (*model.User, error) {
36✔
519
        if id == userIdMe {
45✔
520
                id = identity.FromContext(ctx).Subject
9✔
521
        }
9✔
522
        user, err := ua.db.GetUserById(ctx, id)
36✔
523
        if err != nil {
38✔
524
                return nil, errors.Wrap(err, "useradm: failed to get user")
2✔
525
        }
2✔
526

527
        return user, nil
34✔
528
}
529

530
func (ua *UserAdm) DeleteUser(ctx context.Context, id string) error {
16✔
531
        if ua.verifyTenant {
24✔
532
                identity := identity.FromContext(ctx)
8✔
533
                err := ua.cTenant.DeleteUser(ctx, identity.Tenant, id, ua.clientGetter())
8✔
534

8✔
535
                if err != nil {
10✔
536
                        return errors.Wrap(err, "useradm: failed to delete user in tenantadm")
2✔
537
                }
2✔
538
        }
539

540
        err := ua.db.DeleteUser(ctx, id)
14✔
541
        if err != nil {
16✔
542
                return errors.Wrap(err, "useradm: failed to delete user")
2✔
543
        }
2✔
544

545
        // remove user tokens
546
        err = ua.db.DeleteTokensByUserId(ctx, id)
12✔
547
        if err != nil {
14✔
548
                return errors.Wrap(err, "useradm: failed to delete user tokens")
2✔
549
        }
2✔
550

551
        return nil
10✔
552
}
553

554
// WithTenantVerification produces a UserAdm instance which enforces
555
// tenant verification vs the tenantadm service upon /login.
556
func (u *UserAdm) WithTenantVerification(c tenant.ClientRunner) *UserAdm {
380✔
557
        u.verifyTenant = true
380✔
558
        u.cTenant = c
380✔
559
        return u
380✔
560
}
380✔
561

562
func (u *UserAdm) CreateTenant(ctx context.Context, tenant model.NewTenant) error {
5✔
563
        return nil
5✔
564
}
5✔
565

566
func (ua *UserAdm) SetPassword(ctx context.Context, uu model.UserUpdate) error {
12✔
567
        u, err := ua.db.GetUserByEmail(ctx, uu.Email)
12✔
568
        if err != nil {
14✔
569
                return errors.Wrap(err, "useradm: failed to get user by email")
2✔
570

2✔
571
        }
2✔
572
        if u == nil {
12✔
573
                return ErrUserNotFound
2✔
574
        }
2✔
575

576
        _, err = ua.db.UpdateUser(ctx, u.ID, &uu)
8✔
577

8✔
578
        // if we changed the password, invalidate the JWT tokens but the one used to update the user
8✔
579
        if err == nil && uu.Password != "" {
14✔
580
                if uu.Token != nil {
8✔
581
                        err = ua.db.DeleteTokensByUserIdExceptCurrentOne(ctx, u.ID, uu.Token.ID)
2✔
582
                } else {
6✔
583
                        err = ua.db.DeleteTokensByUserId(ctx, u.ID)
4✔
584
                }
4✔
585
        }
586
        if err != nil {
10✔
587
                return errors.Wrap(err, "useradm: failed to update user information")
2✔
588
        }
2✔
589

590
        return nil
6✔
591
}
592

593
func (ua *UserAdm) DeleteTokens(ctx context.Context, tenantId, userId string) error {
12✔
594
        ctx = identity.WithContext(ctx, &identity.Identity{
12✔
595
                Tenant: tenantId,
12✔
596
        })
12✔
597

12✔
598
        var err error
12✔
599

12✔
600
        if userId != "" {
18✔
601
                err = ua.db.DeleteTokensByUserId(ctx, userId)
6✔
602
        } else {
12✔
603
                err = ua.db.DeleteTokens(ctx)
6✔
604
        }
6✔
605

606
        if err != nil && err != store.ErrTokenNotFound {
14✔
607
                return errors.Wrapf(
2✔
608
                        err,
2✔
609
                        "failed to delete tokens for tenant: %v, user id: %v",
2✔
610
                        tenantId,
2✔
611
                        userId,
2✔
612
                )
2✔
613
        }
2✔
614

615
        return nil
10✔
616
}
617

618
func (u *UserAdm) IssuePersonalAccessToken(
619
        ctx context.Context,
620
        tr *model.TokenRequest,
621
) (string, error) {
63✔
622
        id := identity.FromContext(ctx)
63✔
623
        if id == nil {
63✔
624
                return "", errors.New("identity not present in the context")
×
625
        }
×
626
        if u.config.LimitTokensPerUser > 0 {
124✔
627
                count, err := u.db.CountPersonalAccessTokens(ctx, id.Subject)
61✔
628
                if err != nil {
63✔
629
                        return "", errors.Wrap(err, "useradm: failed to count personal access tokens")
2✔
630
                }
2✔
631
                if count >= int64(u.config.LimitTokensPerUser) {
64✔
632
                        return "", ErrTooManyTokens
5✔
633
                }
5✔
634
        }
635
        //generate and save token
636
        t, err := u.generateToken(id.Subject, scope.All, id.Tenant)
56✔
637
        if err != nil {
56✔
638
                return "", errors.Wrap(err, "useradm: failed to generate token")
×
639
        }
×
640
        // update claims
641
        t.TokenName = tr.Name
56✔
642
        if tr.ExpiresIn > 0 {
112✔
643
                expires := jwt.Time{
56✔
644
                        Time: time.Now().Add(time.Second * time.Duration(tr.ExpiresIn)),
56✔
645
                }
56✔
646
                t.ExpiresAt = &expires
56✔
647
        } else {
56✔
NEW
648
                t.ExpiresAt = nil
×
UNCOV
649
        }
×
650

651
        err = u.db.SaveToken(ctx, t)
56✔
652
        if err == store.ErrDuplicateTokenName {
64✔
653
                return "", ErrDuplicateTokenName
8✔
654
        } else if err != nil {
58✔
655
                return "", errors.Wrap(err, "useradm: failed to save token")
2✔
656
        }
2✔
657

658
        // sign token
659
        return u.jwtHandler.ToJWT(t)
46✔
660
}
661

662
func (ua *UserAdm) GetPersonalAccessTokens(
663
        ctx context.Context,
664
        userID string,
665
) ([]model.PersonalAccessToken, error) {
6✔
666
        tokens, err := ua.db.GetPersonalAccessTokens(ctx, userID)
6✔
667
        if err != nil {
6✔
668
                return nil, errors.Wrap(err, "useradm: failed to get tokens")
×
669
        }
×
670

671
        return tokens, nil
6✔
672
}
673

674
func (ua *UserAdm) DeleteToken(ctx context.Context, id string) error {
3✔
675
        identity := identity.FromContext(ctx)
3✔
676
        if identity == nil {
3✔
677
                return errors.New("identity not present in the context")
×
678
        }
×
679
        err := ua.db.DeleteToken(ctx, oid.FromString(identity.Subject), oid.FromString(id))
3✔
680
        if err != nil {
3✔
681
                return errors.Wrap(err, "useradm: failed to delete token")
×
682
        }
×
683

684
        return nil
3✔
685
}
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