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

mendersoftware / useradm / 800583681

pending completion
800583681

Pull #354

gitlab-ci

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

66 of 71 new or added lines in 6 files covered. (92.96%)

117 existing lines in 5 files now uncovered.

2571 of 2875 relevant lines covered (89.43%)

127.6 hits per line

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

93.61
/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
var endOfCentury, _ = time.Parse(time.RFC3339, "2099-12-31T23:59:59Z")
60

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

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

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

86
        DeleteTokens(ctx context.Context, tenantId, userId string) error
87

88
        CreateTenant(ctx context.Context, tenant model.NewTenant) error
89
}
90

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

104
type ApiClientGetter func() apiclient.HttpRunner
105

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

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

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

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

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

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

143
        return nil
4✔
144
}
145

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

156✔
150
        if email == "" {
156✔
UNCOV
151
                return nil, ErrUnauthorized
×
UNCOV
152
        }
×
153

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

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

162
                if tenant == nil {
118✔
163
                        return nil, ErrUnauthorized
5✔
164
                }
5✔
165

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

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

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

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

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

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

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

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

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

206
        return t, nil
132✔
207
}
208

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

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

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

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

432✔
245
        return ua.doCreateUser(ctx, u, true)
432✔
246
}
247

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

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

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

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

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

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

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

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

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

332
        return nil
439✔
333
}
334

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

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

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

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

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

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

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

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

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

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

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

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

452
        return err
27✔
453
}
454

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

130✔
457
        if token == nil {
130✔
UNCOV
458
                return ErrUnauthorized
×
UNCOV
459
        }
×
460

461
        l := log.FromContext(ctx)
130✔
462

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

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

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

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

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

508
        return nil
107✔
509
}
510

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

517
        return users, nil
343✔
518
}
519

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

529
        return user, nil
34✔
530
}
531

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

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

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

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

553
        return nil
10✔
554
}
555

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

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

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

2✔
573
        }
2✔
574
        if u == nil {
12✔
575
                return ErrUserNotFound
2✔
576
        }
2✔
577

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

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

592
        return nil
6✔
593
}
594

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

12✔
600
        var err error
12✔
601

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

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

617
        return nil
10✔
618
}
619

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

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

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

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

672
        return tokens, nil
6✔
673
}
674

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

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