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

mendersoftware / useradm / 801060822

pending completion
801060822

Pull #354

gitlab-ci

Fabio Tranchitella
feat: add support for returning never-expiring JWT tokens when logging in
Pull Request #354: feat: add support for never expiring PATs

33 of 35 new or added lines in 4 files covered. (94.29%)

85 existing lines in 4 files now uncovered.

2579 of 2883 relevant lines covered (89.46%)

127.77 hits per line

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

/store/mongo/datastore_mongo.go
1
// Copyright 2022 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
        DbTokenIssuedAt  = "iat"
49
        DbTokenTenant    = "tenant"
50
        DbTokenUser      = "user"
51
        DbTokenIssuer    = "iss"
52
        DbTokenScope     = "scp"
53
        DbTokenAudience  = "aud"
54
        DbTokenNotBefore = "nbf"
55
        DbTokenLastUsed  = "last_used"
56
        DbTokenName      = "name"
57
        DbID             = "_id"
58

59
        DbUniqueEmailIndexName     = "email_1"
60
        DbUniqueTokenNameIndexName = "token_name_1"
61
        DbTokenSubjectIndexName    = "token_subject_1"
62
        DbTokenExpirationIndexName = "token_expiration"
63

64
        DbTenantUniqueTokenNameIndexName = "tenant_1_subject_1_name_1"
65
        DbTenantTokenSubjectIndexName    = "tenant_1_subject_1"
66

67
        DbSettingsEtag            = "etag"
68
        DbSettingsTenantIndexName = "tenant"
69
        DbSettingsUserID          = "user_id"
70
)
71

72
type DataStoreMongoConfig struct {
73
        // MGO connection string
74
        ConnectionString string
75

76
        // SSL support
77
        SSL           bool
78
        SSLSkipVerify bool
79

80
        // Overwrites credentials provided in connection string if provided
81
        Username string
82
        Password string
83
}
84

85
type DataStoreMongo struct {
86
        client      *mongo.Client
87
        automigrate bool
88
        multitenant bool
89
}
90

91
func GetDataStoreMongo(config DataStoreMongoConfig) (*DataStoreMongo, error) {
431✔
92
        d, err := NewDataStoreMongo(config)
431✔
93
        if err != nil {
431✔
UNCOV
94
                return nil, errors.Wrap(err, "database connection failed")
×
UNCOV
95
        }
×
96
        return d, nil
431✔
97
}
98

99
func NewDataStoreMongoWithClient(client *mongo.Client) (*DataStoreMongo, error) {
832✔
100

832✔
101
        db := &DataStoreMongo{
832✔
102
                client: client,
832✔
103
        }
832✔
104

832✔
105
        return db, nil
832✔
106
}
832✔
107

108
func NewDataStoreMongo(config DataStoreMongoConfig) (*DataStoreMongo, error) {
660✔
109
        var err error
660✔
110
        var mongoURL string
660✔
111

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

×
118
        }
×
119
        clientOptions.ApplyURI(mongoURL)
660✔
120

660✔
121
        if config.Username != "" {
660✔
UNCOV
122
                credentials := mopts.Credential{
×
UNCOV
123
                        Username: config.Username,
×
124
                }
×
125
                if config.Password != "" {
×
126
                        credentials.Password = config.Password
×
127
                        credentials.PasswordSet = true
×
128
                }
×
129
                clientOptions.SetAuth(credentials)
×
130
        }
131

132
        if config.SSL {
660✔
UNCOV
133
                tlsConfig := &tls.Config{
×
UNCOV
134
                        InsecureSkipVerify: config.SSLSkipVerify,
×
135
                }
×
136
                clientOptions.SetTLSConfig(tlsConfig)
×
137
        }
×
138

139
        ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
660✔
140
        defer cancel()
660✔
141
        c, err := mongo.Connect(ctx, clientOptions)
660✔
142
        if err != nil {
660✔
UNCOV
143
                return nil, err
×
UNCOV
144
        }
×
145

146
        // Validate connection
147
        if err = c.Ping(ctx, nil); err != nil {
660✔
UNCOV
148
                return nil, err
×
UNCOV
149
        }
×
150

151
        db, err := NewDataStoreMongoWithClient(c)
660✔
152
        if err != nil {
660✔
UNCOV
153
                return nil, err
×
UNCOV
154
        }
