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

mendersoftware / mender-server / 1961929783

01 Aug 2025 07:58PM UTC coverage: 65.504% (-0.05%) from 65.555%
1961929783

Pull #848

gitlab-ci

kjaskiewiczz
chore(useradm): remove dead code; update docs and tests

Signed-off-by: Krzysztof Jaskiewicz <krzysztof.jaskiewicz@northern.tech>
Pull Request #848: chore(useradm): remove dead code; update docs and tests

3 of 3 new or added lines in 1 file covered. (100.0%)

23 existing lines in 1 file now uncovered.

32315 of 49333 relevant lines covered (65.5%)

1.39 hits per line

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

88.21
/backend/services/useradm/user/useradm.go
1
// Copyright 2025 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/google/uuid"
22
        "github.com/pkg/errors"
23
        "golang.org/x/crypto/bcrypt"
24

25
        "github.com/mendersoftware/mender-server/pkg/apiclient"
26
        "github.com/mendersoftware/mender-server/pkg/identity"
27
        "github.com/mendersoftware/mender-server/pkg/log"
28
        "github.com/mendersoftware/mender-server/pkg/mongo/oid"
29

30
        "github.com/mendersoftware/mender-server/services/useradm/client/tenant"
31
        "github.com/mendersoftware/mender-server/services/useradm/common"
32
        "github.com/mendersoftware/mender-server/services/useradm/jwt"
33
        "github.com/mendersoftware/mender-server/services/useradm/model"
34
        "github.com/mendersoftware/mender-server/services/useradm/scope"
35
        "github.com/mendersoftware/mender-server/services/useradm/store"
36
        "github.com/mendersoftware/mender-server/services/useradm/utils"
37
)
38

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

60
const (
61
        TenantStatusSuspended = "suspended"
62
        userIdMe              = "me"
63
)
64

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

81
        // SignToken generates a signed
82
        // token using configuration & method set up in UserAdmApp
83
        SignToken(ctx context.Context, t *jwt.Token) (string, error)
84
        DeleteToken(ctx context.Context, id string) error
85

86
        // IssuePersonalAccessToken issues Personal Access Token
87
        IssuePersonalAccessToken(ctx context.Context, tr *model.TokenRequest) (string, error)
88
        // GetPersonalAccessTokens returns list of Personal Access Tokens
89
        GetPersonalAccessTokens(ctx context.Context, userID string) ([]model.PersonalAccessToken, error)
90

91
        DeleteTokens(ctx context.Context, tenantId, userId string) error
92

93
        CreateTenant(ctx context.Context, tenant model.NewTenant) error
94
        GetPlans(ctx context.Context, skip, limit int) []model.Plan
95
        GetPlanBinding(ctx context.Context) (*model.PlanBindingDetails, error)
96
}
97

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

119
type ApiClientGetter func() apiclient.HttpRunner
120

121
func simpleApiClientGetter() apiclient.HttpRunner {
1✔
122
        return &apiclient.HttpApi{}
1✔
123
}
1✔
124

125
type UserAdm struct {
126
        // JWT serialized/deserializer
127
        jwtHandlers  map[int]jwt.Handler
128
        db           store.DataStore
129
        config       Config
130
        verifyTenant bool
131
        cTenant      tenant.ClientRunner
132
        clientGetter ApiClientGetter
133
}
134

135
func NewUserAdm(jwtHandlers map[int]jwt.Handler, db store.DataStore, config Config) *UserAdm {
3✔
136

3✔
137
        return &UserAdm{
3✔
138
                jwtHandlers:  jwtHandlers,
3✔
139
                db:           db,
3✔
140
                config:       config,
3✔
141
                clientGetter: simpleApiClientGetter,
3✔
142
        }
3✔
143
}
3✔
144

