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

mendersoftware / useradm / 1808938521

08 May 2025 10:54AM UTC coverage: 59.747% (-27.3%) from 87.019%
1808938521

push

gitlab-ci

alfrunes
Merge `alfrunes:1.22.x` into `mendersoftware:1.22.x`

2363 of 3955 relevant lines covered (59.75%)

12.76 hits per line

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

87.37
/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/common"
30
        "github.com/mendersoftware/useradm/jwt"
31
        "github.com/mendersoftware/useradm/model"
32
        "github.com/mendersoftware/useradm/scope"
33
        "github.com/mendersoftware/useradm/store"
34
)
35

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

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

60
//go:generate ../utils/mockgen.sh
61
type App interface {
62
        HealthCheck(ctx context.Context) error
63
        // Login accepts email/password, returns JWT
64
        Login(ctx context.Context, email model.Email, pass string,
65
                options *LoginOptions) (*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
        GetPlans(ctx context.Context, skip, limit int) []model.Plan
90
        GetPlanBinding(ctx context.Context) (*model.PlanBindingDetails, error)
91
}
92

93
type Config struct {
94
        // token issuer
95
        Issuer string
96
        // token expiration time
97
        ExpirationTimeSeconds int64
98
        // maximum number of log in tokens per user
99
        // zero means no limit
100
        LimitSessionsPerUser int
101
        // maximum number of personal access tokens per user
102
        // zero means no limit
103
        LimitTokensPerUser int
104
        // how often we should update personal access token
105
        // with last used timestamp
106
        TokenLastUsedUpdateFreqMinutes int
107
        // path to the private key, used to generate kid header in JWT and to get all the keys
108
        PrivateKeyPath string
109
        // PrivateKeyFileNamePattern holds the regular expression used
110
        // to get the key id from a filename
111
        PrivateKeyFileNamePattern string
112
}
113

114
type ApiClientGetter func() apiclient.HttpRunner
115

116
func simpleApiClientGetter() apiclient.HttpRunner {
26✔
117
        return &apiclient.HttpApi{}
26✔
118
}
26✔
119

120
type UserAdm struct {
121
        // JWT serialized/deserializer
122
        jwtHandlers  map[int]jwt.Handler
123
        db           store.DataStore
124
        config       Config
125
        verifyTenant bool
126
        cTenant      tenant.ClientRunner
127
        clientGetter ApiClientGetter
128
}
129

130
func NewUserAdm(jwtHandlers map[int]jwt.Handler, db store.DataStore, config Config) *UserAdm {
87✔
131

87✔
132
        return &UserAdm{
87✔
133
                jwtHandlers:  jwtHandlers,
87✔
134
                db:           db,
87✔
135
                config:       config,
87✔
136
                clientGetter: simpleApiClientGetter,
87✔
137
        }
87✔
138
}
87✔
139

140
func (u *UserAdm) HealthCheck(ctx context.Context) error {
4✔
141
        err := u.db.Ping(ctx)
4✔
142
        if err != nil {
5✔
143
                return errors.Wrap(err, "error reaching MongoDB")
1✔
144
        }
1✔
145

146
        if u.verifyTenant {
5✔
147
                err = u.cTenant.CheckHealth(ctx)
2✔
148
                if err != nil {
3✔
149
                        return errors.Wrap(err, "Tenantadm service unhealthy")
1✔
150
                }
1✔
151
        }
152

153
        return nil
2✔
154
}
155

156
func (u *UserAdm) Login(ctx context.Context, email model.Email, pass string,
157
        options *LoginOptions) (*jwt.Token, error) {
12✔
158
        var ident identity.Identity
12✔
159
        l := log.FromContext(ctx)
12✔
160

12✔
161
        if email == "" {
12✔
162
                return nil, ErrUnauthorized
×
163
        }
×
164

165
        if u.verifyTenant {
18✔
166
                // check the user's tenant
6✔
167
                tenant, err := u.cTenant.GetTenant(ctx, string(email), u.clientGetter())
6✔
168

6✔
169
                if err != nil {
7✔
170
                        return nil, errors.Wrap(err, "failed to check user's tenant")
1✔
171
                }
1✔
172

173
                if tenant == nil {
6✔
174
                        return nil, ErrUnauthorized
1✔
175
                }
1✔
176

177
                if tenant.Status == TenantStatusSuspended {
5✔
178
                        return nil, ErrTenantAccountSuspended
1✔
179
                }
1✔
180

181
                ident.Tenant = tenant.ID
3✔
182
                ctx = identity.WithContext(ctx, &ident)
3✔
183
        }
184

185
        //get user
186
        user, err := u.db.GetUserByEmail(ctx, email)
9✔
187

9✔
188
        if user == nil && err == nil {
10✔
189
                return nil, ErrUnauthorized
1✔
190
        }
1✔
191

192
        if err != nil {
9✔
193
                return nil, errors.Wrap(err, "useradm: failed to get user")
1✔
194
        }
1✔
195

196
        //verify password
197
        err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(pass))
7✔
198
        if err != nil {
8✔
199
                return nil, ErrUnauthorized
1✔
200
        }
1✔
201

202
        //generate and save token
203
        t, err := u.generateToken(
6✔
204
                user.ID,
6✔
205
                scope.All,
6✔
206
                ident.Tenant,
6✔
207
                options.NoExpiry,
6✔
208
                common.KeyIdFromPath(u.config.PrivateKeyPath, u.config.PrivateKeyFileNamePattern),
6✔
209
        )
6✔
210
        if err != nil {
6✔
211
                return nil, errors.Wrap(err, "useradm: failed to generate token")
×
212
        }
×
213

214
        err = u.db.SaveToken(ctx, t)
6✔
215
        if err != nil {
7✔
216
                return nil, errors.Wrap(err, "useradm: failed to save token")
1✔
217
        }
1✔
218
        if u.config.LimitSessionsPerUser > 0 {
10✔
219
                err = u.db.EnsureSessionTokensLimit(ctx, t.Subject, u.config.LimitSessionsPerUser)
5✔
220
                if err != nil {
6✔
221
                        return nil, errors.Wrap(err, "useradm: failed to ensure session tokens limit")
1✔
222
                }
1✔
223
        }
224

225
        if err = u.db.UpdateLoginTs(ctx, user.ID); err != nil {
5✔
226
                l.Warnf("failed to update login timestamp: %s", err.Error())
1✔
227
        }
1✔
228

229
        return t, nil
4✔
230
}
231