×
155

156
        return db, nil
660✔
157
}
158

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

164
func (db *DataStoreMongo) CreateUser(ctx context.Context, u *model.User) error {
447✔
165
        now := time.Now().UTC()
447✔
166

447✔
167
        u.CreatedTs = &now
447✔
168
        u.UpdatedTs = &now
447✔
169

447✔
170
        _, err := db.client.
447✔
171
                Database(mstore.DbFromContext(ctx, DbName)).
447✔
172
                Collection(DbUsersColl).
447✔
173
                InsertOne(ctx, mstore.WithTenantID(ctx, u))
447✔
174

447✔
175
        if err != nil {
455✔
176
                if strings.Contains(err.Error(), "duplicate key error") {
16✔
177
                        return store.ErrDuplicateEmail
8✔
178
                }
8✔
179

UNCOV
180
                return errors.Wrap(err, "failed to insert user")
×
181
        }
182

183
        return nil
439✔
184
}
185

186
func isDuplicateKeyError(err error) bool {
209✔
187
        const errCodeDupKey = 11000
209✔
188
        switch errType := err.(type) {
209✔
189
        case mongo.WriteException:
6✔
190
                if len(errType.WriteErrors) > 0 {
12✔
191
                        for _, we := range errType.WriteErrors {
12✔
192
                                if we.Code == errCodeDupKey {
12✔
193
                                        return true
6✔
194
                                }
6✔
195
                        }
196
                }
197
        case mongo.CommandError:
2✔
198
                if errType.Code == errCodeDupKey {
4✔
199
                        return true
2✔
200
                }
2✔
201
        }
202
        return false
201✔
203
}
204

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

224
        now := time.Now().UTC()
31✔
225
        u.UpdatedTs = &now
31✔
226

31✔
227
        collUsers := db.client.
31✔
228
                Database(mstore.DbFromContext(ctx, DbName)).
31✔
229
                Collection(DbUsersColl)
31✔
230

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

31✔
250
        switch {
31✔
251
        case err == mongo.ErrNoDocuments:
4✔
252
                return nil, store.ErrUserNotFound
4✔
253
        case isDuplicateKeyError(err):
2✔
254
                return nil, store.ErrDuplicateEmail
2✔
UNCOV
255
        case err != nil:
×
UNCOV
256
                return nil, errors.Wrap(err, "store: failed to update user")
×
257
        }
258

259
        return updatedUser, nil
25✔
260
}
261

262
func (db *DataStoreMongo) UpdateLoginTs(ctx context.Context, id string) error {
130✔
263
        collUsrs := db.client.
130✔
264
                Database(mstore.DbFromContext(ctx, DbName)).
130✔
265
                Collection(DbUsersColl)
130✔
266

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

276
func (db *DataStoreMongo) GetUserByEmail(
277
        ctx context.Context,
278
        email model.Email,
279
) (*model.User, error) {
147✔
280
        var user model.User
147✔
281

147✔
282
        err := db.client.Database(mstore.DbFromContext(ctx, DbName)).
147✔
283
                Collection(DbUsersColl).
147✔
284
                FindOne(ctx, mstore.WithTenantID(ctx, bson.M{DbUserEmail: email})).
147✔
285
                Decode(&user)
147✔
286

147✔
287
        if err != nil {
152✔
288
                if err == mongo.ErrNoDocuments {
10✔
289
                        return nil, nil
5✔
290
                } else {
5✔
UNCOV
291
                        return nil, errors.Wrap(err, "failed to fetch user")
×
UNCOV
292
                }
×
293
        }
294

295
        return &user, nil
142✔
296
}
297

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

306
func (db *DataStoreMongo) GetUserAndPasswordById(
307
        ctx context.Context,
308
        id string,
309
) (*model.User, error) {
183✔
310
        var user model.User
183✔
311

183✔
312
        err := db.client.Database(mstore.DbFromContext(ctx, DbName)).
183✔
313
                Collection(DbUsersColl).
183✔
314
                FindOne(ctx, mstore.WithTenantID(ctx, bson.M{"_id": id})).
183✔
315
                Decode(&user)
183✔
316

183✔
317
        if err != nil {
193✔
318
                if err == mongo.ErrNoDocuments {
20✔
319
                        return nil, nil
10✔
320
                } else {
10✔
UNCOV
321
                        return nil, errors.Wrap(err, "failed to fetch user")
×
UNCOV
322
                }
×
323
        }
324

325
        return &user, nil
173✔
326
}
327

328
func (db *DataStoreMongo) GetTokenById(ctx context.Context, id oid.ObjectID) (*jwt.Token, error) {
126✔
329
        var token jwt.Token
126✔
330

126✔
331
        err := db.client.Database(mstore.DbFromContext(ctx, DbName)).
126✔
332
                Collection(DbTokensColl).
126✔
333
                FindOne(ctx, mstore.WithTenantID(ctx, bson.M{"_id": id})).
126✔
334
                Decode(&token)
126✔
335

126✔
336
        if err != nil {
141✔
337
                if err == mongo.ErrNoDocuments {
30✔
338
                        return nil, nil
15✔
339
                } else {
15✔
UNCOV
340
                        return nil, errors.Wrap(err, "failed to fetch token")
×
UNCOV
341
                }
×
342
        }
343

344
        return &token, nil
111✔
345
}
346

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

355✔
354
        collUsers := db.client.
355✔
355
                Database(mstore.DbFromContext(ctx, DbName)).
355✔
356
                Collection(DbUsersColl)
355✔
357

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

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

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

12✔
417
        if err != nil {
12✔
UNCOV
418
                return err
×
UNCOV
419
        }
×
420

421
        return nil
12✔
422
}
423

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

