• 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

/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,
64
                stayLoggedIn bool) (*jwt.Token, error)
65
        Logout(ctx context.Context, token *jwt.Token) error
66
        CreateUser(ctx context.Context, u *model.User) error
67
        CreateUserInternal(ctx context.Context, u *model.UserInternal) error
68
        UpdateUser(ctx context.Context, id string, u *model.UserUpdate) error
69
        Verify(ctx context.Context, token *jwt.Token) error
70
        GetUsers(ctx context.Context, fltr model.UserFilter) ([]model.User, error)
71
        GetUser(ctx context.Context, id string) (*model.User, error)
72
        DeleteUser(ctx context.Context, id string) error
73
        SetPassword(ctx context.Context, u model.UserUpdate) error
74

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

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

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

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

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

103
type ApiClientGetter func() apiclient.HttpRunner
104

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

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

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

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

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

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

142
        return nil
4✔
143
}
144

145
func (u *UserAdm) Login(ctx context.Context, email model.Email, pass string,
146
        stayLoggedIn bool) (*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, stayLoggedIn)
134✔
193
        if err != nil {
134✔
UNCOV
194
                return nil, errors.Wrap(err, "useradm: failed to generate token")
×
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,
210
        stayLoggedIn bool) (*jwt.Token, error) {
190✔
211
        id := oid.NewUUIDv4()
190✔
212
        subjectID := oid.FromString(subject)
190✔
213
        now := jwt.Time{Time: time.Now()}
190✔
214
        ret := &jwt.Token{Claims: jwt.Claims{
190✔
215
                ID:        id,
190✔
216
                Subject:   subjectID,
190✔
217
                Issuer:    u.config.Issuer,
190✔
218
                IssuedAt:  now,
190✔
219
                NotBefore: now,
190✔
220
                Tenant:    tenant,
190✔
221
                Scope:     scope,
190✔
222
                User:      true,
190✔
223
        }}
190✔
224
        if !stayLoggedIn {
380✔
225
                ret.Claims.ExpiresAt = &jwt.Time{
190✔
226
                        Time: now.Add(time.Second * time.Duration(u.config.ExpirationTime)),
190✔
227
                }
190✔
228
        }
190✔
229
        return ret, ret.Claims.Valid()
190✔
230
}
231

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

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

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

432✔
247
        return ua.doCreateUser(ctx, u, true)
432✔
248
}
249

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

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

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

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

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

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

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

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

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

334
        return nil
439✔
335
}
336

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

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

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

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

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

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

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

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

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

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

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

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

454
        return err
27✔
455
}
456

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

130✔
459
        if token == nil {
130✔
UNCOV
460
                return ErrUnauthorized
×
UNCOV
461
        }
×
462

463
        l := log.FromContext(ctx)
130✔
464

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

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

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

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

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

510
        return nil
107✔
511
}
512

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

519
        return users, nil
343✔
520
}
521

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

531
        return user, nil
34✔
532
}
533

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

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

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

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

555
        return nil
10✔
556
}
557

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

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

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

2✔
575
        }
2✔
576
        if u == nil {
12✔
577
                return ErrUserNotFound
2✔
578
        }
2✔
579

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

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

594
        return nil
6✔
595
}
596

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

12✔
602
        var err error
12✔
603

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

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

619
        return nil
10✔
620
}
621

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

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

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

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

673
        return tokens, nil
6✔
674
}
675

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

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