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

supabase / gotrue / 8370607605

21 Mar 2024 06:24AM UTC coverage: 65.015% (-0.2%) from 65.241%
8370607605

Pull #1474

github

J0
fix: split error codes
Pull Request #1474: feat: add custom sms hook

76 of 169 new or added lines in 12 files covered. (44.97%)

68 existing lines in 1 file now uncovered.

8006 of 12314 relevant lines covered (65.02%)

59.58 hits per line

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

80.25
/internal/models/user.go
1
package models
2

3
import (
4
        "context"
5
        "crypto/sha256"
6
        "database/sql"
7
        "encoding/base64"
8
        "fmt"
9
        "strings"
10
        "time"
11

12
        "github.com/gobuffalo/pop/v6"
13
        "github.com/gofrs/uuid"
14
        "github.com/pkg/errors"
15
        "github.com/supabase/auth/internal/crypto"
16
        "github.com/supabase/auth/internal/storage"
17
)
18

19
// User respresents a registered user with email/password authentication
20
type User struct {
21
        ID uuid.UUID `json:"id" db:"id"`
22

23
        Aud       string             `json:"aud" db:"aud"`
24
        Role      string             `json:"role" db:"role"`
25
        Email     storage.NullString `json:"email" db:"email"`
26
        IsSSOUser bool               `json:"-" db:"is_sso_user"`
27

28
        EncryptedPassword string     `json:"-" db:"encrypted_password"`
29
        EmailConfirmedAt  *time.Time `json:"email_confirmed_at,omitempty" db:"email_confirmed_at"`
30
        InvitedAt         *time.Time `json:"invited_at,omitempty" db:"invited_at"`
31

32
        Phone            storage.NullString `json:"phone" db:"phone"`
33
        PhoneConfirmedAt *time.Time         `json:"phone_confirmed_at,omitempty" db:"phone_confirmed_at"`
34

35
        ConfirmationToken  string     `json:"-" db:"confirmation_token"`
36
        ConfirmationSentAt *time.Time `json:"confirmation_sent_at,omitempty" db:"confirmation_sent_at"`
37

38
        // For backward compatibility only. Use EmailConfirmedAt or PhoneConfirmedAt instead.
39
        ConfirmedAt *time.Time `json:"confirmed_at,omitempty" db:"confirmed_at" rw:"r"`
40

41
        RecoveryToken  string     `json:"-" db:"recovery_token"`
42
        RecoverySentAt *time.Time `json:"recovery_sent_at,omitempty" db:"recovery_sent_at"`
43

44
        EmailChangeTokenCurrent  string     `json:"-" db:"email_change_token_current"`
45
        EmailChangeTokenNew      string     `json:"-" db:"email_change_token_new"`
46
        EmailChange              string     `json:"new_email,omitempty" db:"email_change"`
47
        EmailChangeSentAt        *time.Time `json:"email_change_sent_at,omitempty" db:"email_change_sent_at"`
48
        EmailChangeConfirmStatus int        `json:"-" db:"email_change_confirm_status"`
49

50
        PhoneChangeToken  string     `json:"-" db:"phone_change_token"`
51
        PhoneChange       string     `json:"new_phone,omitempty" db:"phone_change"`
52
        PhoneChangeSentAt *time.Time `json:"phone_change_sent_at,omitempty" db:"phone_change_sent_at"`
53

54
        ReauthenticationToken  string     `json:"-" db:"reauthentication_token"`
55
        ReauthenticationSentAt *time.Time `json:"reauthentication_sent_at,omitempty" db:"reauthentication_sent_at"`
56

57
        LastSignInAt *time.Time `json:"last_sign_in_at,omitempty" db:"last_sign_in_at"`
58

59
        AppMetaData  JSONMap `json:"app_metadata" db:"raw_app_meta_data"`
60
        UserMetaData JSONMap `json:"user_metadata" db:"raw_user_meta_data"`
61

62
        Factors    []Factor   `json:"factors,omitempty" has_many:"factors"`
63
        Identities []Identity `json:"identities" has_many:"identities"`
64

65
        CreatedAt   time.Time  `json:"created_at" db:"created_at"`
66
        UpdatedAt   time.Time  `json:"updated_at" db:"updated_at"`
67
        BannedUntil *time.Time `json:"banned_until,omitempty" db:"banned_until"`
68
        DeletedAt   *time.Time `json:"deleted_at,omitempty" db:"deleted_at"`
69
        IsAnonymous bool       `json:"is_anonymous" db:"is_anonymous"`
70

71
        DONTUSEINSTANCEID uuid.UUID `json:"-" db:"instance_id"`
72
}
73