232
func (u *UserAdm) generateToken(subject, scope, tenant string,
233
        noExpiry bool, keyId int) (*jwt.Token, error) {
10✔
234
        id := oid.NewUUIDv4()
10✔
235
        subjectID := oid.FromString(subject)
10✔
236
        now := jwt.Time{Time: time.Now()}
10✔
237
        ret := &jwt.Token{
10✔
238
                KeyId: keyId,
10✔
239
                Claims: jwt.Claims{
10✔
240
                        ID:        id,
10✔
241
                        Subject:   subjectID,
10✔
242
                        Issuer:    u.config.Issuer,
10✔
243
                        IssuedAt:  now,
10✔
244
                        NotBefore: now,
10✔
245
                        Tenant:    tenant,
10✔
246
                        Scope:     scope,
10✔
247
                        User:      true,
10✔
248
                }}
10✔
249
        if !noExpiry {
20✔
250
                ret.Claims.ExpiresAt = &jwt.Time{
10✔
251
                        Time: now.Add(time.Second * time.Duration(u.config.ExpirationTimeSeconds)),
10✔
252
                }
10✔
253
        }
10✔
254
        return ret, ret.Claims.Valid()
10✔
255
}
256

257
func (u *UserAdm) SignToken(ctx context.Context, t *jwt.Token) (string, error) {
4✔
258
        if _, ok := u.jwtHandlers[t.KeyId]; !ok {
4✔
259
                return "", common.ErrKeyIdNotFound
×
260
        }
×
261
        return u.jwtHandlers[t.KeyId].ToJWT(t)
4✔
262
}
263

264
func (u *UserAdm) Logout(ctx context.Context, token *jwt.Token) error {
2✔
265
        return u.db.DeleteToken(ctx, token.Subject, token.ID)
2✔
266
}
2✔
267