182✔
429
        if isDuplicateKeyError(err) {
188✔
430
                return store.ErrDuplicateTokenName
6✔
431
        } else if err != nil {
182✔
UNCOV
432
                return errors.Wrap(err, "failed to store token")
×
UNCOV
433
        }
×
434

435
        return nil
176✔
436
}
437

438
// WithMultitenant enables multitenant support and returns a new datastore based
439
// on current one
440
func (db *DataStoreMongo) WithMultitenant() *DataStoreMongo {
3✔
441
        return &DataStoreMongo{
3✔
442
                client:      db.client,
3✔
443
                automigrate: db.automigrate,
3✔
444
                multitenant: true,
3✔
445
        }
3✔
446
}
3✔
447

448
// WithAutomigrate enables automatic migration and returns a new datastore based
449
// on current one
450
func (db *DataStoreMongo) WithAutomigrate() *DataStoreMongo {
283✔
451
        return &DataStoreMongo{
283✔
452
                client:      db.client,
283✔
453
                automigrate: true,
283✔
454
                multitenant: db.multitenant,
283✔
455
        }
283✔
456
}
283✔
457

458
func (db *DataStoreMongo) DeleteToken(ctx context.Context, userID, tokenID oid.ObjectID) error {
8✔
459
        _, err := db.client.
8✔
460
                Database(mstore.DbFromContext(ctx, DbName)).
8✔
461
                Collection(DbTokensColl).
8✔
462
                DeleteOne(ctx, mstore.WithTenantID(ctx, bson.M{DbID: tokenID, DbTokenSubject: userID}))
8✔
463
        return err
8✔
464
}
8✔
465

466
// deletes all tenant's tokens (identity in context)
467
func (db *DataStoreMongo) DeleteTokens(ctx context.Context) error {
8✔
468
        d, err := db.client.
8✔
469
                Database(mstore.DbFromContext(ctx, DbName)).
8✔
470
                Collection(DbTokensColl).
8✔
471
                DeleteMany(ctx, mstore.WithTenantID(ctx, bson.M{}))
8✔
472

8✔
473
        if err != nil {
8✔
474
                return err
×
475
        }
×
476

477
        if d.DeletedCount == 0 {
11✔
478
                return store.ErrTokenNotFound
3✔
479
        }
3✔
480

481
        return err
5✔
482
}
483

484
// deletes all user's tokens
485
func (db *DataStoreMongo) DeleteTokensByUserId(ctx context.Context, userId string) error {
16✔
486
        return db.DeleteTokensByUserIdExceptCurrentOne(ctx, userId, oid.ObjectID{})
16✔
487
}
16✔
488