74
// NewUser initializes a new user from an email, password and user data.
75
func NewUser(phone, email, password, aud string, userData map[string]interface{}) (*User, error) {
287✔
76
        passwordHash := ""
287✔
77

287✔
78
        if password != "" {
514✔
79
                pw, err := crypto.GenerateFromPassword(context.Background(), password)
227✔
80
                if err != nil {
227✔
81
                        return nil, err
×
82
                }
×
83

84
                passwordHash = pw
227✔
85
        }
86

87
        if userData == nil {
447✔
88
                userData = make(map[string]interface{})
160✔
89
        }
160✔
90

91
        id := uuid.Must(uuid.NewV4())
287✔
92
        user := &User{
287✔
93
                ID:                id,
287✔
94
                Aud:               aud,
287✔
95
                Email:             storage.NullString(strings.ToLower(email)),
287✔
96
                Phone:             storage.NullString(phone),
287✔
97
                UserMetaData:      userData,
287✔
98
                EncryptedPassword: passwordHash,
287✔
99
        }
287✔
100
        return user, nil
287✔
101
}
102

103
// TableName overrides the table name used by pop
104
func (User) TableName() string {
10,329✔
105
        tableName := "users"
10,329✔
106
        return tableName
10,329✔
107
}
10,329✔
108

109
// BeforeSave is invoked before the user is saved to the database
110
func (u *User) BeforeSave(tx *pop.Connection) error {
1,122✔
111
        if u.EmailConfirmedAt != nil && u.EmailConfirmedAt.IsZero() {
1,122✔
112
                u.EmailConfirmedAt = nil
×
113
        }
×
114
        if u.PhoneConfirmedAt != nil && u.PhoneConfirmedAt.IsZero() {
1,122✔
115
                u.PhoneConfirmedAt = nil
×
116
        }
×
117
        if u.InvitedAt != nil && u.InvitedAt.IsZero() {
1,122✔
118
                u.InvitedAt = nil
×
119
        }
×
120
        if u.ConfirmationSentAt != nil && u.ConfirmationSentAt.IsZero() {
1,122✔
121
                u.ConfirmationSentAt = nil
×
122
        }
×
123
        if u.RecoverySentAt != nil && u.RecoverySentAt.IsZero() {
1,126✔
124
                u.RecoverySentAt = nil
4✔
125
        }
4✔
126
        if u.EmailChangeSentAt != nil && u.EmailChangeSentAt.IsZero() {
1,124✔
127
                u.EmailChangeSentAt = nil
2✔
128
        }
2✔
129
        if u.PhoneChangeSentAt != nil && u.PhoneChangeSentAt.IsZero() {
1,122✔
130
                u.PhoneChangeSentAt = nil
×
131
        }
×
132
        if u.ReauthenticationSentAt != nil && u.ReauthenticationSentAt.IsZero() {
1,122✔
133
                u.ReauthenticationSentAt = nil
×
134
        }
×
135
        if u.LastSignInAt != nil && u.LastSignInAt.IsZero() {
1,122✔
136
                u.LastSignInAt = nil
×
137
        }
×
138
        if u.BannedUntil != nil && u.BannedUntil.IsZero() {
1,122✔
139
                u.BannedUntil = nil
×
140
        }
×
141
        return nil
1,122✔
142
}
143

144
// IsConfirmed checks if a user has already been
145
// registered and confirmed.
146
func (u *User) IsConfirmed() bool {
135✔
147
        return u.EmailConfirmedAt != nil
135✔
148
}
135✔
149

150
// HasBeenInvited checks if user has been invited
151
func (u *User) HasBeenInvited() bool {
7✔
152
        return u.InvitedAt != nil
7✔
153
}
7✔
154

155
// IsPhoneConfirmed checks if a user's phone has already been
156
// registered and confirmed.
157
func (u *User) IsPhoneConfirmed() bool {
20✔
158
        return u.PhoneConfirmedAt != nil
20✔
159
}
20✔
160

161
// SetRole sets the users Role to roleName
162
func (u *User) SetRole(tx *storage.Connection, roleName string) error {
64✔
163
        u.Role = strings.TrimSpace(roleName)
64✔
164
        return tx.UpdateOnly(u, "role")
64✔
165
}
64✔
166

167
// HasRole returns true when the users role is set to roleName
168
func (u *User) HasRole(roleName string) bool {
×
169
        return u.Role == roleName
×
170
}
×
171

172
// GetEmail returns the user's email as a string
173
func (u *User) GetEmail() string {
700✔
174
        return string(u.Email)
700✔
175
}
700✔
176

177
// GetPhone returns the user's phone number as a string
178
func (u *User) GetPhone() string {
603✔
179
        return string(u.Phone)
603✔
180
}
603✔
181

182
// UpdateUserMetaData sets all user data from a map of updates,
183
// ensuring that it doesn't override attributes that are not
184
// in the provided map.
185
func (u *User) UpdateUserMetaData(tx *storage.Connection, updates map[string]interface{}) error {
112✔
186
        if u.UserMetaData == nil {
112✔
187
                u.UserMetaData = updates
×
188
        } else {
112✔
189
                for key, value := range updates {
1,007✔
190
                        if value != nil {
1,786✔
191
                                u.UserMetaData[key] = value
891✔
192
                        } else {
895✔
193
                                delete(u.UserMetaData, key)
4✔
194
                        }
4✔
195
                }
196
        }
197
        return tx.UpdateOnly(u, "raw_user_meta_data")
112✔
198
}
199