145
func (u *UserAdm) HealthCheck(ctx context.Context) error {
2✔
146
        err := u.db.Ping(ctx)
2✔
147
        if err != nil {
3✔
148
                return errors.Wrap(err, "error reaching MongoDB")
1✔
149
        }
1✔
150

151
        if u.verifyTenant {
3✔
152
                err = u.cTenant.CheckHealth(ctx)
1✔
153
                if err != nil {
2✔
154
                        return errors.Wrap(err, "Tenantadm service unhealthy")
1✔
155
                }
1✔
156
        }
157

158
        return nil
2✔
159
}
160

161
func (u *UserAdm) Login(ctx context.Context, email model.Email, pass string,
162
        options *LoginOptions) (*jwt.Token, error) {
3✔
163
        var ident identity.Identity
3✔
164
        l := log.FromContext(ctx)
3✔
165

3✔
166
        if email == "" {
3✔
167
                return nil, ErrUnauthorized
×
168
        }
×
169

170
        if u.verifyTenant {
4✔
171
                // check the user's tenant
1✔
172
                tenant, err := u.cTenant.GetTenant(ctx, string(email), u.clientGetter())
1✔
173

1✔
174
                if err != nil {
2✔
175
                        return nil, errors.Wrap(err, "failed to check user's tenant")
1✔
176
                }
1✔
177

178
                if tenant == nil {
2✔
179
                        return nil, ErrUnauthorized
1✔
180
                }
1✔
181

182
                if tenant.Status == TenantStatusSuspended {
2✔
183
                        return nil, ErrTenantAccountSuspended
1✔
184
                }
1✔
185

186
                ident.Tenant = tenant.ID
1✔
187
                ctx = identity.WithContext(ctx, &ident)
1✔
188
        }
189

190
        //get user
191
        user, err := u.db.GetUserByEmail(ctx, email)
3✔
192

3✔
193
        if user == nil && err == nil {
5✔
194
                return nil, ErrUnauthorized
2✔
195
        }
2✔
196

197
        if err != nil {
4✔
198
                return nil, errors.Wrap(err, "useradm: failed to get user")
1✔
199
        }
1✔
200

201
        //verify password
202
        err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(pass))
3✔
203
        if err != nil {
5✔
204
                return nil, ErrUnauthorized
2✔
205
        }
2✔
206

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

219
        err = u.db.SaveToken(ctx, t)
3✔
220
        if err != nil {
4✔
221
                return nil, errors.Wrap(err, "useradm: failed to save token")
1✔
222
        }
1✔
223
        if u.config.LimitSessionsPerUser > 0 {
6✔
224
                err = u.db.EnsureSessionTokensLimit(ctx, t.Subject, u.config.LimitSessionsPerUser)
3✔
225
                if err != nil {
4✔
226
                        return nil, errors.Wrap(err, "useradm: failed to ensure session tokens limit")
1✔
227
                }
1✔
228
        }
229

230
        if err = u.db.UpdateLoginTs(ctx, user.ID); err != nil {
4✔
231
                l.Warnf("failed to update login timestamp: %s", err.Error())
1✔
232
        }
1✔
233

234
        return t, nil
3✔
235
}
236

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

262
func (u *UserAdm) SignToken(ctx context.Context, t *jwt.Token) (string, error) {
3✔
263
        if _, ok := u.jwtHandlers[t.KeyId]; !ok {
3✔
264
                return "", common.ErrKeyIdNotFound
×
265
        }
×
266
        return u.jwtHandlers[t.KeyId].ToJWT(t)
3✔
267
}
268

269
func (u *UserAdm) Logout(ctx context.Context, token *jwt.Token) error {
2✔
270
        return u.db.DeleteToken(ctx, token.Subject, token.ID)
2✔
271
}
2✔
272

273
func (ua *UserAdm) CreateUser(ctx context.Context, u *model.User) error {
2✔
274
        if utils.CheckIfPassSimilarToEmailRaw(string(u.Email), string(u.Password)) {
3✔
275
                return ErrPassAndMailTooSimilar
1✔
276
        }
1✔
277
        hash, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost)