268
func (ua *UserAdm) CreateUser(ctx context.Context, u *model.User) error {
×
269
        hash, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost)
×
270
        if err != nil {
×
271
                return errors.Wrap(err, "failed to generate password hash")
×
272
        }
×
273
        u.Password = string(hash)
×
274

×
275
        return ua.doCreateUser(ctx, u, true)
×
276
}
277

278
func (ua *UserAdm) CreateUserInternal(ctx context.Context, u *model.UserInternal) error {
×
279
        if u.PasswordHash != "" {
×
280
                u.Password = u.PasswordHash
×
281
        } else {
×
282
                hash, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost)
×
283
                if err != nil {
×
284
                        return errors.Wrap(err, "failed to generate password hash")
×
285
                }
×
286
                u.Password = string(hash)
×
287
        }
288

289
        return ua.doCreateUser(ctx, &u.User, u.ShouldPropagate())
×
290
}
291

292
func (ua *UserAdm) doCreateUser(ctx context.Context, u *model.User, propagate bool) error {
13✔
293
        var tenantErr error
13✔
294

13✔
295
        if u.ID == "" {
26✔
296
                //technically one of the functions down the oid.NewUUIDv4() stack can panic,
13✔
297
                //so what we could do is: wrap around it and recover and fall back
13✔
298
                //to non-rand based generation (NewUUIDv5(u.Email)).
13✔
299
                //on the other hand if rand failed maybe it is a good idea t not continue
13✔
300
                //after all.
13✔
301
                //the previous call to NewUUIDv5 produced exact the same ids for same
13✔
302
                //emails, which leads to problems once user changes the email address,
13✔
303
                //and then the old email is used in the new user creation.
13✔
304
                id := oid.NewUUIDv4()
13✔
305
                u.ID = id.String()
13✔
306
        }
13✔
307

308
        id := identity.FromContext(ctx)
13✔
309
        if ua.verifyTenant && propagate {
22✔
310
                tenantErr = ua.cTenant.CreateUser(ctx,
9✔
311
                        &tenant.User{
9✔
312
                                ID:       u.ID,
9✔
313
                                Name:     string(u.Email),
9✔
314
                                TenantID: id.Tenant,
9✔
315
                        },
9✔
316
                        ua.clientGetter())
9✔
317

9✔
318
                if tenantErr != nil && tenantErr != tenant.ErrDuplicateUser {
10✔
319
                        return errors.Wrap(tenantErr, "useradm: failed to create user in tenantadm")
1✔
320
                }
1✔
321
        }
322

323
        if tenantErr == tenant.ErrDuplicateUser {
16✔
324
                // check if the user exists in useradm
4✔
325
                // if the user does not exists then we should try to remove the user from tenantadm
4✔
326
                user, err := ua.db.GetUserByEmail(ctx, u.Email)
4✔
327
                if err != nil {
5✔
328
                        return errors.Wrap(err, "tenant data out of sync: failed to get user from db")
1✔
329
                }
1✔
330
                if user == nil {
5✔
331
                        if compensateErr := ua.compensateTenantUser(
2✔
332
                                ctx,
2✔
333
                                u.ID,
2✔
334
                                id.Tenant,
2✔
335
                        ); compensateErr != nil {
3✔
336
                                tenantErr = compensateErr
1✔
337
                        }
1✔
338
                        return errors.Wrap(tenantErr, "tenant data out of sync")
2✔
339
                }
340
                return store.ErrDuplicateEmail
1✔
341
        }
342

343
        if err := ua.db.CreateUser(ctx, u); err != nil {
13✔
344
                if err == store.ErrDuplicateEmail {
7✔
345
                        return err
2✔
346
                }
2✔
347
                if ua.verifyTenant && propagate {
5✔
348
                        // if the user could not be created in the useradm database
2✔
349
                        // try to remove the user from tenantadm
2✔
350
                        if compensateErr := ua.compensateTenantUser(
2✔
351
                                ctx,
2✔
352
                                u.ID,
2✔
353
                                id.Tenant,
2✔
354
                        ); compensateErr != nil {
3✔
355
                                err = errors.Wrap(err, compensateErr.Error())
1✔
356
                        }
1✔
357
                }
358

359
                return errors.Wrap(err, "useradm: failed to create user in the db")
3✔
360
        }
361

362
        return nil
3✔
363
}
364