200
// UpdateAppMetaData updates all app data from a map of updates
201
func (u *User) UpdateAppMetaData(tx *storage.Connection, updates map[string]interface{}) error {
183✔
202
        if u.AppMetaData == nil {
184✔
203
                u.AppMetaData = updates
1✔
204
        } else {
183✔
205
                for key, value := range updates {
499✔
206
                        if value != nil {
628✔
207
                                u.AppMetaData[key] = value
311✔
208
                        } else {
317✔
209
                                delete(u.AppMetaData, key)
6✔
210
                        }
6✔
211
                }
212
        }
213
        return tx.UpdateOnly(u, "raw_app_meta_data")
183✔
214
}
215

216
// UpdateAppMetaDataProviders updates the provider field in AppMetaData column
217
func (u *User) UpdateAppMetaDataProviders(tx *storage.Connection) error {
159✔
218
        providers, terr := FindProvidersByUser(tx, u)
159✔
219
        if terr != nil {
159✔
220
                return terr
×
221
        }
×
222
        payload := map[string]interface{}{
159✔
223
                "providers": providers,
159✔
224
        }
159✔
225
        if len(providers) > 0 {
292✔
226
                payload["provider"] = providers[0]
133✔
227
        }
133✔
228
        return u.UpdateAppMetaData(tx, payload)
159✔
229
}
230

231
// UpdateUserEmail updates the user's email to one of the identity's email
232
// if the current email used doesn't match any of the identities email
233
func (u *User) UpdateUserEmailFromIdentities(tx *storage.Connection) error {
4✔
234
        identities, terr := FindIdentitiesByUserID(tx, u.ID)
4✔
235
        if terr != nil {
4✔
236
                return terr
×
237
        }
×
238
        for _, i := range identities {
8✔
239
                if u.GetEmail() == i.GetEmail() {
5✔
240
                        // there's an existing identity that uses the same email
1✔
241
                        // so the user's email can be kept
1✔
242
                        return nil
1✔
243
                }
1✔
244
        }
245

246
        var primaryIdentity *Identity
3✔
247
        for _, i := range identities {
6✔
248
                if _, terr := FindUserByEmailAndAudience(tx, i.GetEmail(), u.Aud); terr != nil {
5✔
249
                        if IsNotFoundError(terr) {
4✔
250
                                // the identity's email is not used by another user
2✔
251
                                // so we can set it as the primary identity
2✔
252
                                primaryIdentity = i
2✔
253
                                break
2✔
254
                        }
255
                        return terr
×
256
                }
257
        }
258
        if primaryIdentity == nil {
4✔
259
                return UserEmailUniqueConflictError{}
1✔
260
        }
1✔
261
        // default to the first identity's email
262
        if terr := u.SetEmail(tx, primaryIdentity.GetEmail()); terr != nil {
2✔
263
                return terr
×
264
        }
×
265
        if primaryIdentity.GetEmail() == "" {
3✔
266
                u.EmailConfirmedAt = nil
1✔
267
                if terr := tx.UpdateOnly(u, "email_confirmed_at"); terr != nil {
1✔
268
                        return terr
×
269
                }
×
270
        }
271
        return nil
2✔
272
}
273

274
// SetEmail sets the user's email
275
func (u *User) SetEmail(tx *storage.Connection, email string) error {
7✔
276
        u.Email = storage.NullString(email)
7✔
277
        return tx.UpdateOnly(u, "email")
7✔
278
}
7✔
279

280
// SetPhone sets the user's phone
281
func (u *User) SetPhone(tx *storage.Connection, phone string) error {
6✔
282
        u.Phone = storage.NullString(phone)
6✔
283
        return tx.UpdateOnly(u, "phone")
6✔
284
}
6✔
285

286
func (u *User) SetPassword(ctx context.Context, password string) error {
8✔
287
        if password == "" {
8✔
288
                u.EncryptedPassword = ""
×
289
                return nil
×
290
        }
×
291

292
        pw, err := crypto.GenerateFromPassword(ctx, password)
8✔
293
        if err != nil {
9✔
294
                return err
1✔
295
        }
1✔
296

297
        u.EncryptedPassword = pw
7✔
298

7✔
299
        return nil
7✔
300
}
301

302
// UpdatePassword updates the user's password. Use SetPassword outside of a transaction first!
303
func (u *User) UpdatePassword(tx *storage.Connection, sessionID *uuid.UUID) error {
4✔
304
        if err := tx.UpdateOnly(u, "encrypted_password"); err != nil {
4✔
305
                return err
4✔
306
        }
4✔
307

4✔
308
        if sessionID == nil {
4✔
309
                // log out user from all sessions to ensure reauthentication after password change
4✔
310
                return Logout(tx, u.ID)
4✔
311
        } else {
4✔
312
                // log out user from all other sessions to ensure reauthentication after password change
4✔
313
                return LogoutAllExceptMe(tx, *sessionID, u.ID)
4✔
314
        }
4✔
315
}
4✔
316

4✔
317
// Authenticate a user from a password
4✔
UNCOV
318
func (u *User) Authenticate(ctx context.Context, password string) bool {
×
UNCOV
319
        err := crypto.CompareHashAndPassword(ctx, u.EncryptedPassword, password)
×
320
        return err == nil
321
}
6✔
322