2✔
278
        if err != nil {
2✔
279
                return errors.Wrap(err, "failed to generate password hash")
×
280
        }
×
281
        u.Password = string(hash)
2✔
282

2✔
283
        // Generate a unique user ID for the new user
2✔
284
        u.ID = uuid.NewString()
2✔
285

2✔
286
        return ua.doCreateUser(ctx, u)
2✔
287
}
288

289
func (ua *UserAdm) CreateUserInternal(ctx context.Context, u *model.UserInternal) error {
2✔
290
        if u.PasswordHash != "" {
2✔
291
                u.Password = u.PasswordHash
×
292
        } else {
2✔
293
                hash, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost)
2✔
294
                if err != nil {
2✔
295
                        return errors.Wrap(err, "failed to generate password hash")
×
296
                }
×
297
                u.Password = string(hash)
2✔
298
        }
299

300
        return ua.doCreateUser(ctx, &u.User)
2✔
301
}
302

303
func (ua *UserAdm) doCreateUser(ctx context.Context, u *model.User) error {
3✔
304
        var tenantErr error
3✔
305

3✔
306
        if u.ID == "" {
6✔
307
                // Generate a unique user ID for the new user
3✔
308
                u.ID = uuid.NewString()
3✔
309
        }
3✔
310

311
        id := identity.FromContext(ctx)
3✔
312

3✔
313
        if tenantErr == tenant.ErrDuplicateUser {
3✔
UNCOV
314
                // check if the user exists in useradm
×
UNCOV
315
                // if the user does not exists then we should try to remove the user from tenantadm
×
UNCOV
316
                user, err := ua.db.GetUserByEmail(ctx, u.Email)
×
UNCOV
317
                if err != nil {
×
UNCOV
318
                        return errors.Wrap(err, "tenant data out of sync: failed to get user from db")
×
UNCOV
319
                }
×
UNCOV
320
                if user == nil {
×
UNCOV
321
                        if compensateErr := ua.compensateTenantUser(
×
UNCOV
322
                                ctx,
×
UNCOV
323
                                u.ID,
×
UNCOV
324
                                id.Tenant,
×
UNCOV
325
                        ); compensateErr != nil {
×
UNCOV
326
                                tenantErr = compensateErr
×
UNCOV
327
                        }
×
UNCOV
328
                        return errors.Wrap(tenantErr, "tenant data out of sync")
×
329
                }
UNCOV
330
                return store.ErrDuplicateEmail
×
331
        }
332

333
        if err := ua.db.CreateUser(ctx, u); err != nil {
4✔
334
                if err == store.ErrDuplicateEmail {
2✔
335
                        return err
1✔
336
                }
1✔
337

338
                return errors.Wrap(err, "useradm: failed to create user in the db")
1✔
339
        }
340

341
        return nil
3✔
342
}
343

UNCOV
344
func (ua *UserAdm) compensateTenantUser(ctx context.Context, userId, tenantId string) error {
×
UNCOV
345
        err := ua.cTenant.DeleteUser(ctx, tenantId, userId, ua.clientGetter())
×
UNCOV
346

×
UNCOV
347
        if err != nil {
×
UNCOV
348
                return errors.Wrap(err, "faield to delete tenant user")
×
UNCOV
349
        }
×
UNCOV
350
        return nil
×
351
}
352

353
func (ua *UserAdm) validateUserUpdate(
354
        ctx context.Context,
355
        user *model.User,
356
        u *model.UserUpdate,
357
        me bool,
358
) error {
2✔
359
        // user can change own password only
2✔
360
        if !me {
4✔
361
                if len(u.Password) > 0 {
4✔
362
                        return ErrCannotModifyPassword
2✔
363
                }
2✔
364
        } else {
2✔
365
                // when changing own password or email address
2✔
366
                // user has to provide current password
2✔
367
                if len(u.Password) > 0 || (u.Email != "" && u.Email != user.Email) {
4✔
368
                        if err := bcrypt.CompareHashAndPassword(
2✔
369
                                []byte(user.Password),
2✔
370
                                []byte(u.CurrentPassword),
2✔
371
                        ); err != nil {
4✔
372
                                return ErrCurrentPasswordMismatch
2✔
373
                        }
2✔
374
                }
375
        }
376
        return nil
2✔
377
}
378

