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

mendersoftware / useradm / 1830670534

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

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

80.59
/store/mongo/datastore_mongo.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 mongo
16

17
import (
18
        "context"
19
        "crypto/tls"
20
        "strings"
21
        "time"
22

23
        _ "github.com/mendersoftware/go-lib-micro/mongo/codec"
24
        "github.com/mendersoftware/go-lib-micro/mongo/oid"
25
        mstore "github.com/mendersoftware/go-lib-micro/store/v2"
26
        "github.com/pkg/errors"
27
        "go.mongodb.org/mongo-driver/bson"
28
        "go.mongodb.org/mongo-driver/mongo"
29
        mopts "go.mongodb.org/mongo-driver/mongo/options"
30
        "golang.org/x/crypto/bcrypt"
31

32
        "github.com/mendersoftware/useradm/jwt"
33
        "github.com/mendersoftware/useradm/model"
34
        "github.com/mendersoftware/useradm/store"
35
)
36

37
const (
38
        DbUsersColl        = "users"
39
        DbTokensColl       = "tokens"
40
        DbSettingsColl     = "settings"
41
        DbUserSettingsColl = "user_settings"
42

43
        DbUserEmail       = "email"
44
        DbUserPass        = "password"
45
        DbUserLoginTs     = "login_ts"
46
        DbTokenSubject    = "sub"
47
        DbTokenExpiresAt  = "exp"
48
        DbTokenExpireTime = "exp.time"
49
        DbTokenIssuedAt   = "iat"
50
        DbTokenTenant     = "tenant"
51
        DbTokenUser       = "user"
52
        DbTokenIssuer     = "iss"
53
        DbTokenScope      = "scp"
54
        DbTokenAudience   = "aud"
55
        DbTokenNotBefore  = "nbf"
56
        DbTokenLastUsed   = "last_used"
57
        DbTokenName       = "name"
58
        DbID              = "_id"
59

60
        DbTokenIssuedAtTime = DbTokenIssuedAt + ".time"
61

62
        DbUniqueEmailIndexName           = "email_1"
63
        DbUniqueTokenNameIndexName       = "token_name_1"
64
        DbTokenSubjectIndexName          = "token_subject_1"
65
        brokenDbTokenExpirationIndexName = "token_expiration"
66

67
        DbTenantUniqueTokenNameIndexName = "tenant_1_subject_1_name_1"
68
        DbTenantTokenSubjectIndexName    = "tenant_1_subject_1"
69

70
        DbSettingsEtag            = "etag"
71
        DbSettingsTenantIndexName = "tenant"
72
        DbSettingsUserID          = "user_id"
73
)
74

75
type DataStoreMongoConfig struct {
76
        // MGO connection string
77
        ConnectionString string
78

79
        // SSL support
80
        SSL           bool
81
        SSLSkipVerify bool
82

83
        // Overwrites credentials provided in connection string if provided
84
        Username string
85
        Password string
86
}
87

88
type DataStoreMongo struct {
89
        client      *mongo.Client
90
        automigrate bool
91
        multitenant bool
92
}
93

94
func GetDataStoreMongo(config DataStoreMongoConfig) (*DataStoreMongo, error) {
×
95
        d, err := NewDataStoreMongo(config)
×
96
        if err != nil {
×
97
                return nil, errors.Wrap(err, "database connection failed")
×
98
        }
×
99
        return d, nil
×
100
}
101

102
func NewDataStoreMongoWithClient(client *mongo.Client) (*DataStoreMongo, error) {
90✔
103

90✔
104
        db := &DataStoreMongo{
90✔
105
                client: client,
90✔
106
        }
90✔
107

90✔
108
        return db, nil
90✔
109
}
90✔
110