2✔
323
// ConfirmReauthentication resets the reauthentication token
2✔
324
func (u *User) ConfirmReauthentication(tx *storage.Connection) error {
4✔
325
        u.ReauthenticationToken = ""
2✔
326
        return tx.UpdateOnly(u, "reauthentication_token")
2✔
327
}
2✔
328

329
// Confirm resets the confimation token and sets the confirm timestamp
330
func (u *User) Confirm(tx *storage.Connection) error {
331
        u.ConfirmationToken = ""
24✔
332
        now := time.Now()
24✔
333
        u.EmailConfirmedAt = &now
24✔
334
        return tx.UpdateOnly(u, "confirmation_token", "email_confirmed_at")
24✔
335
}
336

337
// ConfirmPhone resets the confimation token and sets the confirm timestamp
1✔
338
func (u *User) ConfirmPhone(tx *storage.Connection) error {
1✔
339
        u.ConfirmationToken = ""
1✔
340
        now := time.Now()
1✔
341
        u.PhoneConfirmedAt = &now
342
        return tx.UpdateOnly(u, "confirmation_token", "phone_confirmed_at")
343
}
109✔
344

109✔
345
// UpdateLastSignInAt update field last_sign_in_at for user according to specified field
109✔
346
func (u *User) UpdateLastSignInAt(tx *storage.Connection) error {
109✔
347
        return tx.UpdateOnly(u, "last_sign_in_at")
109✔
348
}
109✔
349

350
// ConfirmEmailChange confirm the change of email for a user
351
func (u *User) ConfirmEmailChange(tx *storage.Connection, status int) error {
6✔
352
        email := u.EmailChange
6✔
353

6✔
354
        u.Email = storage.NullString(email)
6✔
355
        u.EmailChange = ""
6✔
356
        u.EmailChangeTokenCurrent = ""
6✔
357
        u.EmailChangeTokenNew = ""
358
        u.EmailChangeConfirmStatus = status
359

159✔
360
        if err := tx.UpdateOnly(
159✔
361
                u,
159✔
362
                "email",
363
                "email_change",
364
                "email_change_token_current",
5✔
365
                "email_change_token_new",
5✔
366
                "email_change_confirm_status",
5✔
367
        ); err != nil {
5✔
368
                return err
5✔
369
        }
5✔
370

5✔
371
        if !u.IsConfirmed() {
5✔
372
                if err := u.Confirm(tx); err != nil {
5✔
373
                        return err
5✔
374
                }
5✔
375
        }
5✔
376

5✔
377
        identity, err := FindIdentityByIdAndProvider(tx, u.ID.String(), "email")
5✔
378
        if err != nil {
5✔
379
                if IsNotFoundError(err) {
5✔
380
                        // no email identity, not an error
5✔
381
                        return nil
×
382
                }
×
383
                return err
384
        }
10✔
385

5✔
UNCOV
386
        if _, ok := identity.IdentityData["email"]; ok {
×
UNCOV
387
                identity.IdentityData["email"] = email
×
388
                if err := tx.UpdateOnly(identity, "identity_data"); err != nil {
389
                        return err
390
                }
5✔
391
        }
5✔
UNCOV
392

×
UNCOV
393
        return nil
×
UNCOV
394
}
×
UNCOV
395

×
UNCOV
396
// ConfirmPhoneChange confirms the change of phone for a user
×
397
func (u *User) ConfirmPhoneChange(tx *storage.Connection) error {
398
        now := time.Now()
399
        phone := u.PhoneChange
10✔
400

5✔
401
        u.Phone = storage.NullString(phone)
5✔
UNCOV
402
        u.PhoneChange = ""
×
UNCOV
403
        u.PhoneChangeToken = ""
×
404
        u.PhoneConfirmedAt = &now
405

406
        if err := tx.UpdateOnly(
5✔
407
                u,
408
                "phone",
409
                "phone_change",
410
                "phone_change_token",
3✔
411
                "phone_confirmed_at",
3✔
412
        ); err != nil {
3✔
413
                return err
3✔
414
        }
3✔
415

3✔
416
        identity, err := FindIdentityByIdAndProvider(tx, u.ID.String(), "phone")
3✔
417
        if err != nil {
3✔
418
                if IsNotFoundError(err) {
3✔
419
                        // no phone identity, not an error
3✔
420
                        return nil
3✔
421
                }
3✔
422

3✔
423
                return err
3✔
424
        }
3✔
425

3✔
UNCOV
426
        if _, ok := identity.IdentityData["phone"]; ok {
×
UNCOV
427
                identity.IdentityData["phone"] = phone
×
428
        }
429

3✔
430
        if err := tx.UpdateOnly(identity, "identity_data"); err != nil {
3✔
431
                return err
×
432
        }
×
UNCOV
433

×
UNCOV
434
        return nil
×
435
}
UNCOV
436

×
437
// Recover resets the recovery token
438
func (u *User) Recover(tx *storage.Connection) error {
439
        u.RecoveryToken = ""
6✔
440
        return tx.UpdateOnly(u, "recovery_token")
3✔
441
}
3✔
442

