Skip to content

Commit

Permalink
feat: encrypt sensitive columns (supabase#1593)
Browse files Browse the repository at this point in the history
Adds support for encrypting sensitive columns like the MFA secret and
password hash.

The goal with this encryption mechanism is to add yet another layer of
security on top of the database permissions provided by Postgres. In the
event that the database leaks or is accessed by malicious users or the
database permissions are incorrectly defined, the encryption key would
also be required to inspect this sensitive data.

Encryption is done using AES-GCM-256. Strings that are encrypted are
converted into a JSON string with this shape:

```json
{
  "key_id": "key identifier used for encryption",
  "alg": "aes-gcm-hkdf",
  "nonce": "GCM 12 byte nonce",
  "data": "Base64 standard encoding of the ciphertext"
}
```

As AES-GCM must not be used more than 2^32 times with a single symmetric
key, and this is not that much -- imagine serving 100m users -- then
this means that all users can only add 42 passwords or MFA verification
factors before running into this hard limit. To fix this, a symmetric
key is derived using
[HKDF](https://datatracker.ietf.org/doc/html/rfc5869) with SHA256 such
that the symmetric key is used together with the object ID (for
passwords - the user ID, for TOTP secrets - the factor ID). This way
there's a separate AES-GCM key per object, and additionally gives the
security property that a malicious actor with write permissions to the
database cannot swap passwords / TOTP secrets from Malice's account to
Target's account. They would need to also change the UUIDs of these
objects, which is likely to be hard.

To turn on encryption the following configs need to be added:

`GOTRUE_SECURITY_DB_ENCRYPTION_ENCRYPT=true` -- that turns on encryption
for new objects.
`GOTRUE_SECURITY_DB_ENCRYPTION_ENCRYPTION_KEY_ID=key-id` -- ID of the
encryption key, allowing to rotate keys easily.
`GOTRUE_SECURITY_DB_ENCRYPTION_ENCRYPTION_KEY=key` -- Base64 URL
encoding of a 256 bit AES key

Once encryption has been turned on, in order to have the rows be
readable **for ever** this config must be provided with all past and
future keys:

`GOTRUE_SECURITY_DB_ENCRYPTION_DECRYPTION_KEYS=key-id:key` -- A map of
key IDs and Base64 URL key encodings of the keys.

To retire keys, you should just move the old key to the decryption keys
map, and advertise the new encryption key ID. On each successful sign in
with password, or any MFA verification attempt, the latest key will be
used to re-encrypt the column. This also applies for the
non-encrypted-to-encrypted case.
  • Loading branch information
hf authored Jun 12, 2024
1 parent 357bda2 commit e4a4758
Show file tree
Hide file tree
Showing 16 changed files with 413 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, "[email protected]", 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, "[email protected]", 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, "[email protected]", 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 e4a4758

Please sign in to comment.