Skip to content

Commit

Permalink
feat: encrypt sensitive columns
Browse files Browse the repository at this point in the history
  • Loading branch information
hf committed Jun 7, 2024
1 parent fa90764 commit 92afa8c
Show file tree
Hide file tree
Showing 16 changed files with 408 additions and 43 deletions.
4 changes: 4 additions & 0 deletions hack/test.env
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,7 @@ GOTRUE_SAML_ENABLED="true"
GOTRUE_SAML_PRIVATE_KEY="MIIEowIBAAKCAQEAszrVveMQcSsa0Y+zN1ZFb19cRS0jn4UgIHTprW2tVBmO2PABzjY3XFCfx6vPirMAPWBYpsKmXrvm1tr0A6DZYmA8YmJd937VUQ67fa6DMyppBYTjNgGEkEhmKuszvF3MARsIKCGtZqUrmS7UG4404wYxVppnr2EYm3RGtHlkYsXu20MBqSDXP47bQP+PkJqC3BuNGk3xt5UHl2FSFpTHelkI6lBynw16B+lUT1F96SERNDaMqi/TRsZdGe5mB/29ngC/QBMpEbRBLNRir5iUevKS7Pn4aph9Qjaxx/97siktK210FJT23KjHpgcUfjoQ6BgPBTLtEeQdRyDuc/CgfwIDAQABAoIBAGYDWOEpupQPSsZ4mjMnAYJwrp4ZISuMpEqVAORbhspVeb70bLKonT4IDcmiexCg7cQBcLQKGpPVM4CbQ0RFazXZPMVq470ZDeWDEyhoCfk3bGtdxc1Zc9CDxNMs6FeQs6r1beEZug6weG5J/yRn/qYxQife3qEuDMl+lzfl2EN3HYVOSnBmdt50dxRuX26iW3nqqbMRqYn9OHuJ1LvRRfYeyVKqgC5vgt/6Tf7DAJwGe0dD7q08byHV8DBZ0pnMVU0bYpf1GTgMibgjnLjK//EVWafFHtN+RXcjzGmyJrk3+7ZyPUpzpDjO21kpzUQLrpEkkBRnmg6bwHnSrBr8avECgYEA3pq1PTCAOuLQoIm1CWR9/dhkbJQiKTJevlWV8slXQLR50P0WvI2RdFuSxlWmA4xZej8s4e7iD3MYye6SBsQHygOVGc4efvvEZV8/XTlDdyj7iLVGhnEmu2r7AFKzy8cOvXx0QcLg+zNd7vxZv/8D3Qj9Jje2LjLHKM5n/dZ3RzUCgYEAzh5Lo2anc4WN8faLGt7rPkGQF+7/18ImQE11joHWa3LzAEy7FbeOGpE/vhOv5umq5M/KlWFIRahMEQv4RusieHWI19ZLIP+JwQFxWxS+cPp3xOiGcquSAZnlyVSxZ//dlVgaZq2o2MfrxECcovRlaknl2csyf+HjFFwKlNxHm2MCgYAr//R3BdEy0oZeVRndo2lr9YvUEmu2LOihQpWDCd0fQw0ZDA2kc28eysL2RROte95r1XTvq6IvX5a0w11FzRWlDpQ4J4/LlcQ6LVt+98SoFwew+/PWuyLmxLycUbyMOOpm9eSc4wJJZNvaUzMCSkvfMtmm5jgyZYMMQ9A2Ul/9SQKBgB9mfh9mhBwVPIqgBJETZMMXOdxrjI5SBYHGSyJqpT+5Q0vIZLfqPrvNZOiQFzwWXPJ+tV4Mc/YorW3rZOdo6tdvEGnRO6DLTTEaByrY/io3/gcBZXoSqSuVRmxleqFdWWRnB56c1hwwWLqNHU+1671FhL6pNghFYVK4suP6qu4BAoGBAMk+VipXcIlD67mfGrET/xDqiWWBZtgTzTMjTpODhDY1GZck1eb4CQMP5j5V3gFJ4cSgWDJvnWg8rcz0unz/q4aeMGl1rah5WNDWj1QKWMS6vJhMHM/rqN1WHWR0ZnV83svYgtg0zDnQKlLujqW4JmGXLMU7ur6a+e6lpa1fvLsP"
GOTRUE_MAX_VERIFIED_FACTORS=10
GOTRUE_SMS_TEST_OTP_VALID_UNTIL=""
GOTRUE_SECURITY_DB_ENCRYPTION_ENCRYPT=true
GOTRUE_SECURITY_DB_ENCRYPTION_ENCRYPTION_KEY_ID=abc
GOTRUE_SECURITY_DB_ENCRYPTION_ENCRYPTION_KEY=pwFoiPyybQMqNmYVN0gUnpbfpGQV2sDv9vp0ZAxi_Y4
GOTRUE_SECURITY_DB_ENCRYPTION_DECRYPTION_KEYS=abc:pwFoiPyybQMqNmYVN0gUnpbfpGQV2sDv9vp0ZAxi_Y4
3 changes: 2 additions & 1 deletion internal/api/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ func (a *API) adminUserGet(w http.ResponseWriter, r *http.Request) error {
func (a *API) adminUserUpdate(w http.ResponseWriter, r *http.Request) error {
ctx := r.Context()
db := a.db.WithContext(ctx)
config := a.config
user := getUser(ctx)
adminUser := getAdminUser(ctx)
params, err := a.getAdminParams(r)
Expand Down Expand Up @@ -175,7 +176,7 @@ func (a *API) adminUserUpdate(w http.ResponseWriter, r *http.Request) error {
return err
}

if err := user.SetPassword(ctx, password); err != nil {
if err := user.SetPassword(ctx, password, config.Security.DBEncryption.Encrypt, config.Security.DBEncryption.EncryptionKeyID, config.Security.DBEncryption.EncryptionKey); err != nil {
return err
}
}
Expand Down
14 changes: 10 additions & 4 deletions internal/api/admin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,10 @@ func (ts *AdminTestSuite) TestAdminUserCreate() {
expectedPassword = fmt.Sprintf("%v", c.params["password"])
}

assert.Equal(ts.T(), c.expected["isAuthenticated"], u.Authenticate(context.Background(), expectedPassword))
isAuthenticated, _, err := u.Authenticate(context.Background(), expectedPassword, ts.API.config.Security.DBEncryption.DecryptionKeys, ts.API.config.Security.DBEncryption.Encrypt, ts.API.config.Security.DBEncryption.EncryptionKeyID)
require.NoError(ts.T(), err)

assert.Equal(ts.T(), c.expected["isAuthenticated"], isAuthenticated)

// remove created user after each case
require.NoError(ts.T(), ts.API.db.Destroy(u))
Expand Down Expand Up @@ -726,7 +729,8 @@ func (ts *AdminTestSuite) TestAdminUserDeleteFactor() {
require.NoError(ts.T(), err, "Error making new user")
require.NoError(ts.T(), ts.API.db.Create(u), "Error creating user")

f := models.NewFactor(u, "testSimpleName", models.TOTP, models.FactorStateVerified, "secretkey")
f := models.NewFactor(u, "testSimpleName", models.TOTP, models.FactorStateVerified)
require.NoError(ts.T(), f.SetSecret("secretkey", ts.Config.Security.DBEncryption.Encrypt, ts.Config.Security.DBEncryption.EncryptionKeyID, ts.Config.Security.DBEncryption.EncryptionKey))
require.NoError(ts.T(), ts.API.db.Create(f), "Error saving new test factor")

// Setup request
Expand All @@ -749,7 +753,8 @@ func (ts *AdminTestSuite) TestAdminUserGetFactors() {
require.NoError(ts.T(), err, "Error making new user")
require.NoError(ts.T(), ts.API.db.Create(u), "Error creating user")

f := models.NewFactor(u, "testSimpleName", models.TOTP, models.FactorStateUnverified, "secretkey")
f := models.NewFactor(u, "testSimpleName", models.TOTP, models.FactorStateUnverified)
require.NoError(ts.T(), f.SetSecret("secretkey", ts.Config.Security.DBEncryption.Encrypt, ts.Config.Security.DBEncryption.EncryptionKeyID, ts.Config.Security.DBEncryption.EncryptionKey))
require.NoError(ts.T(), ts.API.db.Create(f), "Error saving new test factor")

// Setup request
Expand All @@ -770,7 +775,8 @@ func (ts *AdminTestSuite) TestAdminUserUpdateFactor() {
require.NoError(ts.T(), err, "Error making new user")
require.NoError(ts.T(), ts.API.db.Create(u), "Error creating user")

f := models.NewFactor(u, "testSimpleName", models.TOTP, models.FactorStateUnverified, "secretkey")
f := models.NewFactor(u, "testSimpleName", models.TOTP, models.FactorStateUnverified)
require.NoError(ts.T(), f.SetSecret("secretkey", ts.Config.Security.DBEncryption.Encrypt, ts.Config.Security.DBEncryption.EncryptionKeyID, ts.Config.Security.DBEncryption.EncryptionKey))
require.NoError(ts.T(), ts.API.db.Create(f), "Error saving new test factor")

var cases = []struct {
Expand Down
57 changes: 46 additions & 11 deletions internal/api/mfa.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/boombuler/barcode/qr"
"github.com/gofrs/uuid"
"github.com/pquerna/otp/totp"
"github.com/supabase/auth/internal/crypto"
"github.com/supabase/auth/internal/hooks"
"github.com/supabase/auth/internal/metering"
"github.com/supabase/auth/internal/models"
Expand Down Expand Up @@ -63,6 +64,7 @@ func (a *API) EnrollFactor(w http.ResponseWriter, r *http.Request) error {
user := getUser(ctx)
session := getSession(ctx)
config := a.config
db := a.db.WithContext(ctx)

if session == nil || user == nil {
return internalServerError("A valid session and a registered user are required to enroll a factor")
Expand Down Expand Up @@ -92,7 +94,7 @@ func (a *API) EnrollFactor(w http.ResponseWriter, r *http.Request) error {

factorCount := len(factors)
numVerifiedFactors := 0
if err := models.DeleteExpiredFactors(a.db, config.MFA.FactorExpiryDuration); err != nil {
if err := models.DeleteExpiredFactors(db, config.MFA.FactorExpiryDuration); err != nil {
return err
}

Expand Down Expand Up @@ -132,9 +134,12 @@ func (a *API) EnrollFactor(w http.ResponseWriter, r *http.Request) error {
}
svgData.End()

factor := models.NewFactor(user, params.FriendlyName, params.FactorType, models.FactorStateUnverified, key.Secret())
factor := models.NewFactor(user, params.FriendlyName, params.FactorType, models.FactorStateUnverified)
if err := factor.SetSecret(key.Secret(), config.Security.DBEncryption.Encrypt, config.Security.DBEncryption.EncryptionKeyID, config.Security.DBEncryption.EncryptionKey); err != nil {
return err
}

err = a.db.Transaction(func(tx *storage.Connection) error {
err = db.Transaction(func(tx *storage.Connection) error {
if terr := tx.Create(factor); terr != nil {
pgErr := utilities.NewPostgresError(terr)
if pgErr.IsUniqueConstraintViolated() {
Expand All @@ -161,7 +166,7 @@ func (a *API) EnrollFactor(w http.ResponseWriter, r *http.Request) error {
TOTP: TOTPObject{
// See: https://css-tricks.com/probably-dont-base64-svg/
QRCode: buf.String(),
Secret: factor.Secret,
Secret: key.Secret(),
URI: key.URL(),
},
})
Expand All @@ -170,13 +175,14 @@ func (a *API) EnrollFactor(w http.ResponseWriter, r *http.Request) error {
func (a *API) ChallengeFactor(w http.ResponseWriter, r *http.Request) error {
ctx := r.Context()
config := a.config
db := a.db.WithContext(ctx)

user := getUser(ctx)
factor := getFactor(ctx)
ipAddress := utilities.GetIPAddress(r)
challenge := models.NewChallenge(factor, ipAddress)

if err := a.db.Transaction(func(tx *storage.Connection) error {
if err := db.Transaction(func(tx *storage.Connection) error {
if terr := tx.Create(challenge); terr != nil {
return terr
}
Expand All @@ -203,6 +209,7 @@ func (a *API) VerifyFactor(w http.ResponseWriter, r *http.Request) error {
user := getUser(ctx)
factor := getFactor(ctx)
config := a.config
db := a.db.WithContext(ctx)

params := &VerifyFactorParams{}
if err := retrieveRequestParams(r, params); err != nil {
Expand All @@ -214,7 +221,7 @@ func (a *API) VerifyFactor(w http.ResponseWriter, r *http.Request) error {
return internalServerError(InvalidFactorOwnerErrorMessage)
}

challenge, err := models.FindChallengeByID(a.db, params.ChallengeID)
challenge, err := models.FindChallengeByID(db, params.ChallengeID)
if err != nil && models.IsNotFoundError(err) {
return notFoundError(ErrorCodeMFAFactorNotFound, "MFA factor with the provided challenge ID not found")
} else if err != nil {
Expand All @@ -226,13 +233,18 @@ func (a *API) VerifyFactor(w http.ResponseWriter, r *http.Request) error {
}

if challenge.HasExpired(config.MFA.ChallengeExpiryDuration) {
if err := a.db.Destroy(challenge); err != nil {
if err := db.Destroy(challenge); err != nil {
return internalServerError("Database error deleting challenge").WithInternalError(err)
}
return unprocessableEntityError(ErrorCodeMFAChallengeExpired, "MFA challenge %v has expired, verify against another challenge or create a new challenge.", challenge.ID)
}

valid := totp.Validate(params.Code, factor.Secret)
secret, shouldReEncrypt, err := factor.GetSecret(config.Security.DBEncryption.DecryptionKeys, config.Security.DBEncryption.Encrypt, config.Security.DBEncryption.EncryptionKeyID)
if err != nil {
return internalServerError("Database error verifying MFA TOTP secret").WithInternalError(err)
}

valid := totp.Validate(params.Code, secret)

if config.Hook.MFAVerificationAttempt.Enabled {
input := hooks.MFAVerificationAttemptInput{
Expand All @@ -248,7 +260,7 @@ func (a *API) VerifyFactor(w http.ResponseWriter, r *http.Request) error {
}

if output.Decision == hooks.HookRejection {
if err := models.Logout(a.db, user.ID); err != nil {
if err := models.Logout(db, user.ID); err != nil {
return err
}

Expand All @@ -259,12 +271,22 @@ func (a *API) VerifyFactor(w http.ResponseWriter, r *http.Request) error {
return forbiddenError(ErrorCodeMFAVerificationRejected, output.Message)
}
}

if !valid {
if shouldReEncrypt && config.Security.DBEncryption.Encrypt {
if err := factor.SetSecret(secret, true, config.Security.DBEncryption.EncryptionKeyID, config.Security.DBEncryption.EncryptionKey); err != nil {
return err
}

if err := db.UpdateOnly(factor, "secret"); err != nil {
return err
}
}
return unprocessableEntityError(ErrorCodeMFAVerificationFailed, "Invalid TOTP code entered")
}

var token *AccessTokenResponse
err = a.db.Transaction(func(tx *storage.Connection) error {
err = db.Transaction(func(tx *storage.Connection) error {
var terr error
if terr = models.NewAuditLogEntry(r, tx, user, models.VerifyFactorAction, r.RemoteAddr, map[string]interface{}{
"factor_id": factor.ID,
Expand All @@ -280,6 +302,17 @@ func (a *API) VerifyFactor(w http.ResponseWriter, r *http.Request) error {
return terr
}
}
if shouldReEncrypt && config.Security.DBEncryption.Encrypt {
es, terr := crypto.NewEncryptedString(factor.ID.String(), []byte(secret), config.Security.DBEncryption.EncryptionKeyID, config.Security.DBEncryption.EncryptionKey)
if terr != nil {
return terr
}

factor.Secret = es.String()
if terr := tx.UpdateOnly(factor, "secret"); terr != nil {
return terr
}
}
user, terr = models.FindUserByID(tx, user.ID)
if terr != nil {
return terr
Expand Down Expand Up @@ -316,6 +349,8 @@ func (a *API) UnenrollFactor(w http.ResponseWriter, r *http.Request) error {
user := getUser(ctx)
factor := getFactor(ctx)
session := getSession(ctx)
db := a.db.WithContext(ctx)

if factor == nil || session == nil || user == nil {
return internalServerError("A valid session and factor are required to unenroll a factor")
}
Expand All @@ -327,7 +362,7 @@ func (a *API) UnenrollFactor(w http.ResponseWriter, r *http.Request) error {
return internalServerError(InvalidFactorOwnerErrorMessage)
}

err = a.db.Transaction(func(tx *storage.Connection) error {
err = db.Transaction(func(tx *storage.Connection) error {
var terr error
if terr := tx.Destroy(factor); terr != nil {
return terr
Expand Down
23 changes: 14 additions & 9 deletions internal/api/mfa_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package api

import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
Expand All @@ -14,15 +13,15 @@ import (
"github.com/gofrs/uuid"

"database/sql"

"github.com/pkg/errors"
"github.com/pquerna/otp"
"github.com/supabase/auth/internal/conf"
"github.com/supabase/auth/internal/crypto"
"github.com/supabase/auth/internal/models"
"github.com/supabase/auth/internal/storage"
"github.com/supabase/auth/internal/utilities"

"github.com/jackc/pgx/v4"

"github.com/pquerna/otp/totp"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
Expand Down Expand Up @@ -62,7 +61,8 @@ func (ts *MFATestSuite) SetupTest() {
require.NoError(ts.T(), err, "Error creating test user model")
require.NoError(ts.T(), ts.API.db.Create(u), "Error saving new test user")
// Create Factor
f := models.NewFactor(u, "test_factor", models.TOTP, models.FactorStateUnverified, "secretkey")
f := models.NewFactor(u, "test_factor", models.TOTP, models.FactorStateUnverified)
require.NoError(ts.T(), f.SetSecret("secretkey", ts.Config.Security.DBEncryption.Encrypt, ts.Config.Security.DBEncryption.EncryptionKeyID, ts.Config.Security.DBEncryption.EncryptionKey))
require.NoError(ts.T(), ts.API.db.Create(f), "Error saving new test factor")
// Create corresponding session
s, err := models.NewSession(u.ID, &f.ID)
Expand Down Expand Up @@ -482,14 +482,19 @@ func ServeAuthenticatedRequest(ts *MFATestSuite, method, path, token string, buf
func performVerifyFlow(ts *MFATestSuite, challengeID, factorID uuid.UUID, token string, requireStatusOK bool) *httptest.ResponseRecorder {
var buffer bytes.Buffer

conn, err := pgx.Connect(context.Background(), ts.API.db.URL())
factor, err := models.FindFactorByFactorID(ts.API.db, factorID)
require.NoError(ts.T(), err)
require.NotNil(ts.T(), factor)

defer conn.Close(context.Background())
totpSecret := factor.Secret

var totpSecret string
err = conn.QueryRow(context.Background(), "select secret from mfa_factors where id=$1", factorID).Scan(&totpSecret)
require.NoError(ts.T(), err)
if es := crypto.ParseEncryptedString(factor.Secret); es != nil {
secret, err := es.Decrypt(factor.ID.String(), ts.API.config.Security.DBEncryption.DecryptionKeys)
require.NoError(ts.T(), err)
require.NotNil(ts.T(), secret)

totpSecret = string(secret)
}

code, err := totp.GenerateCode(totpSecret, time.Now().UTC())
require.NoError(ts.T(), err)
Expand Down
19 changes: 18 additions & 1 deletion internal/api/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,10 @@ func (a *API) ResourceOwnerPasswordGrant(ctx context.Context, w http.ResponseWri
return oauthError("invalid_grant", InvalidLoginMessage)
}

isValidPassword := user.Authenticate(ctx, params.Password)
isValidPassword, shouldReEncrypt, err := user.Authenticate(ctx, params.Password, config.Security.DBEncryption.DecryptionKeys, config.Security.DBEncryption.Encrypt, config.Security.DBEncryption.EncryptionKeyID)
if err != nil {
return err
}

var weakPasswordError *WeakPasswordError
if isValidPassword {
Expand All @@ -156,6 +159,20 @@ func (a *API) ResourceOwnerPasswordGrant(ctx context.Context, w http.ResponseWri
observability.GetLogEntry(r).Entry.WithError(err).Warn("Password strength check on sign-in failed")
}
}

if shouldReEncrypt {
if err := user.SetPassword(ctx, params.Password, true, config.Security.DBEncryption.EncryptionKeyID, config.Security.DBEncryption.EncryptionKey); err != nil {
return err
}

// directly change this in the database without
// calling user.UpdatePassword() because this
// is not a password change, just encryption
// change in the database
if err := db.UpdateOnly(user, "encrypted_password"); err != nil {
return err
}
}
}

if config.Hook.PasswordVerificationAttempt.Enabled {
Expand Down
15 changes: 13 additions & 2 deletions internal/api/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,12 +153,23 @@ func (a *API) UserUpdate(w http.ResponseWriter, r *http.Request) error {

password := *params.Password
if password != "" {
if user.EncryptedPassword != "" && user.Authenticate(ctx, password) {
isSamePassword := false

if user.EncryptedPassword != "" {
auth, _, err := user.Authenticate(ctx, password, config.Security.DBEncryption.DecryptionKeys, false, "")
if err != nil {
return err
}

isSamePassword = auth
}

if isSamePassword {
return unprocessableEntityError(ErrorCodeSamePassword, "New password should be different from the old password.")
}
}

if err := user.SetPassword(ctx, password); err != nil {
if err := user.SetPassword(ctx, password, config.Security.DBEncryption.Encrypt, config.Security.DBEncryption.EncryptionKeyID, config.Security.DBEncryption.EncryptionKey); err != nil {
return err
}
}
Expand Down
15 changes: 12 additions & 3 deletions internal/api/user_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,10 @@ func (ts *UserTestSuite) TestUserUpdatePassword() {
u, err = models.FindUserByEmailAndAudience(ts.API.db, "test@example.com", ts.Config.JWT.Aud)
require.NoError(ts.T(), err)

require.Equal(ts.T(), c.expected.isAuthenticated, u.Authenticate(context.Background(), c.newPassword))
isAuthenticated, _, err := u.Authenticate(context.Background(), c.newPassword, ts.API.config.Security.DBEncryption.DecryptionKeys, ts.API.config.Security.DBEncryption.Encrypt, ts.API.config.Security.DBEncryption.EncryptionKeyID)
require.NoError(ts.T(), err)

require.Equal(ts.T(), c.expected.isAuthenticated, isAuthenticated)
})
}
}
Expand Down Expand Up @@ -369,7 +372,10 @@ func (ts *UserTestSuite) TestUserUpdatePasswordNoReauthenticationRequired() {
u, err = models.FindUserByEmailAndAudience(ts.API.db, "test@example.com", ts.Config.JWT.Aud)
require.NoError(ts.T(), err)

require.Equal(ts.T(), c.expected.isAuthenticated, u.Authenticate(context.Background(), c.newPassword))
isAuthenticated, _, err := u.Authenticate(context.Background(), c.newPassword, ts.API.config.Security.DBEncryption.DecryptionKeys, ts.API.config.Security.DBEncryption.Encrypt, ts.API.config.Security.DBEncryption.EncryptionKeyID)
require.NoError(ts.T(), err)

require.Equal(ts.T(), c.expected.isAuthenticated, isAuthenticated)
})
}
}
Expand Down Expand Up @@ -424,7 +430,10 @@ func (ts *UserTestSuite) TestUserUpdatePasswordReauthentication() {
u, err = models.FindUserByEmailAndAudience(ts.API.db, "test@example.com", ts.Config.JWT.Aud)
require.NoError(ts.T(), err)

require.True(ts.T(), u.Authenticate(context.Background(), "newpass"))
isAuthenticated, _, err := u.Authenticate(context.Background(), "newpass", ts.Config.Security.DBEncryption.DecryptionKeys, ts.Config.Security.DBEncryption.Encrypt, ts.Config.Security.DBEncryption.EncryptionKeyID)
require.NoError(ts.T(), err)

require.True(ts.T(), isAuthenticated)
require.Empty(ts.T(), u.ReauthenticationToken)
require.Nil(ts.T(), u.ReauthenticationSentAt)
}
Expand Down
Loading

0 comments on commit 92afa8c

Please sign in to comment.