365
func (ua *UserAdm) compensateTenantUser(ctx context.Context, userId, tenantId string) error {
4✔
366
        err := ua.cTenant.DeleteUser(ctx, tenantId, userId, ua.clientGetter())
4✔
367

4✔
368
        if err != nil {
6✔
369
                return errors.Wrap(err, "faield to delete tenant user")
2✔
370
        }
2✔
371
        return nil
2✔
372
}
373

374
func (ua *UserAdm) validateUserUpdate(
375
        ctx context.Context,
376
        user *model.User,
377
        u *model.UserUpdate,
378
        me bool,
379
) error {
14✔
380
        // user can change own password only
14✔
381
        if !me {
17✔
382
                if len(u.Password) > 0 {
4✔
383
                        return ErrCannotModifyPassword
1✔
384
                }
1✔
385
        } else {
11✔
386
                // when changing own password or email address
11✔
387
                // user has to provide current password
11✔
388
                if len(u.Password) > 0 || (u.Email != "" && u.Email != user.Email) {
21✔
389
                        if err := bcrypt.CompareHashAndPassword(
10✔
390
                                []byte(user.Password),
10✔
391
                                []byte(u.CurrentPassword),
10✔
392
                        ); err != nil {
12✔
393
                                return ErrCurrentPasswordMismatch
2✔
394
                        }
2✔
395
                }
396
        }
397
        return nil
11✔
398
}
399

400
func (ua *UserAdm) deleteAndInvalidateUserTokens(
401
        ctx context.Context,
402
        userID string,
403
        token *jwt.Token,
404
) error {
4✔
405
        var err error
4✔
406
        if token != nil {
6✔
407
                err = ua.db.DeleteTokensByUserIdExceptCurrentOne(ctx, userID, token.ID)
2✔
408
        } else {
4✔
409
                err = ua.db.DeleteTokensByUserId(ctx, userID)
2✔
410
        }
2✔
411
        return err
4✔
412
}
413

414
func (ua *UserAdm) UpdateUser(ctx context.Context, id string, userUpdate *model.UserUpdate) error {
16✔
415
        idty := identity.FromContext(ctx)
16✔
416
        me := idty.Subject == id
16✔
417
        user, err := ua.db.GetUserAndPasswordById(ctx, id)
16✔
418
        if err != nil {
17✔
419
                return errors.Wrap(err, "useradm: failed to get user")
1✔
420
        } else if user == nil {
17✔
421
                return store.ErrUserNotFound
1✔
422
        }
1✔
423

424
        if err := ua.validateUserUpdate(ctx, user, userUpdate, me); err != nil {
17✔
425
                return err
3✔
426
        }
3✔
427

428
        if userUpdate.ETag == nil {
20✔
429
                // Update without the support for etags.
9✔
430
                next := user.NextETag()
9✔
431
                userUpdate.ETagUpdate = &next
9✔
432
                userUpdate.ETag = user.ETag
9✔
433
                if user.ETag == nil {
18✔
434
                        // If the ETag field is not set, assign the etag to nil
9✔
435
                        user.ETag = &model.ETagNil
9✔
436
                }
9✔
437
        } else if user.ETag == nil || *userUpdate.ETag != *user.ETag {
3✔
438
                return ErrETagMismatch
1✔
439
        }
1✔
440

441
        if len(userUpdate.Email) > 0 && userUpdate.Email != user.Email {
19✔
442
                if ua.verifyTenant {
15✔
443
                        err := ua.cTenant.UpdateUser(ctx,
6✔
444
                                idty.Tenant,
6✔
445
                                id,
6✔
446
                                &tenant.UserUpdate{
6✔
447
                                        Name: string(userUpdate.Email),
6✔
448
                                },
6✔
449
                                ua.clientGetter())
6✔
450

6✔
451
                        switch err {
6✔
452
                        case nil:
3✔
453
                                break
3✔
454
                        case tenant.ErrDuplicateUser:
1✔
455
                                return store.ErrDuplicateEmail
1✔
456
                        case tenant.ErrUserNotFound:
1✔
457
                                return store.ErrUserNotFound
1✔
458
                        default:
1✔
459
                                return errors.Wrap(err, "useradm: failed to update user in tenantadm")
1✔
460
                        }
461
                }
462
        }
463

464
        _, err = ua.db.UpdateUser(ctx, id, userUpdate)
7✔
465
        switch err {
7✔
466
        case nil:
4✔
467
                // invalidate the JWT tokens but the one used to update the user
4✔
468
                err = ua.deleteAndInvalidateUserTokens(ctx, id, userUpdate.Token)
4✔
469
                err = errors.Wrap(err, "useradm: failed to invalidate tokens")
4✔
470

471
        case store.ErrUserNotFound:
1✔
472
                // We matched the user earlier, the ETag must have changed in
1✔
473
                // the meantime
1✔
474
                err = ErrETagMismatch
1✔
475
        case store.ErrDuplicateEmail:
1✔
476
                break
1✔
477

478
        default:
1✔
479
                err = errors.Wrap(err, "useradm: failed to update user information")
1✔
480
        }
481

482
        return err
7✔
483
}
484