111
func NewDataStoreMongo(config DataStoreMongoConfig) (*DataStoreMongo, error) {
×
112
        var err error
×
113
        var mongoURL string
×
114

×
115
        clientOptions := mopts.Client()
×
116
        if !strings.Contains(config.ConnectionString, "://") {
×
117
                mongoURL = "mongodb://" + config.ConnectionString
×
118
        } else {
×
119
                mongoURL = config.ConnectionString
×
120

×
121
        }
×
122
        clientOptions.ApplyURI(mongoURL)
×
123

×
124
        if config.Username != "" {
×
125
                credentials := mopts.Credential{
×
126
                        Username: config.Username,
×
127
                }
×
128
                if config.Password != "" {
×
129
                        credentials.Password = config.Password
×
130
                        credentials.PasswordSet = true
×
131
                }
×
132
                clientOptions.SetAuth(credentials)
×
133
        }
134

135
        if config.SSL {
×
136
                tlsConfig := &tls.Config{
×
137
                        InsecureSkipVerify: config.SSLSkipVerify,
×
138
                }
×
139
                clientOptions.SetTLSConfig(tlsConfig)
×
140
        }
×
141

142
        ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
×
143
        defer cancel()
×
144
        c, err := mongo.Connect(ctx, clientOptions)
×
145
        if err != nil {
×
146
                return nil, err
×
147
        }
×
148

149
        // Validate connection
150
        if err = c.Ping(ctx, nil); err != nil {
×
151
                return nil, err
×
152
        }
×
153

154
        db, err := NewDataStoreMongoWithClient(c)
×
155
        if err != nil {
×
156
                return nil, err
×
157
        }
×
158

159
        return db, nil
×
160
}
161

162
func (db *DataStoreMongo) Ping(ctx context.Context) error {
1✔
163
        res := db.client.Database(DbName).RunCommand(ctx, bson.M{"ping": 1})
1✔
164
        return res.Err()
1✔
165
}
1✔
166

167
func (db *DataStoreMongo) CreateUser(ctx context.Context, u *model.User) error {
7✔
168
        now := time.Now().UTC()
7✔
169

7✔
170
        u.CreatedTs = &now
7✔
171
        u.UpdatedTs = &now
7✔
172

7✔
173
        _, err := db.client.
7✔
174
                Database(mstore.DbFromContext(ctx, DbName)).
7✔
175
                Collection(DbUsersColl).
7✔
176
                InsertOne(ctx, mstore.WithTenantID(ctx, u))
7✔
177

7✔
178
        if err != nil {
11✔
179
                if strings.Contains(err.Error(), "duplicate key error") {
8✔
180
                        return store.ErrDuplicateEmail
4✔
181
                }
4✔
182

183
                return errors.Wrap(err, "failed to insert user")
×
184
        }
185

186
        return nil
3✔
187
}
188

189
func isDuplicateKeyError(err error) bool {
17✔
190
        const errCodeDupKey = 11000
17✔
191
        switch errType := err.(type) {
17✔
192
        case mongo.WriteException:
×
193
                if len(errType.WriteErrors) > 0 {
×
194
                        for _, we := range errType.WriteErrors {
×
195
                                if we.Code == errCodeDupKey {
×
196
                                        return true
×
197
                                }
×
198
                        }
199
                }
200
        case mongo.CommandError:
1✔
201
                if errType.Code == errCodeDupKey {
2✔
202
                        return true
1✔
203
                }
1✔
204
        }
205
        return false
16✔
206
}
207

208
// UpdateUser updates the user of a given ID.
209
// NOTE: This function supports using ETag matching using UserUpdate,
210
// but defaults to wildcard matching
211
func (db *DataStoreMongo) UpdateUser(
212
        ctx context.Context,
213
        id string,
214
        u *model.UserUpdate,
215
) (*model.User, error) {
8✔
216
        var updatedUser = new(model.User)
8✔
217
        //compute/set password hash
8✔
218
        if u.Password != "" {
15✔
219
                hash, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost)
7✔
220
                if err != nil {
7✔
221
                        return nil, errors.Wrap(err,
×
222
                                "failed to generate password hash")
×
223
                }
×
224
                u.Password = string(hash)
7✔
225
        }
226

227
        now := time.Now().UTC()
8✔
228
        u.UpdatedTs = &now
8✔
229

8✔
230
        collUsers := db.client.
8✔
231
                Database(mstore.DbFromContext(ctx, DbName)).
8✔
232
                Collection(DbUsersColl)
8✔
233

8✔
234
        f := bson.M{"_id": id}
8✔
235
        if u.ETag != nil {
10✔
236
                if *u.ETag == model.ETagNil {
2✔
237
                        f["etag"] = bson.D{{Key: "$exists", Value: false}}
×
238
                } else {
2✔
239
                        f["etag"] = *u.ETag
2✔
240
                }
2✔
241
                if u.ETagUpdate == nil {
2✔
242
                        next := *u.ETag
×
243
                        next.Increment()
×
244
                        u.ETagUpdate = &next
×
245
                }
×
246
        }