489
// deletes all user's tokens except the current one
490
func (db *DataStoreMongo) DeleteTokensByUserIdExceptCurrentOne(
491
        ctx context.Context,
492
        userId string,
493
        tokenID oid.ObjectID,
494
) error {
31✔
495
        c := db.client.
31✔
496
                Database(mstore.DbFromContext(ctx, DbName)).
31✔
497
                Collection(DbTokensColl)
31✔
498

31✔
499
        id := oid.FromString(userId)
31✔
500
        filter := bson.M{
31✔
501
                "sub": id,
31✔
502
        }
31✔
503

31✔
504
        if tokenID != (oid.ObjectID{}) {
46✔
505
                filter["_id"] = bson.M{
15✔
506
                        "$ne": tokenID,
15✔
507
                }
15✔
508
        }
15✔
509

510
        _, err := c.DeleteMany(ctx, mstore.WithTenantID(ctx, filter))
31✔
511
        if err != nil {
31✔
UNCOV
512
                return errors.Wrap(err, "failed to remove tokens")
×
UNCOV
513
        }
×
514

515
        return nil
31✔
516
}
517

518
func (db *DataStoreMongo) SaveSettings(ctx context.Context, s *model.Settings, etag string) error {
18✔
519
        c := db.client.Database(mstore.DbFromContext(ctx, DbName)).
18✔
520
                Collection(DbSettingsColl)
18✔
521

18✔
522
        o := &mopts.ReplaceOptions{}
18✔
523
        o.SetUpsert(true)
18✔
524

18✔
525
        filters := bson.M{}
18✔
526
        if etag != "" {
22✔
527
                filters[DbSettingsEtag] = etag
4✔
528
        }
4✔
529
        _, err := c.ReplaceOne(ctx,
18✔
530
                mstore.WithTenantID(ctx, filters),
18✔
531
                mstore.WithTenantID(ctx, s),
18✔
532
                o,
18✔
533
        )
18✔
534
        if err != nil && !mongo.IsDuplicateKeyError(err) {
18✔
UNCOV
535
                return errors.Wrapf(err, "failed to store settings %v", s)
×
536
        } else if mongo.IsDuplicateKeyError(err) && etag != "" {
20✔
537
                return store.ErrETagMismatch
2✔
538
        }
2✔
539

540
        return err
16✔
541
}
542

543
func (db *DataStoreMongo) SaveUserSettings(ctx context.Context, userID string,
544
        s *model.Settings, etag string) error {
12✔
545
        c := db.client.Database(mstore.DbFromContext(ctx, DbName)).
12✔
546
                Collection(DbUserSettingsColl)
12✔
547

12✔
548
        o := &mopts.ReplaceOptions{}
12✔
549
        o.SetUpsert(true)
12✔
550

12✔
551
        filters := bson.M{
12✔
552
                DbSettingsUserID: userID,
12✔
553
        }
12✔
554
        if etag != "" {
16✔
555
                filters[DbSettingsEtag] = etag
4✔
556
        }
4✔
557
        settings := mstore.WithTenantID(ctx, s)
12✔
558
        settings = append(settings, bson.E{
12✔
559
                Key:   DbSettingsUserID,
12✔
560
                Value: userID,
12✔
561
        })
12✔
562
        _, err := c.ReplaceOne(ctx,
12✔
563
                mstore.WithTenantID(ctx, filters),
12✔
564
                settings,
12✔
565
                o,
12✔
566
        )
12✔
567
        if err != nil && !mongo.IsDuplicateKeyError(err) {
12✔
UNCOV
568
                return errors.Wrapf(err, "failed to store settings %v", s)
×
569
        } else if mongo.IsDuplicateKeyError(err) && etag != "" {
14✔
570
                return store.ErrETagMismatch
2✔
571
        }
2✔
572

573
        return err
10✔
574
}
575

576
func (db *DataStoreMongo) GetSettings(ctx context.Context) (*model.Settings, error) {
13✔
577
        c := db.client.Database(mstore.DbFromContext(ctx, DbName)).
13✔
578
                Collection(DbSettingsColl)
13✔
579

13✔
580
        var settings *model.Settings
13✔
581
        err := c.FindOne(ctx, mstore.WithTenantID(ctx, bson.M{})).Decode(&settings)
13✔
582

13✔
583
        switch err {
13✔
584
        case nil:
11✔
585
                return settings, nil
11✔
586
        case mongo.ErrNoDocuments:
2✔
587
                return nil, nil
2✔
UNCOV
588
        default:
×
UNCOV
589
                return nil, errors.Wrapf(err, "failed to get settings")
×
590
        }
591
}
592