485
func (ua *UserAdm) Verify(ctx context.Context, token *jwt.Token) error {
87✔
486

87✔
487
        if token == nil {
152✔
488
                return ErrUnauthorized
65✔
489
        }
65✔
490

491
        l := log.FromContext(ctx)
22✔
492

22✔
493
        if !token.Claims.User {
23✔
494
                l.Errorf("not a user token")
1✔
495
                return ErrUnauthorized
1✔
496
        }
1✔
497

498
        if ua.verifyTenant {
36✔
499
                if token.Claims.Tenant == "" {
15✔
500
                        l.Errorf("Token has no tenant claim")
×
501
                        return jwt.ErrTokenInvalid
×
502
                }
×
503
        } else if token.Claims.Tenant != "" {
6✔
504
                l.Errorf("Unexpected tenant claim: %s in the token", token.Claims.Tenant)
×
505
                return jwt.ErrTokenInvalid
×
506
        }
×
507

508
        user, err := ua.db.GetUserById(ctx, token.Claims.Subject.String())
21✔
509
        if user == nil && err == nil {
23✔
510
                return ErrUnauthorized
2✔
511
        }
2✔
512
        if err != nil {
20✔
513
                return errors.Wrap(err, "useradm: failed to get user")
1✔
514
        }
1✔
515

516
        dbToken, err := ua.db.GetTokenById(ctx, token.ID)
18✔
517
        if dbToken == nil && err == nil {
19✔
518
                return ErrUnauthorized
1✔
519
        }
1✔
520
        if err != nil {
18✔
521
                return errors.Wrap(err, "useradm: failed to get token")
1✔
522
        }
1✔
523

524
        // in case the token is a personal access token, update last used timestam
525
        // to not overload the database with writes to tokens collection, we do not
526
        // update the timestamp every time, but instead we wait some configurable
527
        // amount of time between updates
528
        if dbToken.TokenName != nil && ua.config.TokenLastUsedUpdateFreqMinutes > 0 {
16✔
529
                t := time.Now().Add(
×
530
                        (-time.Minute * time.Duration(ua.config.TokenLastUsedUpdateFreqMinutes)))
×
531
                if dbToken.LastUsed == nil || dbToken.LastUsed.Before(t) {
×
532
                        if err := ua.db.UpdateTokenLastUsed(ctx, token.ID); err != nil {
×
533
                                return err
×
534
                        }
×
535
                }
536
        }
537

538
        return nil
16✔
539
}
540

541
func (ua *UserAdm) GetUsers(ctx context.Context, fltr model.UserFilter) ([]model.User, error) {
3✔
542
        users, err := ua.db.GetUsers(ctx, fltr)
3✔
543
        if err != nil {
4✔
544
                return nil, errors.Wrap(err, "useradm: failed to get users")
1✔
545
        }
1✔
546

547
        return users, nil
2✔
548
}
549