379
func (ua *UserAdm) deleteAndInvalidateUserTokens(
380
        ctx context.Context,
381
        userID string,
382
        token *jwt.Token,
383
) error {
2✔
384
        var err error
2✔
385
        if token != nil {
4✔
386
                err = ua.db.DeleteTokensByUserIdExceptCurrentOne(ctx, userID, token.ID)
2✔
387
        } else {
3✔
388
                err = ua.db.DeleteTokensByUserId(ctx, userID)
1✔
389
        }
1✔
390
        return err
2✔
391
}
392

393
func (ua *UserAdm) UpdateUser(ctx context.Context, id string, userUpdate *model.UserUpdate) error {
2✔
394
        idty := identity.FromContext(ctx)
2✔
395
        me := idty.Subject == id
2✔
396
        user, err := ua.db.GetUserAndPasswordById(ctx, id)
2✔
397
        if err != nil {
3✔
398
                return errors.Wrap(err, "useradm: failed to get user")
1✔
399
        } else if user == nil {
5✔
400
                return store.ErrUserNotFound
2✔
401
        }
2✔
402

403
        if err := ua.validateUserUpdate(ctx, user, userUpdate, me); err != nil {
4✔
404
                return err
2✔
405
        }
2✔
406

407
        if userUpdate.ETag == nil {
4✔
408
                // Update without the support for etags.
2✔
409
                next := user.NextETag()
2✔
410
                userUpdate.ETagUpdate = &next
2✔
411
                userUpdate.ETag = user.ETag
2✔
412
                if user.ETag == nil {
4✔
413
                        // If the ETag field is not set, assign the etag to nil
2✔
414
                        user.ETag = &model.ETagNil
2✔
415
                }
2✔
416
        } else if user.ETag == nil || *userUpdate.ETag != *user.ETag {
2✔
417
                return ErrETagMismatch
1✔
418
        }
1✔
419

420
        if len(userUpdate.Email) > 0 && userUpdate.Email != user.Email {
4✔
421
                if ua.verifyTenant {
3✔
422
                        err := ua.cTenant.UpdateUser(ctx,
1✔
423
                                idty.Tenant,
1✔
424
                                id,
1✔
425
                                &tenant.UserUpdate{
1✔
426
                                        Name: string(userUpdate.Email),
1✔
427
                                },
1✔
428
                                ua.clientGetter())
1✔
429

1✔
430
                        switch err {
1✔
431
                        case nil:
1✔
432
                                break
1✔
433
                        case tenant.ErrDuplicateUser:
1✔
434
                                return store.ErrDuplicateEmail
1✔
435
                        case tenant.ErrUserNotFound:
1✔
436
                                return store.ErrUserNotFound
1✔
437
                        default:
1✔
438
                                return errors.Wrap(err, "useradm: failed to update user in tenantadm")
1✔
439
                        }
440
                }
441
        }
442

443
        if utils.CheckIfPassSimilarToEmail(user, userUpdate) {
3✔
444
                return ErrPassAndMailTooSimilar
1✔
445
        }
1✔
446

447
        _, err = ua.db.UpdateUser(ctx, id, userUpdate)
2✔
448
        switch err {
2✔
449
        case nil:
2✔
450
                // invalidate the JWT tokens but the one used to update the user
2✔
451
                err = ua.deleteAndInvalidateUserTokens(ctx, id, userUpdate.Token)
2✔
452
                err = errors.Wrap(err, "useradm: failed to invalidate tokens")
2✔
453

454
        case store.ErrUserNotFound:
1✔
455
                // We matched the user earlier, the ETag must have changed in
1✔
456
                // the meantime
1✔
457
                err = ErrETagMismatch
1✔
458
        case store.ErrDuplicateEmail:
1✔
459
                break
1✔
460

461
        default:
1✔
462
                err = errors.Wrap(err, "useradm: failed to update user information")
1✔
463
        }
464

465
        return err
2✔
466
}
467