443
// CountOtherUsers counts how many other users exist besides the one provided
3✔
444
func CountOtherUsers(tx *storage.Connection, id uuid.UUID) (int, error) {
×
445
        userCount, err := tx.Q().Where("instance_id = ? and id != ?", uuid.Nil, id).Count(&User{})
×
446
        return userCount, errors.Wrap(err, "error finding registered users")
447
}
3✔
448

449
func findUser(tx *storage.Connection, query string, args ...interface{}) (*User, error) {
450
        obj := &User{}
451
        if err := tx.Eager().Q().Where(query, args...).First(obj); err != nil {
8✔
452
                if errors.Cause(err) == sql.ErrNoRows {
8✔
453
                        return nil, UserNotFoundError{}
8✔
454
                }
8✔
455
                return nil, errors.Wrap(err, "error finding user")
456
        }
UNCOV
457

×
UNCOV
458
        return obj, nil
×
UNCOV
459
}
×
UNCOV
460

×
461
// FindUserByConfirmationToken finds users with the matching confirmation token.
462
func FindUserByConfirmationOrRecoveryToken(tx *storage.Connection, token string) (*User, error) {
771✔
463
        user, err := findUser(tx, "(confirmation_token = ? or recovery_token = ?) and is_sso_user = false", token, token)
771✔
464
        if err != nil {
996✔
465
                return nil, ConfirmationOrRecoveryTokenNotFoundError{}
450✔
466
        }
225✔
467
        return user, nil
225✔
UNCOV
468
}
×
469

470
// FindUserByConfirmationToken finds users with the matching confirmation token.
471
func FindUserByConfirmationToken(tx *storage.Connection, token string) (*User, error) {
546✔
472
        user, err := findUser(tx, "confirmation_token = ? and is_sso_user = false", token)
473
        if err != nil {
474
                return nil, ConfirmationTokenNotFoundError{}
475
        }
1✔
476
        return user, nil
1✔
477
}
1✔
UNCOV
478

×
UNCOV
479
// FindUserByEmailAndAudience finds a user with the matching email and audience.
×
480
func FindUserByEmailAndAudience(tx *storage.Connection, email, aud string) (*User, error) {
1✔
481
        return findUser(tx, "instance_id = ? and LOWER(email) = ? and aud = ? and is_sso_user = false", uuid.Nil, strings.ToLower(email), aud)
482
}
483

484
// FindUserByPhoneAndAudience finds a user with the matching email and audience.
120✔
485
func FindUserByPhoneAndAudience(tx *storage.Connection, phone, aud string) (*User, error) {
120✔
486
        return findUser(tx, "instance_id = ? and phone = ? and aud = ? and is_sso_user = false", uuid.Nil, phone, aud)
153✔
487
}
33✔
488

33✔
489
// FindUserByID finds a user matching the provided ID.
87✔
490
func FindUserByID(tx *storage.Connection, id uuid.UUID) (*User, error) {
491
        return findUser(tx, "instance_id = ? and id = ?", uuid.Nil, id)
492
}
493

377✔
494
// FindUserByRecoveryToken finds a user with the matching recovery token.
377✔
495
func FindUserByRecoveryToken(tx *storage.Connection, token string) (*User, error) {
377✔
496
        return findUser(tx, "recovery_token = ? and is_sso_user = false", token)
497
}
498

36✔
499
// FindUserByEmailChangeToken finds a user with the matching email change token.
36✔
500
func FindUserByEmailChangeToken(tx *storage.Connection, token string) (*User, error) {
36✔
501
        return findUser(tx, "is_sso_user = false and (email_change_token_current = ? or email_change_token_new = ?)", token, token)
502
}
503

213✔
504
// FindUserWithRefreshToken finds a user from the provided refresh token. If
213✔
505
// forUpdate is set to true, then the SELECT statement used by the query has
213✔
506
// the form SELECT ... FOR UPDATE SKIP LOCKED. This means that a FOR UPDATE
507
// lock will only be acquired if there's no other lock. In case there is a
508
// lock, a IsNotFound(err) error will be returned.
9✔
509
func FindUserWithRefreshToken(tx *storage.Connection, token string, forUpdate bool) (*User, *RefreshToken, *Session, error) {
9✔
510
        refreshToken := &RefreshToken{}
9✔
511

512
        if forUpdate {
513
                // pop does not provide us with a way to execute FOR UPDATE
11✔
514
                // queries which lock the rows affected by the query from
11✔
515
                // being accessed by any other transaction that also uses FOR
11✔
516
                // UPDATE
517
                if err := tx.RawQuery(fmt.Sprintf("SELECT * FROM %q WHERE token = ? LIMIT 1 FOR UPDATE SKIP LOCKED;", refreshToken.TableName()), token).First(refreshToken); err != nil {
518
                        if errors.Cause(err) == sql.ErrNoRows {
519
                                return nil, nil, nil, RefreshTokenNotFoundError{}
520
                        }
521

522
                        return nil, nil, nil, errors.Wrap(err, "error finding refresh token for update")
52✔
523
                }
52✔
524
        }
52✔
525

71✔
526
        // once the rows are locked (if forUpdate was true), we can query again using pop
19✔
527
        if err := tx.Where("token = ?", token).First(refreshToken); err != nil {
19✔
528
                if errors.Cause(err) == sql.ErrNoRows {
19✔
529
                        return nil, nil, nil, RefreshTokenNotFoundError{}
19✔
530
                }
19✔
531
                return nil, nil, nil, errors.Wrap(err, "error finding refresh token")
×
UNCOV
532
        }
×
UNCOV
533

×
534
        user, err := FindUserByID(tx, refreshToken.UserID)
UNCOV
535
        if err != nil {
×
536
                return nil, nil, nil, err
537
        }
538

539
        var session *Session
540

54✔
541
        if refreshToken.SessionId != nil {
4✔
542
                sessionId := *refreshToken.SessionId
2✔
543

2✔
UNCOV
544
                if sessionId != uuid.Nil {
×
545
                        session, err = FindSessionByID(tx, sessionId, forUpdate)
546
                        if err != nil {
547
                                if forUpdate {
50✔
548
                                        return nil, nil, nil, err
50✔
549
                                }
×
UNCOV
550

×
551
                                if !IsNotFoundError(err) {
552
                                        return nil, nil, nil, errors.Wrap(err, "error finding session from refresh token")
50✔
553
                                }
50✔
554

100✔
555
                                // otherwise, there's no session for this refresh token
50✔
556
                        }
50✔
557
                }
100✔
558
        }
50✔
559

50✔
UNCOV
560
        return user, refreshToken, session, nil
×
UNCOV
561
}
×
UNCOV
562