550
func (ua *UserAdm) GetUser(ctx context.Context, id string) (*model.User, error) {
3✔
551
        if id == userIdMe {
3✔
552
                id = identity.FromContext(ctx).Subject
×
553
        }
×
554
        user, err := ua.db.GetUserById(ctx, id)
3✔
555
        if err != nil {
4✔
556
                return nil, errors.Wrap(err, "useradm: failed to get user")
1✔
557
        }
1✔
558

559
        return user, nil
2✔
560
}
561

562
func (ua *UserAdm) DeleteUser(ctx context.Context, id string) error {
5✔
563
        if ua.verifyTenant {
7✔
564
                identity := identity.FromContext(ctx)
2✔
565
                err := ua.cTenant.DeleteUser(ctx, identity.Tenant, id, ua.clientGetter())
2✔
566

2✔
567
                if err != nil {
3✔
568
                        return errors.Wrap(err, "useradm: failed to delete user in tenantadm")
1✔
569
                }
1✔
570
        }
571

572
        err := ua.db.DeleteUser(ctx, id)
4✔
573
        if err != nil {
5✔
574
                return errors.Wrap(err, "useradm: failed to delete user")
1✔
575
        }
1✔
576

577
        // remove user tokens
578
        err = ua.db.DeleteTokensByUserId(ctx, id)
3✔
579
        if err != nil {
4✔
580
                return errors.Wrap(err, "useradm: failed to delete user tokens")
1✔
581
        }
1✔
582

583
        return nil
2✔
584
}
585

586
// WithTenantVerification produces a UserAdm instance which enforces
587
// tenant verification vs the tenantadm service upon /login.
588
func (u *UserAdm) WithTenantVerification(c tenant.ClientRunner) *UserAdm {
25✔
589
        u.verifyTenant = true
25✔
590
        u.cTenant = c
25✔
591
        return u
25✔
592
}
25✔
593

594
func (u *UserAdm) CreateTenant(ctx context.Context, tenant model.NewTenant) error {
1✔
595
        return nil
1✔
596
}
1✔
597

598
func (ua *UserAdm) SetPassword(ctx context.Context, uu model.UserUpdate) error {
5✔
599
        u, err := ua.db.GetUserByEmail(ctx, uu.Email)
5✔
600
        if err != nil {
6✔
601
                return errors.Wrap(err, "useradm: failed to get user by email")
1✔
602

1✔
603
        }
1✔
604
        if u == nil {
5✔
605
                return ErrUserNotFound
1✔
606
        }
1✔
607

608
        _, err = ua.db.UpdateUser(ctx, u.ID, &uu)
3✔
609

3✔
610
        // if we changed the password, invalidate the JWT tokens but the one used to update the user
3✔
611
        if err == nil && uu.Password != "" {
5✔
612
                if uu.Token != nil {
3✔
613
                        err = ua.db.DeleteTokensByUserIdExceptCurrentOne(ctx, u.ID, uu.Token.ID)
1✔
614
                } else {
2✔
615
                        err = ua.db.DeleteTokensByUserId(ctx, u.ID)
1✔
616
                }
1✔
617
        }
618
        if err != nil {
4✔
619
                return errors.Wrap(err, "useradm: failed to update user information")
1✔
620
        }
1✔
621

622
        return nil
2✔
623
}
624

625
func (ua *UserAdm) DeleteTokens(ctx context.Context, tenantId, userId string) error {
4✔
626
        ctx = identity.WithContext(ctx, &identity.Identity{
4✔
627
                Tenant: tenantId,
4✔
628
        })
4✔
629

4✔
630
        var err error
4✔
631

4✔
632
        if userId != "" {
6✔
633
                err = ua.db.DeleteTokensByUserId(ctx, userId)
2✔
634
        } else {
4✔
635
                err = ua.db.DeleteTokens(ctx)
2✔
636
        }
2✔
637

638
        if err != nil && err != store.ErrTokenNotFound {
5✔
639
                return errors.Wrapf(
1✔
640
                        err,
1✔
641
                        "failed to delete tokens for tenant: %v, user id: %v",
1✔
642
                        tenantId,
1✔
643
                        userId,
1✔
644
                )
1✔
645
        }
1✔
646

647
        return nil
3✔
648
}
649