468
func (ua *UserAdm) Verify(ctx context.Context, token *jwt.Token) error {
3✔
469

3✔
470
        if token == nil {
4✔
471
                return ErrUnauthorized
1✔
472
        }
1✔
473

474
        l := log.FromContext(ctx)
3✔
475

3✔
476
        if !token.Claims.User {
4✔
477
                l.Errorf("not a user token")
1✔
478
                return ErrUnauthorized
1✔
479
        }
1✔
480

481
        if ua.verifyTenant {
4✔
482
                if token.Claims.Tenant == "" {
1✔
483
                        l.Errorf("Token has no tenant claim")
×
484
                        return jwt.ErrTokenInvalid
×
485
                }
×
486
        } else if token.Claims.Tenant != "" {
3✔
487
                l.Errorf("Unexpected tenant claim: %s in the token", token.Claims.Tenant)
×
488
                return jwt.ErrTokenInvalid
×
489
        }
×
490

491
        user, err := ua.db.GetUserById(ctx, token.Claims.Subject.String())
3✔
492
        if user == nil && err == nil {
4✔
493
                return ErrUnauthorized
1✔
494
        }
1✔
495
        if err != nil {
4✔
496
                return errors.Wrap(err, "useradm: failed to get user")
1✔
497
        }
1✔
498

499
        dbToken, err := ua.db.GetTokenById(ctx, token.ID)
3✔
500
        if dbToken == nil && err == nil {
5✔
501
                return ErrUnauthorized
2✔
502
        }
2✔
503
        if err != nil {
4✔
504
                return errors.Wrap(err, "useradm: failed to get token")
1✔
505
        }
1✔
506

507
        // in case the token is a personal access token, update last used timestam
508
        // to not overload the database with writes to tokens collection, we do not
509
        // update the timestamp every time, but instead we wait some configurable
510
        // amount of time between updates
511
        if dbToken.TokenName != nil && ua.config.TokenLastUsedUpdateFreqMinutes > 0 {
4✔
512
                t := time.Now().Add(
1✔
513
                        (-time.Minute * time.Duration(ua.config.TokenLastUsedUpdateFreqMinutes)))
1✔
514
                if dbToken.LastUsed == nil || dbToken.LastUsed.Before(t) {
2✔
515
                        if err := ua.db.UpdateTokenLastUsed(ctx, token.ID); err != nil {
1✔
516
                                return err
×
517
                        }
×
518
                }
519
        }
520

521
        return nil
3✔
522
}
523

524
func (ua *UserAdm) GetUsers(ctx context.Context, fltr model.UserFilter) ([]model.User, error) {
3✔
525
        users, err := ua.db.GetUsers(ctx, fltr)
3✔
526
        if err != nil {
4✔
527
                return nil, errors.Wrap(err, "useradm: failed to get users")
1✔
528
        }
1✔
529

530
        return users, nil
3✔
531
}
532

533
func (ua *UserAdm) GetUser(ctx context.Context, id string) (*model.User, error) {
2✔
534
        if id == userIdMe {
3✔
535
                id = identity.FromContext(ctx).Subject
1✔
536
        }
1✔
537
        user, err := ua.db.GetUserById(ctx, id)
2✔
538
        if err != nil {
3✔
539
                return nil, errors.Wrap(err, "useradm: failed to get user")
1✔
540
        }
1✔
541

542
        return user, nil
2✔
543
}
544