247
        up := bson.M{"$set": u}
8✔
248
        fuOpts := mopts.FindOneAndUpdate().
8✔
249
                SetReturnDocument(mopts.Before)
8✔
250
        err := collUsers.FindOneAndUpdate(ctx, mstore.WithTenantID(ctx, f), up, fuOpts).
8✔
251
                Decode(updatedUser)
8✔
252

8✔
253
        switch {
8✔
254
        case err == mongo.ErrNoDocuments:
2✔
255
                return nil, store.ErrUserNotFound
2✔
256
        case isDuplicateKeyError(err):
1✔
257
                return nil, store.ErrDuplicateEmail
1✔
258
        case err != nil:
×
259
                return nil, errors.Wrap(err, "store: failed to update user")
×
260
        }
261

262
        return updatedUser, nil
5✔
263
}
264

265
func (db *DataStoreMongo) UpdateLoginTs(ctx context.Context, id string) error {
1✔
266
        collUsrs := db.client.
1✔
267
                Database(mstore.DbFromContext(ctx, DbName)).
1✔
268
                Collection(DbUsersColl)
1✔
269

1✔
270
        _, err := collUsrs.UpdateOne(ctx,
1✔
271
                mstore.WithTenantID(ctx, bson.D{{Key: "_id", Value: id}}),
1✔
272
                bson.D{{Key: "$set", Value: bson.D{
1✔
273
                        {Key: DbUserLoginTs, Value: time.Now()}},
1✔
274
                }},
1✔
275
        )
1✔
276
        return err
1✔
277
}
1✔
278

279
func (db *DataStoreMongo) GetUserByEmail(
280
        ctx context.Context,
281
        email model.Email,
282
) (*model.User, error) {
5✔
283
        var user model.User
5✔
284

5✔
285
        err := db.client.Database(mstore.DbFromContext(ctx, DbName)).
5✔
286
                Collection(DbUsersColl).
5✔
287
                FindOne(ctx, mstore.WithTenantID(ctx, bson.M{DbUserEmail: email})).
5✔
288
                Decode(&user)
5✔
289

5✔
290
        if err != nil {
7✔
291
                if err == mongo.ErrNoDocuments {
4✔
292
                        return nil, nil
2✔
293
                } else {
2✔
294
                        return nil, errors.Wrap(err, "failed to fetch user")
×
295
                }
×
296
        }
297

298
        return &user, nil
3✔
299
}
300

301
func (db *DataStoreMongo) GetUserById(ctx context.Context, id string) (*model.User, error) {
7✔
302
        user, err := db.GetUserAndPasswordById(ctx, id)
7✔
303
        if user != nil {
12✔
304
                user.Password = ""
5✔
305
        }
5✔
306
        return user, err
7✔
307
}
308

309
func (db *DataStoreMongo) GetUserAndPasswordById(
310
        ctx context.Context,
311
        id string,
312
) (*model.User, error) {
7✔
313
        var user model.User
7✔
314

7✔
315
        err := db.client.Database(mstore.DbFromContext(ctx, DbName)).
7✔
316
                Collection(DbUsersColl).
7✔
317
                FindOne(ctx, mstore.WithTenantID(ctx, bson.M{"_id": id})).
7✔
318
                Decode(&user)
7✔
319

7✔
320
        if err != nil {
9✔
321
                if err == mongo.ErrNoDocuments {
4✔
322
                        return nil, nil
2✔
323
                } else {
2✔
324
                        return nil, errors.Wrap(err, "failed to fetch user")
×
325
                }
×
326
        }
327

328
        return &user, nil
5✔
329
}
330

331
func (db *DataStoreMongo) GetTokenById(ctx context.Context, id oid.ObjectID) (*jwt.Token, error) {
5✔
332
        var token jwt.Token
5✔
333

5✔
334
        err := db.client.Database(mstore.DbFromContext(ctx, DbName)).
5✔
335
                Collection(DbTokensColl).
5✔
336
                FindOne(ctx, mstore.WithTenantID(ctx, bson.M{"_id": id})).
5✔
337
                Decode(&token)
5✔
338

5✔
339
        if err != nil {
7✔
340
                if err == mongo.ErrNoDocuments {
4✔
341
                        return nil, nil
2✔
342
                } else {
2✔
343
                        return nil, errors.Wrap(err, "failed to fetch token")
×
344
                }
×
345
        }
346

347
        return &token, nil
3✔
348
}
349