×
563
// FindUsersInAudience finds users with the matching audience.
UNCOV
564
func FindUsersInAudience(tx *storage.Connection, aud string, pageParams *Pagination, sortParams *SortParams, filter string) ([]*User, error) {
×
UNCOV
565
        users := []*User{}
×
UNCOV
566
        q := tx.Q().Where("instance_id = ? and aud = ?", uuid.Nil, aud)
×
567

568
        if filter != "" {
569
                lf := "%" + filter + "%"
570
                // we must specify the collation in order to get case insensitive search for the JSON column
571
                q = q.Where("(email LIKE ? OR raw_user_meta_data->>'full_name' ILIKE ?)", lf, lf)
572
        }
573

50✔
574
        if sortParams != nil && len(sortParams.Fields) > 0 {
575
                for _, field := range sortParams.Fields {
576
                        q = q.Order(field.Name + " " + string(field.Dir))
577
                }
9✔
578
        }
9✔
579

9✔
580
        var err error
9✔
581
        if pageParams != nil {
11✔
582
                err = q.Paginate(int(pageParams.Page), int(pageParams.PerPage)).All(&users)
2✔
583
                pageParams.Count = uint64(q.Paginator.TotalEntriesSize)
2✔
584
        } else {
2✔
585
                err = q.All(&users)
2✔
586
        }
587

16✔
588
        return users, err
14✔
589
}
7✔
590

7✔
591
// FindUserByEmailChangeCurrentAndAudience finds a user with the matching email change and audience.
592
func FindUserByEmailChangeCurrentAndAudience(tx *storage.Connection, email, token, aud string) (*User, error) {
593
        return findUser(
9✔
594
                tx,
16✔
595
                "instance_id = ? and LOWER(email) = ? and aud = ? and is_sso_user = false and (email_change_token_current = 'pkce_' || ? or email_change_token_current = ?)",
7✔
596
                uuid.Nil, strings.ToLower(email), aud, token, token,
7✔
597
        )
9✔
598
}
2✔
599

2✔
600
// FindUserByEmailChangeNewAndAudience finds a user with the matching email change and audience.
601
func FindUserByEmailChangeNewAndAudience(tx *storage.Connection, email, token, aud string) (*User, error) {
9✔
602
        return findUser(
603
                tx,
604
                "instance_id = ? and LOWER(email_change) = ? and aud = ? and is_sso_user = false and (email_change_token_new = 'pkce_' || ? or email_change_token_new = ?)",
605
                uuid.Nil, strings.ToLower(email), aud, token, token,
1✔
606
        )
1✔
607
}
1✔
608

1✔
609
// FindUserForEmailChange finds a user requesting for an email change
1✔
610
func FindUserForEmailChange(tx *storage.Connection, email, token, aud string, secureEmailChangeEnabled bool) (*User, error) {
1✔
611
        if secureEmailChangeEnabled {
1✔
612
                if user, err := FindUserByEmailChangeCurrentAndAudience(tx, email, token, aud); err == nil {
613
                        return user, err
614
                } else if !IsNotFoundError(err) {
1✔
615
                        return nil, err
1✔
616
                }
1✔
617
        }
1✔
618
        return FindUserByEmailChangeNewAndAudience(tx, email, token, aud)
1✔
619
}
1✔
620

1✔
621
// FindUserByPhoneChangeAndAudience finds a user with the matching phone change and audience.
622
func FindUserByPhoneChangeAndAudience(tx *storage.Connection, phone, aud string) (*User, error) {
623
        return findUser(tx, "instance_id = ? and phone_change = ? and aud = ? and is_sso_user = false", uuid.Nil, phone, aud)
1✔
624
}
2✔
625