545
func (ua *UserAdm) DeleteUser(ctx context.Context, id string) error {
3✔
546
        if ua.verifyTenant {
4✔
547
                identity := identity.FromContext(ctx)
1✔
548
                err := ua.cTenant.DeleteUser(ctx, identity.Tenant, id, ua.clientGetter())
1✔
549

1✔
550
                if err != nil {
2✔
551
                        return errors.Wrap(err, "useradm: failed to delete user in tenantadm")
1✔
552
                }
1✔
553
        }
554

555
        err := ua.db.DeleteUser(ctx, id)
3✔
556
        if err != nil {
4✔
557
                return errors.Wrap(err, "useradm: failed to delete user")
1✔
558
        }
1✔
559

560
        // remove user tokens
561
        err = ua.db.DeleteTokensByUserId(ctx, id)
3✔
562
        if err != nil {
4✔
563
                return errors.Wrap(err, "useradm: failed to delete user tokens")
1✔
564
        }
1✔
565

566
        return nil
3✔
567
}
568

569
// WithTenantVerification produces a UserAdm instance which enforces
570
// tenant verification vs the tenantadm service upon /login.
571
func (u *UserAdm) WithTenantVerification(c tenant.ClientRunner) *UserAdm {
1✔
572
        u.verifyTenant = true
1✔
573
        u.cTenant = c
1✔
574
        return u
1✔
575
}
1✔
576

577
func (u *UserAdm) CreateTenant(ctx context.Context, tenant model.NewTenant) error {
2✔
578
        return nil
2✔
579
}
2✔
580

581
func (ua *UserAdm) SetPassword(ctx context.Context, uu model.UserUpdate) error {
2✔
582
        u, err := ua.db.GetUserByEmail(ctx, uu.Email)
2✔
583
        if err != nil {
3✔
584
                return errors.Wrap(err, "useradm: failed to get user by email")
1✔
585

1✔
586
        }
1✔
587
        if u == nil {
3✔
588
                return ErrUserNotFound
1✔
589
        }
1✔
590
        if utils.CheckIfPassSimilarToEmail(u, &uu) {
3✔
591
                return ErrPassAndMailTooSimilar
1✔
592
        }
1✔
593

594
        _, err = ua.db.UpdateUser(ctx, u.ID, &uu)
2✔
595

2✔
596
        // if we changed the password, invalidate the JWT tokens but the one used to update the user
2✔
597
        if err == nil && uu.Password != "" {
4✔
598
                if uu.Token != nil {
3✔
599
                        err = ua.db.DeleteTokensByUserIdExceptCurrentOne(ctx, u.ID, uu.Token.ID)
1✔
600
                } else {
3✔
601
                        err = ua.db.DeleteTokensByUserId(ctx, u.ID)
2✔
602
                }
2✔
603
        }
604
        if err != nil {
3✔
605
                return errors.Wrap(err, "useradm: failed to update user information")
1✔
606
        }
1✔
607

608
        return nil
2✔
609
}
610

611
func (ua *UserAdm) DeleteTokens(ctx context.Context, tenantId, userId string) error {
1✔
612
        ctx = identity.WithContext(ctx, &identity.Identity{
1✔
613
                Tenant: tenantId,
1✔
614
        })
1✔
615

1✔
616
        var err error
1✔
617

1✔
618
        if userId != "" {
2✔
619
                err = ua.db.DeleteTokensByUserId(ctx, userId)
1✔
620
        } else {
2✔
621
                err = ua.db.DeleteTokens(ctx)
1✔
622
        }
1✔
623

624
        if err != nil && err != store.ErrTokenNotFound {
2✔
625
                return errors.Wrapf(
1✔
626
                        err,
1✔
627
                        "failed to delete tokens for tenant: %v, user id: %v",
1✔
628
                        tenantId,
1✔
629
                        userId,
1✔
630
                )
1✔
631
        }
1✔
632

633
        return nil
1✔
634
}
635