350
func (db *DataStoreMongo) GetUsers(
351
        ctx context.Context,
352
        fltr model.UserFilter,
353
) ([]model.User, error) {
8✔
354
        findOpts := mopts.Find().
8✔
355
                SetProjection(bson.M{DbUserPass: 0})
8✔
356

8✔
357
        collUsers := db.client.
8✔
358
                Database(mstore.DbFromContext(ctx, DbName)).
8✔
359
                Collection(DbUsersColl)
8✔
360

8✔
361
        var mgoFltr = bson.D{}
8✔
362
        if fltr.ID != nil {
9✔
363
                mgoFltr = append(mgoFltr, bson.E{Key: "_id", Value: bson.D{{
1✔
364
                        Key: "$in", Value: fltr.ID,
1✔
365
                }}})
1✔
366
        }
1✔
367
        if fltr.Email != nil {
9✔
368
                mgoFltr = append(mgoFltr, bson.E{Key: "email", Value: bson.D{{
1✔
369
                        Key: "$in", Value: fltr.Email,
1✔
370
                }}})
1✔
371
        }
1✔
372
        if fltr.CreatedAfter != nil {
9✔
373
                mgoFltr = append(mgoFltr, bson.E{
1✔
374
                        Key: "created_ts", Value: bson.D{{
1✔
375
                                Key: "$gt", Value: *fltr.CreatedAfter,
1✔
376
                        }},
1✔
377
                })
1✔
378
        }
1✔
379
        if fltr.CreatedBefore != nil {
9✔
380
                mgoFltr = append(mgoFltr, bson.E{
1✔
381
                        Key: "created_ts", Value: bson.D{{
1✔
382
                                Key: "$lt", Value: *fltr.CreatedBefore,
1✔
383
                        }},
1✔
384
                })
1✔
385
        }
1✔
386
        if fltr.UpdatedAfter != nil {
9✔
387
                mgoFltr = append(mgoFltr, bson.E{
1✔
388
                        Key: "updated_ts", Value: bson.D{{
1✔
389
                                Key: "$gt", Value: *fltr.UpdatedAfter,
1✔
390
                        }},
1✔
391
                })
1✔
392
        }
1✔
393
        if fltr.UpdatedBefore != nil {
9✔
394
                mgoFltr = append(mgoFltr, bson.E{
1✔
395
                        Key: "updated_ts", Value: bson.D{{
1✔
396
                                Key: "$lt", Value: *fltr.UpdatedBefore,
1✔
397
                        }},
1✔
398
                })
1✔
399
        }
1✔
400
        cur, err := collUsers.Find(ctx, mstore.WithTenantID(ctx, mgoFltr), findOpts)
8✔
401
        if err != nil {
9✔
402
                return nil, errors.Wrap(err, "store: failed to fetch users")
1✔
403
        }
1✔
404

405
        users := []model.User{}
7✔
406
        err = cur.All(ctx, &users)
7✔
407
        switch err {
7✔
408
        case nil, mongo.ErrNoDocuments:
7✔
409
                return users, nil
7✔
410
        default:
×
411
                return nil, errors.Wrap(err, "store: failed to decode users")
×
412
        }
413
}
414

415
func (db *DataStoreMongo) DeleteUser(ctx context.Context, id string) error {
3✔
416
        _, err := db.client.Database(mstore.DbFromContext(ctx, DbName)).
3✔
417
                Collection(DbUsersColl).
3✔
418
                DeleteOne(ctx, mstore.WithTenantID(ctx, bson.M{"_id": id}))
3✔
419

3✔
420
        if err != nil {
3✔
421
                return err
×
422
        }
×
423

424
        return nil
3✔
425
}
426

427
func (db *DataStoreMongo) SaveToken(ctx context.Context, token *jwt.Token) error {
11✔
428
        _, err := db.client.Database(mstore.DbFromContext(ctx, DbName)).
11✔
429
                Collection(DbTokensColl).
11✔
430
                InsertOne(ctx, mstore.WithTenantID(ctx, token))
11✔
431

11✔
432
        if isDuplicateKeyError(err) {
11✔
433
                return store.ErrDuplicateTokenName
×
434
        } else if err != nil {
11✔
435
                return errors.Wrap(err, "failed to store token")
×
436
        }
×
437
        return err
11✔
438
}
439