1✔
UNCOV
626
// IsDuplicatedEmail returns whether a user exists with a matching email and audience.
×
627
// If a currentUser is provided, we will need to filter out any identities that belong to the current user.
1✔
UNCOV
628
func IsDuplicatedEmail(tx *storage.Connection, email, aud string, currentUser *User) (*User, error) {
×
UNCOV
629
        var identities []Identity
×
630

631
        if err := tx.Eager().Q().Where("email = ?", strings.ToLower(email)).All(&identities); err != nil {
1✔
632
                if errors.Cause(err) == sql.ErrNoRows {
633
                        return nil, nil
634
                }
635

2✔
636
                return nil, errors.Wrap(err, "unable to find identity by email for duplicates")
2✔
637
        }
2✔
638

639
        userIDs := make(map[string]uuid.UUID)
640
        for _, identity := range identities {
641
                if _, ok := userIDs[identity.UserID.String()]; !ok {
39✔
642
                        if !identity.IsForSSOProvider() {
39✔
643
                                userIDs[identity.UserID.String()] = identity.UserID
39✔
644
                        }
39✔
UNCOV
645
                }
×
UNCOV
646
        }
×
UNCOV
647

×
648
        var currentUserId uuid.UUID
UNCOV
649
        if currentUser != nil {
×
650
                currentUserId = currentUser.ID
651
        }
652

39✔
653
        for _, userID := range userIDs {
45✔
654
                if userID != currentUserId {
12✔
655
                        user, err := FindUserByID(tx, userID)
12✔
656
                        if err != nil {
6✔
657
                                return nil, errors.Wrap(err, "unable to find user from email identity for duplicates")
6✔
658
                        }
659
                        if user.Aud == aud {
660
                                return user, nil
661
                        }
39✔
662
                }
48✔
663
        }
9✔
664

9✔
665
        // out of an abundance of caution, if nothing was found via the
666
        // identities table we also do a final check on the users table
45✔
667
        user, err := FindUserByEmailAndAudience(tx, email, aud)
12✔
668
        if err != nil && !IsNotFoundError(err) {
6✔
669
                return nil, errors.Wrap(err, "unable to find user email address for duplicates")
6✔
670
        }
×
UNCOV
671

×
672
        return user, nil
11✔
673
}
5✔
674

5✔
675
// IsDuplicatedPhone checks if the phone number already exists in the users table
676
func IsDuplicatedPhone(tx *storage.Connection, phone, aud string) (bool, error) {
677
        _, err := FindUserByPhoneAndAudience(tx, phone, aud)
678
        if err != nil {
679
                if IsNotFoundError(err) {
680
                        return false, nil
34✔
681
                }
34✔
682
                return false, err
×
UNCOV
683
        }
×
684
        return true, nil
685
}
34✔
686

687
// Ban a user for a given duration.
688
func (u *User) Ban(tx *storage.Connection, duration time.Duration) error {
689
        if duration == time.Duration(0) {
9✔
690
                u.BannedUntil = nil
9✔
691
        } else {
17✔
692
                t := time.Now().Add(duration)
16✔
693
                u.BannedUntil = &t
8✔
694
        }
8✔
UNCOV
695
        return tx.UpdateOnly(u, "banned_until")
×
696
}
697

1✔
698
// IsBanned checks if a user is banned or not
699
func (u *User) IsBanned() bool {
700
        if u.BannedUntil == nil {
701
                return false
2✔
702
        }
2✔
UNCOV
703
        return time.Now().Before(*u.BannedUntil)
×
704
}
2✔
705

2✔
706
func (u *User) UpdateBannedUntil(tx *storage.Connection) error {
2✔
707
        return tx.UpdateOnly(u, "banned_until")
2✔
708
}
2✔
709

710
// RemoveUnconfirmedIdentities removes potentially malicious unconfirmed identities from a user (if any)
711
func (u *User) RemoveUnconfirmedIdentities(tx *storage.Connection, identity *Identity) error {
712
        if identity.Provider != "email" && identity.Provider != "phone" {
130✔
713
                // user is unconfirmed so the password should be reset
249✔
714
                u.EncryptedPassword = ""
119✔
715
                if terr := tx.UpdateOnly(u, "encrypted_password"); terr != nil {
119✔
716
                        return terr
11✔
717
                }
718
        }
UNCOV
719

×
UNCOV
720
        // user is unconfirmed so existing user_metadata should be overwritten
×
UNCOV
721
        // to use the current identity metadata
×
722
        u.UserMetaData = identity.IdentityData
723
        if terr := u.UpdateUserMetaData(tx, u.UserMetaData); terr != nil {
724
                return terr
81✔
725
        }
144✔
726

63✔
727
        // finally, remove all identities except the current identity being authenticated
63✔
728
        for i := range u.Identities {
63✔
UNCOV
729
                if u.Identities[i].ID != identity.ID {
×
UNCOV
730
                        if terr := tx.Destroy(&u.Identities[i]); terr != nil {
×
731
                                return terr
732
                        }
733
                }
734
        }
735

81✔
736
        // user is unconfirmed so none of the providers associated to it are verified yet
81✔
UNCOV
737
        // only the current provider should be kept
×
UNCOV
738
        if terr := u.UpdateAppMetaDataProviders(tx); terr != nil {
×
739
                return terr
740
        }
741
        return nil
121✔
742
}
79✔
743