636
func (u *UserAdm) IssuePersonalAccessToken(
637
        ctx context.Context,
638
        tr *model.TokenRequest,
639
) (string, error) {
2✔
640
        id := identity.FromContext(ctx)
2✔
641
        if id == nil {
2✔
642
                return "", errors.New("identity not present in the context")
×
643
        }
×
644
        if u.config.LimitTokensPerUser > 0 {
4✔
645
                count, err := u.db.CountPersonalAccessTokens(ctx, id.Subject)
2✔
646
                if err != nil {
3✔
647
                        return "", errors.Wrap(err, "useradm: failed to count personal access tokens")
1✔
648
                }
1✔
649
                if count >= int64(u.config.LimitTokensPerUser) {
4✔
650
                        return "", ErrTooManyTokens
2✔
651
                }
2✔
652
        }
653
        //generate and save token
654
        keyId := common.KeyIdFromPath(u.config.PrivateKeyPath, u.config.PrivateKeyFileNamePattern)
2✔
655
        t, err := u.generateToken(
2✔
656
                id.Subject,
2✔
657
                scope.All,
2✔
658
                id.Tenant,
2✔
659
                tr.ExpiresIn == 0,
2✔
660
                keyId,
2✔
661
        )
2✔
662
        if err != nil {
2✔
663
                return "", errors.Wrap(err, "useradm: failed to generate token")
×
664
        }
×
665
        // update claims
666
        t.TokenName = tr.Name
2✔
667
        if tr.ExpiresIn > 0 {
4✔
668
                expires := jwt.Time{
2✔
669
                        Time: time.Now().Add(time.Second * time.Duration(tr.ExpiresIn)),
2✔
670
                }
2✔
671
                t.ExpiresAt = &expires
2✔
672
        }
2✔
673

674
        err = u.db.SaveToken(ctx, t)
2✔
675
        if err == store.ErrDuplicateTokenName {
4✔
676
                return "", ErrDuplicateTokenName
2✔
677
        } else if err != nil {
5✔
678
                return "", errors.Wrap(err, "useradm: failed to save token")
1✔
679
        }
1✔
680

681
        if _, ok := u.jwtHandlers[keyId]; !ok {
2✔
682
                return "", common.ErrKeyIdNotFound
×
683
        }
×
684
        // sign token
685
        return u.jwtHandlers[keyId].ToJWT(t)
2✔
686
}
687

688
func (ua *UserAdm) GetPersonalAccessTokens(
689
        ctx context.Context,
690
        userID string,
691
) ([]model.PersonalAccessToken, error) {
1✔
692
        tokens, err := ua.db.GetPersonalAccessTokens(ctx, userID)
1✔
693
        if err != nil {
1✔
694
                return nil, errors.Wrap(err, "useradm: failed to get tokens")
×
695
        }
×
696

697
        return tokens, nil
1✔
698
}
699

700
func (ua *UserAdm) DeleteToken(ctx context.Context, id string) error {
1✔
701
        identity := identity.FromContext(ctx)
1✔
702
        if identity == nil {
1✔
703
                return errors.New("identity not present in the context")
×
704
        }
×
705
        err := ua.db.DeleteToken(ctx, oid.FromString(identity.Subject), oid.FromString(id))
1✔
706
        if err != nil {
1✔
707
                return errors.Wrap(err, "useradm: failed to delete token")
×
708
        }
×
709

710
        return nil
1✔
711
}
712

713
func (ua *UserAdm) GetPlans(ctx context.Context, skip, limit int) []model.Plan {
1✔
714
        if (skip + limit) <= len(model.PlanList) {
2✔
715
                return model.PlanList[skip:(skip + limit)]
1✔
716
        } else if skip <= len(model.PlanList) {
3✔
717
                return model.PlanList[skip:]
1✔
718
        }
1✔
719
        return []model.Plan{}
1✔
720
}
721

722
func (ua *UserAdm) GetPlanBinding(ctx context.Context) (*model.PlanBindingDetails, error) {
1✔
723
        if len(model.PlanList) == 1 {
2✔
724
                return &model.PlanBindingDetails{
1✔
725
                        Plan: model.PlanList[0],
1✔
726
                }, nil
1✔
727
        }
1✔
728
        return &model.PlanBindingDetails{}, nil
1✔
729
}
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