440
func (db *DataStoreMongo) EnsureSessionTokensLimit(ctx context.Context, userID oid.ObjectID,
441
        tokensLimit int) error {
3✔
442
        opts := &mopts.FindOptions{}
3✔
443
        opts.SetLimit(int64(tokensLimit))
3✔
444
        opts.SetSkip(int64(tokensLimit) - 1)
3✔
445
        opts.SetSort(bson.M{
3✔
446
                DbTokenIssuedAtTime: -1,
3✔
447
        })
3✔
448

3✔
449
        cur, err := db.client.Database(mstore.DbFromContext(ctx, DbName)).
3✔
450
                Collection(DbTokensColl).
3✔
451
                Find(ctx, mstore.WithTenantID(ctx, bson.M{
3✔
452
                        DbTokenSubject: userID,
3✔
453
                        DbTokenName:    bson.M{"$exists": false},
3✔
454
                }), opts)
3✔
455
        if err != nil {
3✔
456
                return err
×
457
        }
×
458

459
        tokens := []jwt.Token{}
3✔
460
        err = cur.All(ctx, &tokens)
3✔
461
        if err == mongo.ErrNoDocuments || len(tokens) == 0 {
3✔
462
                return nil
×
463
        } else if err != nil {
3✔
464
                return err
×
465
        }
×
466

467
        _, err = db.client.
3✔
468
                Database(mstore.DbFromContext(ctx, DbName)).
3✔
469
                Collection(DbTokensColl).
3✔
470
                DeleteMany(ctx, mstore.WithTenantID(ctx, bson.M{
3✔
471
                        DbTokenSubject: userID,
3✔
472
                        DbTokenName:    bson.M{"$exists": false},
3✔
473
                        DbTokenIssuedAtTime: bson.M{
3✔
474
                                "$lt": tokens[0].IssuedAt.Time,
3✔
475
                        },
3✔
476
                }))
3✔
477

3✔
478
        return err
3✔
479
}
480

481
// WithMultitenant enables multitenant support and returns a new datastore based
482
// on current one
483
func (db *DataStoreMongo) WithMultitenant() *DataStoreMongo {
1✔
484
        return &DataStoreMongo{
1✔
485
                client:      db.client,
1✔
486
                automigrate: db.automigrate,
1✔
487
                multitenant: true,
1✔
488
        }
1✔
489
}
1✔
490

491
// WithAutomigrate enables automatic migration and returns a new datastore based
492
// on current one
493
func (db *DataStoreMongo) WithAutomigrate() *DataStoreMongo {
27✔
494
        return &DataStoreMongo{
27✔
495
                client:      db.client,
27✔
496
                automigrate: true,
27✔
497
                multitenant: db.multitenant,
27✔
498
        }
27✔
499
}
27✔
500

501
func (db *DataStoreMongo) DeleteToken(ctx context.Context, userID, tokenID oid.ObjectID) error {
2✔
502
        _, err := db.client.
2✔
503
                Database(mstore.DbFromContext(ctx, DbName)).
2✔
504
                Collection(DbTokensColl).
2✔
505
                DeleteOne(ctx, mstore.WithTenantID(ctx, bson.M{DbID: tokenID, DbTokenSubject: userID}))
2✔
506
        return err
2✔
507
}
2✔
508

509
// deletes all tenant's tokens (identity in context)
510
func (db *DataStoreMongo) DeleteTokens(ctx context.Context) error {
3✔
511
        d, err := db.client.
3✔
512
                Database(mstore.DbFromContext(ctx, DbName)).
3✔
513
                Collection(DbTokensColl).
3✔
514
                DeleteMany(ctx, mstore.WithTenantID(ctx, bson.M{}))
3✔
515

3✔
516
        if err != nil {
3✔
517
                return err
×
518
        }
×
519

520
        if d.DeletedCount == 0 {
4✔
521
                return store.ErrTokenNotFound
1✔
522
        }
1✔
523

524
        return err
2✔
525
}
526

527
// deletes all user's tokens
528
func (db *DataStoreMongo) DeleteTokensByUserId(ctx context.Context, userId string) error {
3✔
529
        return db.DeleteTokensByUserIdExceptCurrentOne(ctx, userId, oid.ObjectID{})
3✔
530
}
3✔
531