650
func (u *UserAdm) IssuePersonalAccessToken(
651
        ctx context.Context,
652
        tr *model.TokenRequest,
653
) (string, error) {
6✔
654
        id := identity.FromContext(ctx)
6✔
655
        if id == nil {
6✔
656
                return "", errors.New("identity not present in the context")
×
657
        }
×
658
        if u.config.LimitTokensPerUser > 0 {
11✔
659
                count, err := u.db.CountPersonalAccessTokens(ctx, id.Subject)
5✔
660
                if err != nil {
6✔
661
                        return "", errors.Wrap(err, "useradm: failed to count personal access tokens")
1✔
662
                }
1✔
663
                if count >= int64(u.config.LimitTokensPerUser) {
5✔
664
                        return "", ErrTooManyTokens
1✔
665
                }
1✔
666
        }
667
        //generate and save token
668
        keyId := common.KeyIdFromPath(u.config.PrivateKeyPath, u.config.PrivateKeyFileNamePattern)
4✔
669
        t, err := u.generateToken(
4✔
670
                id.Subject,
4✔
671
                scope.All,
4✔
672
                id.Tenant,
4✔
673
                tr.ExpiresIn == 0,
4✔
674
                keyId,
4✔
675
        )
4✔
676
        if err != nil {
4✔
677
                return "", errors.Wrap(err, "useradm: failed to generate token")
×
678
        }
×
679
        // update claims
680
        t.TokenName = tr.Name
4✔
681
        if tr.ExpiresIn > 0 {
8✔
682
                expires := jwt.Time{
4✔
683
                        Time: time.Now().Add(time.Second * time.Duration(tr.ExpiresIn)),
4✔
684
                }
4✔
685
                t.ExpiresAt = &expires
4✔
686
        }
4✔
687

688
        err = u.db.SaveToken(ctx, t)
4✔
689
        if err == store.ErrDuplicateTokenName {
5✔
690
                return "", ErrDuplicateTokenName
1✔
691
        } else if err != nil {
5✔
692
                return "", errors.Wrap(err, "useradm: failed to save token")
1✔
693
        }
1✔
694

695
        if _, ok := u.jwtHandlers[keyId]; !ok {
2✔
696
                return "", common.ErrKeyIdNotFound
×
697
        }
×
698
        // sign token
699
        return u.jwtHandlers[keyId].ToJWT(t)
2✔
700
}
701

702
func (ua *UserAdm) GetPersonalAccessTokens(
703
        ctx context.Context,
704
        userID string,
705
) ([]model.PersonalAccessToken, error) {
×
706
        tokens, err := ua.db.GetPersonalAccessTokens(ctx, userID)
×
707
        if err != nil {
×
708
                return nil, errors.Wrap(err, "useradm: failed to get tokens")
×
709
        }
×
710

711
        return tokens, nil
×
712
}
713

714
func (ua *UserAdm) DeleteToken(ctx context.Context, id string) error {
×
715
        identity := identity.FromContext(ctx)
×
716
        if identity == nil {
×
717
                return errors.New("identity not present in the context")
×
718
        }
×
719
        err := ua.db.DeleteToken(ctx, oid.FromString(identity.Subject), oid.FromString(id))
×
720
        if err != nil {
×
721
                return errors.Wrap(err, "useradm: failed to delete token")
×
722
        }
×
723

724
        return nil
×
725
}
726

727
func (ua *UserAdm) GetPlans(ctx context.Context, skip, limit int) []model.Plan {
5✔
728
        if (skip + limit) <= len(model.PlanList) {
8✔
729
                return model.PlanList[skip:(skip + limit)]
3✔
730
        } else if skip <= len(model.PlanList) {
6✔
731
                return model.PlanList[skip:]
1✔
732
        }
1✔
733
        return []model.Plan{}
1✔
734
}
735

736
func (ua *UserAdm) GetPlanBinding(ctx context.Context) (*model.PlanBindingDetails, error) {
2✔
737
        if len(model.PlanList) == 1 {
3✔
738
                return &model.PlanBindingDetails{
1✔
739
                        Plan: model.PlanList[0],
1✔
740
                }, nil
1✔
741
        }
1✔
742
        return &model.PlanBindingDetails{}, nil
1✔
743
}
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