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

mendersoftware / useradm / 1565757771

29 Nov 2024 07:56AM UTC coverage: 87.019%. Remained the same
1565757771

push

gitlab-ci

web-flow
Merge pull request #434 from alfrunes/1.22.x

chore(deps): Upgrade golang to latest

2869 of 3297 relevant lines covered (87.02%)

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

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

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

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

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

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

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

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

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

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

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

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

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

440✔
178
        if err != nil {
444✔
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
436✔
187
}
188

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

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

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

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

23✔
253
        switch {
23✔
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
20✔
263
}
264

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

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

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

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

142✔
290
        if err != nil {
145✔
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
139✔
299
}
300

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

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

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

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

328
        return &user, nil
168✔
329
}
330

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

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

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

347
        return &token, nil
108✔
348
}
349

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

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

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

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

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

424
        return nil
9✔
425
}
426

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

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

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

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

459
        tokens := []jwt.Token{}
131✔
460
        err = cur.All(ctx, &tokens)
131✔
461
        if err == mongo.ErrNoDocuments || len(tokens) == 0 {
259✔
462
                return nil
128✔
463
        } else if err != nil {
131✔
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 {
2✔
484
        return &DataStoreMongo{
2✔
485
                client:      db.client,
2✔
486
                automigrate: db.automigrate,
2✔
487
                multitenant: true,
2✔
488
        }
2✔
489
}
2✔
490

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

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

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

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

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

524
        return err
3✔
525
}
526

527
// deletes all user's tokens
528
func (db *DataStoreMongo) DeleteTokensByUserId(ctx context.Context, userId string) error {
13✔
529
        return db.DeleteTokensByUserIdExceptCurrentOne(ctx, userId, oid.ObjectID{})
13✔
530
}
13✔
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 {
27✔
538
        c := db.client.
27✔
539
                Database(mstore.DbFromContext(ctx, DbName)).
27✔
540
                Collection(DbTokensColl)
27✔
541

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

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

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

558
        return nil
27✔
559
}
560

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

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

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

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

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

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

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

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

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

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

5✔
707
        return err
5✔
708
}
5✔
709

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

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