532
// deletes all user's tokens except the current one
533
func (db *DataStoreMongo) DeleteTokensByUserIdExceptCurrentOne(
534
        ctx context.Context,
535
        userId string,
536
        tokenID oid.ObjectID,
537
) error {
4✔
538
        c := db.client.
4✔
539
                Database(mstore.DbFromContext(ctx, DbName)).
4✔
540
                Collection(DbTokensColl)
4✔
541

4✔
542
        id := oid.FromString(userId)
4✔
543
        filter := bson.M{
4✔
544
                "sub": id,
4✔
545
        }
4✔
546

4✔
547
        if tokenID != (oid.ObjectID{}) {
5✔
548
                filter["_id"] = bson.M{
1✔
549
                        "$ne": tokenID,
1✔
550
                }
1✔
551
        }
1✔
552

553
        _, err := c.DeleteMany(ctx, mstore.WithTenantID(ctx, filter))
4✔
554
        if err != nil {
4✔
555
                return errors.Wrap(err, "failed to remove tokens")
×
556
        }
×
557

558
        return nil
4✔
559
}
560

561
func (db *DataStoreMongo) SaveSettings(ctx context.Context, s *model.Settings, etag string) error {
6✔
562
        c := db.client.Database(mstore.DbFromContext(ctx, DbName)).
6✔
563
                Collection(DbSettingsColl)
6✔
564

6✔
565
        o := &mopts.ReplaceOptions{}
6✔
566
        o.SetUpsert(true)
6✔
567

6✔
568
        filters := bson.M{}
6✔
569
        if etag != "" {
8✔
570
                filters[DbSettingsEtag] = etag
2✔
571
        }
2✔
572
        _, err := c.ReplaceOne(ctx,
6✔
573
                mstore.WithTenantID(ctx, filters),
6✔
574
                mstore.WithTenantID(ctx, s),
6✔
575
                o,
6✔
576
        )
6✔
577
        if err != nil && !mongo.IsDuplicateKeyError(err) {
6✔
578
                return errors.Wrapf(err, "failed to store settings %v", s)
×
579
        } else if mongo.IsDuplicateKeyError(err) && etag != "" {
7✔
580
                return store.ErrETagMismatch
1✔
581
        }
1✔
582

583
        return err
5✔
584
}
585

586
func (db *DataStoreMongo) SaveUserSettings(ctx context.Context, userID string,
587
        s *model.Settings, etag string) error {
6✔
588
        c := db.client.Database(mstore.DbFromContext(ctx, DbName)).
6✔
589
                Collection(DbUserSettingsColl)
6✔
590

6✔
591
        o := &mopts.ReplaceOptions{}
6✔
592
        o.SetUpsert(true)
6✔
593

6✔
594
        filters := bson.M{
6✔
595
                DbSettingsUserID: userID,
6✔
596
        }
6✔
597
        if etag != "" {
8✔
598
                filters[DbSettingsEtag] = etag
2✔
599
        }
2✔
600
        settings := mstore.WithTenantID(ctx, s)
6✔
601
        settings = append(settings, bson.E{
6✔
602
                Key:   DbSettingsUserID,
6✔
603
                Value: userID,
6✔
604
        })
6✔
605
        _, err := c.ReplaceOne(ctx,
6✔
606
                mstore.WithTenantID(ctx, filters),
6✔
607
                settings,
6✔
608
                o,
6✔
609
        )
6✔
610
        if err != nil && !mongo.IsDuplicateKeyError(err) {
6✔
611
                return errors.Wrapf(err, "failed to store settings %v", s)
×
612
        } else if mongo.IsDuplicateKeyError(err) && etag != "" {
7✔
613
                return store.ErrETagMismatch
1✔
614
        }
1✔
615

616
        return err
5✔
617
}
618

619
func (db *DataStoreMongo) GetSettings(ctx context.Context) (*model.Settings, error) {
3✔
620
        c := db.client.Database(mstore.DbFromContext(ctx, DbName)).
3✔
621
                Collection(DbSettingsColl)
3✔
622

3✔
623
        var settings *model.Settings
3✔
624
        err := c.FindOne(ctx, mstore.WithTenantID(ctx, bson.M{})).Decode(&settings)
3✔
625

3✔
626
        switch err {
3✔
627
        case nil:
2✔
628
                return settings, nil
2✔
629
        case mongo.ErrNoDocuments:
1✔
630
                return nil, nil
1✔
631
        default:
×
632
                return nil, errors.Wrapf(err, "failed to get settings")
×
633
        }
634
}
635