39✔
UNCOV
744
// SoftDeleteUser performs a soft deletion on the user by obfuscating and clearing certain fields
×
UNCOV
745
func (u *User) SoftDeleteUser(tx *storage.Connection) error {
×
746
        u.Email = storage.NullString(obfuscateEmail(u, u.GetEmail()))
747
        u.Phone = storage.NullString(obfuscatePhone(u, u.GetPhone()))
748
        u.EmailChange = obfuscateEmail(u, u.EmailChange)
749
        u.PhoneChange = obfuscatePhone(u, u.PhoneChange)
750
        u.EncryptedPassword = ""
751
        u.ConfirmationToken = ""
81✔
UNCOV
752
        u.RecoveryToken = ""
×
UNCOV
753
        u.EmailChangeTokenCurrent = ""
×
754
        u.EmailChangeTokenNew = ""
81✔
755
        u.PhoneChangeToken = ""
756

757
        // set deleted_at time
758
        now := time.Now()
3✔
759
        u.DeletedAt = &now
3✔
760

3✔
761
        if err := tx.UpdateOnly(
3✔
762
                u,
3✔
763
                "email",
3✔
764
                "phone",
3✔
765
                "encrypted_password",
3✔
766
                "email_change",
3✔
767
                "phone_change",
3✔
768
                "confirmation_token",
3✔
769
                "recovery_token",
3✔
770
                "email_change_token_current",
3✔
771
                "email_change_token_new",
3✔
772
                "phone_change_token",
3✔
773
                "deleted_at",
3✔
774
        ); err != nil {
3✔
775
                return err
3✔
776
        }
3✔
777

3✔
778
        // set raw_user_meta_data to {}
3✔
779
        userMetaDataUpdates := map[string]interface{}{}
3✔
780
        for k := range u.UserMetaData {
3✔
781
                userMetaDataUpdates[k] = nil
3✔
782
        }
3✔
783

3✔
784
        if err := u.UpdateUserMetaData(tx, userMetaDataUpdates); err != nil {
3✔
785
                return err
3✔
786
        }
3✔
787

3✔
UNCOV
788
        // set raw_app_meta_data to {}
×
UNCOV
789
        appMetaDataUpdates := map[string]interface{}{}
×
790
        for k := range u.AppMetaData {
791
                appMetaDataUpdates[k] = nil
792
        }
3✔
793

6✔
794
        if err := u.UpdateAppMetaData(tx, appMetaDataUpdates); err != nil {
3✔
795
                return err
3✔
796
        }
797

3✔
UNCOV
798
        return nil
×
UNCOV
799
}
×
800

801
// SoftDeleteUserIdentities performs a soft deletion on all identities associated to a user
802
func (u *User) SoftDeleteUserIdentities(tx *storage.Connection) error {
3✔
803
        identities, err := FindIdentitiesByUserID(tx, u.ID)
8✔
804
        if err != nil {
5✔
805
                return err
5✔
806
        }
807

3✔
UNCOV
808
        // set identity_data to {}
×
UNCOV
809
        for _, identity := range identities {
×
810
                identityDataUpdates := map[string]interface{}{}
811
                for k := range identity.IdentityData {
3✔
812
                        identityDataUpdates[k] = nil
813
                }
814
                if err := identity.UpdateIdentityData(tx, identityDataUpdates); err != nil {
815
                        return err
3✔
816
                }
3✔
817
                // updating the identity.ID has to happen last since the primary key is on (provider, id)
3✔
UNCOV
818
                // we use RawQuery here instead of UpdateOnly because UpdateOnly relies on the primary key of Identity
×
UNCOV
819
                if err := tx.RawQuery(
×
820
                        "update "+
821
                                (&pop.Model{Value: Identity{}}).TableName()+
822
                                " set provider_id = ? where id = ?",
5✔
823
                        obfuscateIdentityProviderId(identity),
2✔
824
                        identity.ID,
6✔
825
                ).Exec(); err != nil {
4✔
826
                        return err
4✔
827
                }
2✔
UNCOV
828
        }
×
UNCOV
829
        return nil
×
830
}
831

832
func obfuscateValue(id uuid.UUID, value string) string {
2✔
833
        hash := sha256.Sum256([]byte(id.String() + value))
2✔
834
        return base64.RawURLEncoding.EncodeToString(hash[:])
2✔
835
}
2✔
836

2✔
837
func obfuscateEmail(u *User, email string) string {
2✔
838
        return obfuscateValue(u.ID, email)
2✔
UNCOV
839
}
×
UNCOV
840

×
841
func obfuscatePhone(u *User, phone string) string {
842
        // Field converted from VARCHAR(15) to text
3✔
843
        return obfuscateValue(u.ID, phone)[:15]
844
}
845

14✔
846
func obfuscateIdentityProviderId(identity *Identity) string {
14✔
847
        return obfuscateValue(identity.UserID, identity.Provider+":"+identity.ProviderID)
14✔
848
}
14✔
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