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

mendersoftware / useradm / 1645624551

13 Sep 2024 11:58AM UTC coverage: 70.971% (-14.3%) from 85.226%
1645624551

push

gitlab-ci

web-flow
Merge pull request #432 from mzedel/chore/deprecate

Chore/deprecate

2792 of 3934 relevant lines covered (70.97%)

40.83 hits per line

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

86.86
/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) {
96✔
95
        d, err := NewDataStoreMongo(config)
96✔
96
        if err != nil {
96✔
97
                return nil, errors.Wrap(err, "database connection failed")
×
98
        }
×
99
        return d, nil
96✔
100
}
101

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

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

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

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

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

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

191✔
124
        if config.Username != "" {
191✔
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 {
191✔
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)
191✔
143
        defer cancel()
191✔
144
        c, err := mongo.Connect(ctx, clientOptions)
191✔
145
        if err != nil {
191✔
146
                return nil, err
×
147
        }
×
148

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

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

159
        return db, nil
191✔
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 {
102✔
168
        now := time.Now().UTC()
102✔
169

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

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

102✔
178
        if err != nil {
106✔
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
98✔
187
}
188

189
func isDuplicateKeyError(err error) bool {
66✔
190
        const errCodeDupKey = 11000
66✔
191
        switch errType := err.(type) {
66✔
192
        case mongo.WriteException:
2✔
193
                if len(errType.WriteErrors) > 0 {
4✔
194
                        for _, we := range errType.WriteErrors {
4✔
195
                                if we.Code == errCodeDupKey {
4✔
196
                                        return true
2✔
197
                                }
2✔
198
                        }
199
                }
200
        case mongo.CommandError:
1✔
201
                if errType.Code == errCodeDupKey {
2✔
202
                        return true
1✔
203
                }
1✔
204
        }
205
        return false
63✔
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) {
14✔
216
        var updatedUser = new(model.User)
14✔
217
        //compute/set password hash
14✔
218
        if u.Password != "" {
24✔
219
                hash, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost)
10✔
220
                if err != nil {
10✔
221
                        return nil, errors.Wrap(err,
×
222
                                "failed to generate password hash")
×
223
                }
×
224
                u.Password = string(hash)
10✔
225
        }
226

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

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

14✔
234
        f := bson.M{"_id": id}
14✔
235
        if u.ETag != nil {
16✔
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}
14✔
248
        fuOpts := mopts.FindOneAndUpdate().
14✔
249
                SetReturnDocument(mopts.Before)
14✔
250
        err := collUsers.FindOneAndUpdate(ctx, mstore.WithTenantID(ctx, f), up, fuOpts).
14✔
251
                Decode(updatedUser)
14✔
252

14✔
253
        switch {
14✔
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
11✔
263
}
264

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

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

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

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

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

298
        return &user, nil
34✔
299
}
300

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

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

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

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

328
        return &user, nil
34✔
329
}
330

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

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

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

347
        return &token, nil
12✔
348
}
349

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

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

36✔
361
        var mgoFltr = bson.D{}
36✔
362
        if fltr.ID != nil {
37✔
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 {
37✔
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 {
37✔
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 {
37✔
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 {
37✔
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 {
37✔
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)
36✔
401
        if err != nil {
37✔
402
                return nil, errors.Wrap(err, "store: failed to fetch users")
1✔
403
        }
1✔
404

405
        users := []model.User{}
35✔
406
        err = cur.All(ctx, &users)
35✔
407
        switch err {
35✔
408
        case nil, mongo.ErrNoDocuments:
35✔
409
                return users, nil
35✔
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 {
5✔
416
        _, err := db.client.Database(mstore.DbFromContext(ctx, DbName)).
5✔
417
                Collection(DbUsersColl).
5✔
418
                DeleteOne(ctx, mstore.WithTenantID(ctx, bson.M{"_id": id}))
5✔
419

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

424
        return nil
5✔
425
}
426

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

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

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

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

459
        tokens := []jwt.Token{}
30✔
460
        err = cur.All(ctx, &tokens)
30✔
461
        if err == mongo.ErrNoDocuments || len(tokens) == 0 {
57✔
462
                return nil
27✔
463
        } else if err != nil {
30✔
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 {
122✔
494
        return &DataStoreMongo{
122✔
495
                client:      db.client,
122✔
496
                automigrate: true,
122✔
497
                multitenant: db.multitenant,
122✔
498
        }
122✔
499
}
122✔
500

501
func (db *DataStoreMongo) DeleteToken(ctx context.Context, userID, tokenID oid.ObjectID) error {
4✔
502
        _, err := db.client.
4✔
503
                Database(mstore.DbFromContext(ctx, DbName)).
4✔
504
                Collection(DbTokensColl).
4✔
505
                DeleteOne(ctx, mstore.WithTenantID(ctx, bson.M{DbID: tokenID, DbTokenSubject: userID}))
4✔
506
        return err
4✔
507
}
4✔
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 {
6✔
529
        return db.DeleteTokensByUserIdExceptCurrentOne(ctx, userId, oid.ObjectID{})
6✔
530
}
6✔
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 {
12✔
538
        c := db.client.
12✔
539
                Database(mstore.DbFromContext(ctx, DbName)).
12✔
540
                Collection(DbTokensColl)
12✔
541

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

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

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

558
        return nil
12✔
559
}
560

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

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

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

583
        return err
7✔
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) {
6✔
620
        c := db.client.Database(mstore.DbFromContext(ctx, DbName)).
6✔
621
                Collection(DbSettingsColl)
6✔
622

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

6✔
626
        switch err {
6✔
627
        case nil:
5✔
628
                return settings, nil
5✔
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) {
8✔
661
        findOpts := mopts.Find().
8✔
662
                SetProjection(
8✔
663
                        bson.M{
8✔
664
                                DbID:             1,
8✔
665
                                DbTokenName:      1,
8✔
666
                                DbTokenExpiresAt: 1,
8✔
667
                                DbTokenLastUsed:  1,
8✔
668
                                DbTokenIssuedAt:  1,
8✔
669
                        },
8✔
670
                )
8✔
671

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

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

685
        tokens := []model.PersonalAccessToken{}
8✔
686
        err = cur.All(ctx, &tokens)
8✔
687
        switch err {
8✔
688
        case nil, mongo.ErrNoDocuments:
8✔
689
                return tokens, nil
8✔
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 {
3✔
696
        collTokens := db.client.
3✔
697
                Database(mstore.DbFromContext(ctx, DbName)).
3✔
698
                Collection(DbTokensColl)
3✔
699

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

3✔
707
        return err
3✔
708
}
3✔
709

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

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