593
func (db *DataStoreMongo) GetUserSettings(ctx context.Context,
594
        userID string) (*model.Settings, error) {
6✔
595
        c := db.client.Database(mstore.DbFromContext(ctx, DbName)).
6✔
596
                Collection(DbUserSettingsColl)
6✔
597

6✔
598
        filters := bson.M{
6✔
599
                DbSettingsUserID: userID,
6✔
600
        }
6✔
601
        var settings *model.Settings
6✔
602
        err := c.FindOne(ctx, mstore.WithTenantID(ctx, filters)).Decode(&settings)
6✔
603

6✔
604
        switch err {
6✔
605
        case nil:
4✔
606
                return settings, nil
4✔
607
        case mongo.ErrNoDocuments:
2✔
608
                return nil, nil
2✔
UNCOV
609
        default:
×
610
                return nil, errors.Wrapf(err, "failed to get settings")
×
611
        }
612
}
613

614
func (db *DataStoreMongo) GetPersonalAccessTokens(
615
        ctx context.Context,
616
        userID string,
617
) ([]model.PersonalAccessToken, error) {
18✔
618
        findOpts := mopts.Find().
18✔
619
                SetProjection(
18✔
620
                        bson.M{
18✔
621
                                DbID:             1,
18✔
622
                                DbTokenName:      1,
18✔
623
                                DbTokenExpiresAt: 1,
18✔
624
                                DbTokenLastUsed:  1,
18✔
625
                                DbTokenIssuedAt:  1,
18✔
626
                        },
18✔
627
                )
18✔
628

18✔
629
        collTokens := db.client.
18✔
630
                Database(mstore.DbFromContext(ctx, DbName)).
18✔
631
                Collection(DbTokensColl)
18✔
632

18✔
633
        var mgoFltr = bson.M{
18✔
634
                DbTokenSubject: oid.FromString(userID),
18✔
635
                DbTokenName:    bson.M{"$exists": true},
18✔
636
        }
18✔
637
        cur, err := collTokens.Find(ctx, mstore.WithTenantID(ctx, mgoFltr), findOpts)
18✔
638
        if err != nil {
18✔
UNCOV
639
                return nil, errors.Wrap(err, "store: failed to fetch tokens")
×
UNCOV
640
        }
×
641

642
        tokens := []model.PersonalAccessToken{}
18✔
643
        err = cur.All(ctx, &tokens)
18✔
644
        switch err {
18✔
645
        case nil, mongo.ErrNoDocuments:
18✔
646
                return tokens, nil
18✔
UNCOV
647
        default:
×
UNCOV
648
                return nil, errors.Wrap(err, "store: failed to decode tokens")
×
649
        }
650
}
651

652
func (db *DataStoreMongo) UpdateTokenLastUsed(ctx context.Context, id oid.ObjectID) error {
7✔
653
        collTokens := db.client.
7✔
654
                Database(mstore.DbFromContext(ctx, DbName)).
7✔
655
                Collection(DbTokensColl)
7✔
656

7✔
657
        _, err := collTokens.UpdateOne(ctx,
7✔
658
                mstore.WithTenantID(ctx, bson.D{{Key: DbID, Value: id}}),
7✔
659
                bson.D{{Key: "$set", Value: bson.D{
7✔
660
                        {Key: DbTokenLastUsed, Value: time.Now()}},
7✔
661
                }},
7✔
662
        )
7✔
663

7✔
664
        return err
7✔
665
}
7✔
666

667
func (db *DataStoreMongo) CountPersonalAccessTokens(
668
        ctx context.Context,
669
        userID string,
670
) (int64, error) {
55✔
671
        collTokens := db.client.
55✔
672
                Database(mstore.DbFromContext(ctx, DbName)).
55✔
673
                Collection(DbTokensColl)
55✔
674

55✔
675
        var mgoFltr = bson.M{
55✔
676
                DbTokenSubject: oid.FromString(userID),
55✔
677
                DbTokenName:    bson.M{"$exists": true},
55✔
678
        }
55✔
679
        count, err := collTokens.CountDocuments(ctx, mstore.WithTenantID(ctx, mgoFltr))
55✔
680
        if err != nil {
55✔
681
                return -1, errors.Wrap(err, "store: failed to count tokens")
×
682
        }
×
683
        return count, nil
55✔
684
}
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