636
func (db *DataStoreMongo) GetUserSettings(ctx context.Context,
637
        userID string) (*model.Settings, error) {
3✔
638
        c := db.client.Database(mstore.DbFromContext(ctx, DbName)).
3✔
639
                Collection(DbUserSettingsColl)
3✔
640

3✔
641
        filters := bson.M{
3✔
642
                DbSettingsUserID: userID,
3✔
643
        }
3✔
644
        var settings *model.Settings
3✔
645
        err := c.FindOne(ctx, mstore.WithTenantID(ctx, filters)).Decode(&settings)
3✔
646

3✔
647
        switch err {
3✔
648
        case nil:
2✔
649
                return settings, nil
2✔
650
        case mongo.ErrNoDocuments:
1✔
651
                return nil, nil
1✔
652
        default:
×
653
                return nil, errors.Wrapf(err, "failed to get settings")
×
654
        }
655
}
656

657
func (db *DataStoreMongo) GetPersonalAccessTokens(
658
        ctx context.Context,
659
        userID string,
660
) ([]model.PersonalAccessToken, error) {
6✔
661
        findOpts := mopts.Find().
6✔
662
                SetProjection(
6✔
663
                        bson.M{
6✔
664
                                DbID:             1,
6✔
665
                                DbTokenName:      1,
6✔
666
                                DbTokenExpiresAt: 1,
6✔
667
                                DbTokenLastUsed:  1,
6✔
668
                                DbTokenIssuedAt:  1,
6✔
669
                        },
6✔
670
                )
6✔
671

6✔
672
        collTokens := db.client.
6✔
673
                Database(mstore.DbFromContext(ctx, DbName)).
6✔
674
                Collection(DbTokensColl)
6✔
675

6✔
676
        var mgoFltr = bson.M{
6✔
677
                DbTokenSubject: oid.FromString(userID),
6✔
678
                DbTokenName:    bson.M{"$exists": true},
6✔
679
        }
6✔
680
        cur, err := collTokens.Find(ctx, mstore.WithTenantID(ctx, mgoFltr), findOpts)
6✔
681
        if err != nil {
6✔
682
                return nil, errors.Wrap(err, "store: failed to fetch tokens")
×
683
        }
×
684

685
        tokens := []model.PersonalAccessToken{}
6✔
686
        err = cur.All(ctx, &tokens)
6✔
687
        switch err {
6✔
688
        case nil, mongo.ErrNoDocuments:
6✔
689
                return tokens, nil
6✔
690
        default:
×
691
                return nil, errors.Wrap(err, "store: failed to decode tokens")
×
692
        }
693
}
694

695
func (db *DataStoreMongo) UpdateTokenLastUsed(ctx context.Context, id oid.ObjectID) error {
2✔
696
        collTokens := db.client.
2✔
697
                Database(mstore.DbFromContext(ctx, DbName)).
2✔
698
                Collection(DbTokensColl)
2✔
699

2✔
700
        _, err := collTokens.UpdateOne(ctx,
2✔
701
                mstore.WithTenantID(ctx, bson.D{{Key: DbID, Value: id}}),
2✔
702
                bson.D{{Key: "$set", Value: bson.D{
2✔
703
                        {Key: DbTokenLastUsed, Value: time.Now()}},
2✔
704
                }},
2✔
705
        )
2✔
706

2✔
707
        return err
2✔
708
}
2✔
709

710
func (db *DataStoreMongo) CountPersonalAccessTokens(
711
        ctx context.Context,
712
        userID string,
713
) (int64, error) {
2✔
714
        collTokens := db.client.
2✔
715
                Database(mstore.DbFromContext(ctx, DbName)).
2✔
716
                Collection(DbTokensColl)
2✔
717

2✔
718
        var mgoFltr = bson.M{
2✔
719
                DbTokenSubject: oid.FromString(userID),
2✔
720
                DbTokenName:    bson.M{"$exists": true},
2✔
721
        }
2✔
722
        count, err := collTokens.CountDocuments(ctx, mstore.WithTenantID(ctx, mgoFltr))
2✔
723
        if err != nil {
2✔
724
                return -1, errors.Wrap(err, "store: failed to count tokens")
×
725
        }
×
726
        return count, nil
2✔
727
}
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