Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions internal/api/admin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -649,7 +649,7 @@ func (ts *AdminTestSuite) TestAdminUserUpdateClearsPendingTokensOnEmailChange()
u.RecoveryToken = recoveryHash
u.RecoverySentAt = &now
require.NoError(ts.T(), ts.API.db.UpdateOnly(u, "recovery_token", "recovery_sent_at"))
require.NoError(ts.T(), models.CreateOneTimeToken(ts.API.db, u.ID, u.GetEmail(), recoveryHash, models.RecoveryToken))
require.NoError(ts.T(), models.CreateOneTimeToken(ts.API.db, u.ID, u.GetEmail(), recoveryHash, models.RecoveryToken, ts.Config.Mailer.OtpExpAsDuration()))

// sanity check: the token is redeemable before the email change
_, err = models.FindUserByOneTimeToken(ts.API.db, recoveryHash, models.RecoveryToken)
Expand Down Expand Up @@ -877,11 +877,11 @@ func (ts *AdminTestSuite) TestAdminUserSoftDeletion() {
"provider": "email",
}
require.NoError(ts.T(), ts.API.db.Create(u))
require.NoError(ts.T(), models.CreateOneTimeToken(ts.API.db, u.ID, u.GetEmail(), u.ConfirmationToken, models.ConfirmationToken))
require.NoError(ts.T(), models.CreateOneTimeToken(ts.API.db, u.ID, u.GetEmail(), u.RecoveryToken, models.RecoveryToken))
require.NoError(ts.T(), models.CreateOneTimeToken(ts.API.db, u.ID, u.GetEmail(), u.EmailChangeTokenCurrent, models.EmailChangeTokenCurrent))
require.NoError(ts.T(), models.CreateOneTimeToken(ts.API.db, u.ID, u.GetEmail(), u.EmailChangeTokenNew, models.EmailChangeTokenNew))
require.NoError(ts.T(), models.CreateOneTimeToken(ts.API.db, u.ID, u.GetPhone(), u.PhoneChangeToken, models.PhoneChangeToken))
require.NoError(ts.T(), models.CreateOneTimeToken(ts.API.db, u.ID, u.GetEmail(), u.ConfirmationToken, models.ConfirmationToken, ts.Config.Mailer.OtpExpAsDuration()))
require.NoError(ts.T(), models.CreateOneTimeToken(ts.API.db, u.ID, u.GetEmail(), u.RecoveryToken, models.RecoveryToken, ts.Config.Mailer.OtpExpAsDuration()))
require.NoError(ts.T(), models.CreateOneTimeToken(ts.API.db, u.ID, u.GetEmail(), u.EmailChangeTokenCurrent, models.EmailChangeTokenCurrent, ts.Config.Mailer.OtpExpAsDuration()))
require.NoError(ts.T(), models.CreateOneTimeToken(ts.API.db, u.ID, u.GetEmail(), u.EmailChangeTokenNew, models.EmailChangeTokenNew, ts.Config.Mailer.OtpExpAsDuration()))
require.NoError(ts.T(), models.CreateOneTimeToken(ts.API.db, u.ID, u.GetPhone(), u.PhoneChangeToken, models.PhoneChangeToken, ts.Config.Sms.OtpExpAsDuration()))

// create user identities
_, err = ts.API.createNewIdentity(ts.API.db, u, "email", map[string]interface{}{
Expand Down
4 changes: 2 additions & 2 deletions internal/api/external_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ func (ts *ExternalTestSuite) createUser(providerId string, email string, name st
ts.Require().NoError(ts.API.db.Create(u), "Error creating user")

if confirmationToken != "" {
ts.Require().NoError(models.CreateOneTimeToken(ts.API.db, u.ID, email, u.ConfirmationToken, models.ConfirmationToken), "Error creating one-time confirmation/invite token")
ts.Require().NoError(models.CreateOneTimeToken(ts.API.db, u.ID, email, u.ConfirmationToken, models.ConfirmationToken, ts.Config.Mailer.OtpExpAsDuration()), "Error creating one-time confirmation/invite token")
}

i, err := models.NewIdentity(u, "email", map[string]interface{}{
Expand Down Expand Up @@ -142,7 +142,7 @@ func (ts *ExternalTestSuite) createUserWithIdentity(providerType, providerId str
ts.Require().NoError(ts.API.db.Create(u), "Error creating user")

if confirmationToken != "" {
ts.Require().NoError(models.CreateOneTimeToken(ts.API.db, u.ID, email, u.ConfirmationToken, models.ConfirmationToken), "Error creating one-time confirmation/invite token")
ts.Require().NoError(models.CreateOneTimeToken(ts.API.db, u.ID, email, u.ConfirmationToken, models.ConfirmationToken, ts.Config.Mailer.OtpExpAsDuration()), "Error creating one-time confirmation/invite token")
}

if email != "" {
Expand Down
2 changes: 1 addition & 1 deletion internal/api/invite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ func (ts *InviteTestSuite) TestVerifyInvite() {
user.ConfirmationToken = crypto.GenerateTokenHash(c.email, c.requestBody["token"].(string))
require.NoError(ts.T(), err)
require.NoError(ts.T(), ts.API.db.Create(user))
require.NoError(ts.T(), models.CreateOneTimeToken(ts.API.db, user.ID, user.GetEmail(), user.ConfirmationToken, models.ConfirmationToken))
require.NoError(ts.T(), models.CreateOneTimeToken(ts.API.db, user.ID, user.GetEmail(), user.ConfirmationToken, models.ConfirmationToken, ts.Config.Mailer.OtpExpAsDuration()))

// Find test user
_, err = models.FindUserByEmailAndAudience(ts.API.db, c.email, ts.Config.JWT.Aud)
Expand Down
24 changes: 12 additions & 12 deletions internal/api/mail.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ func (a *API) adminGenerateLink(w http.ResponseWriter, r *http.Request) error {
return terr
}

terr = models.CreateOneTimeToken(tx, user.ID, user.GetEmail(), user.RecoveryToken, models.RecoveryToken)
terr = models.CreateOneTimeToken(tx, user.ID, user.GetEmail(), user.RecoveryToken, models.RecoveryToken, config.Mailer.OtpExpAsDuration())
if terr != nil {
terr = errors.Wrap(terr, "Database error creating recovery token in admin")
return terr
Expand Down Expand Up @@ -194,7 +194,7 @@ func (a *API) adminGenerateLink(w http.ResponseWriter, r *http.Request) error {
terr = errors.Wrap(terr, "Database error updating user for invite")
return terr
}
terr = models.CreateOneTimeToken(tx, user.ID, user.GetEmail(), user.ConfirmationToken, models.ConfirmationToken)
terr = models.CreateOneTimeToken(tx, user.ID, user.GetEmail(), user.ConfirmationToken, models.ConfirmationToken, config.Mailer.OtpExpAsDuration())
if terr != nil {
terr = errors.Wrap(terr, "Database error creating confirmation token for invite in admin")
return terr
Expand Down Expand Up @@ -233,7 +233,7 @@ func (a *API) adminGenerateLink(w http.ResponseWriter, r *http.Request) error {
terr = errors.Wrap(terr, "Database error updating user for confirmation")
return terr
}
terr = models.CreateOneTimeToken(tx, user.ID, user.GetEmail(), user.ConfirmationToken, models.ConfirmationToken)
terr = models.CreateOneTimeToken(tx, user.ID, user.GetEmail(), user.ConfirmationToken, models.ConfirmationToken, config.Mailer.OtpExpAsDuration())
if terr != nil {
terr = errors.Wrap(terr, "Database error creating confirmation token for signup in admin")
return terr
Expand Down Expand Up @@ -266,14 +266,14 @@ func (a *API) adminGenerateLink(w http.ResponseWriter, r *http.Request) error {
return terr
}
if user.EmailChangeTokenCurrent != "" {
terr = models.CreateOneTimeToken(tx, user.ID, user.GetEmail(), user.EmailChangeTokenCurrent, models.EmailChangeTokenCurrent)
terr = models.CreateOneTimeToken(tx, user.ID, user.GetEmail(), user.EmailChangeTokenCurrent, models.EmailChangeTokenCurrent, config.Mailer.OtpExpAsDuration())
if terr != nil {
terr = errors.Wrap(terr, "Database error creating email change token current in admin")
return terr
}
}
if user.EmailChangeTokenNew != "" {
terr = models.CreateOneTimeToken(tx, user.ID, user.EmailChange, user.EmailChangeTokenNew, models.EmailChangeTokenNew)
terr = models.CreateOneTimeToken(tx, user.ID, user.EmailChange, user.EmailChangeTokenNew, models.EmailChangeTokenNew, config.Mailer.OtpExpAsDuration())
if terr != nil {
terr = errors.Wrap(terr, "Database error creating email change token new in admin")
return terr
Expand Down Expand Up @@ -349,7 +349,7 @@ func (a *API) sendConfirmation(r *http.Request, tx *storage.Connection, u *model
return apierrors.NewInternalServerError("Error sending confirmation email").WithInternalError(errors.Wrap(err, "Database error updating user for confirmation"))
}

if err := models.CreateOneTimeToken(tx, u.ID, u.GetEmail(), u.ConfirmationToken, models.ConfirmationToken); err != nil {
if err := models.CreateOneTimeToken(tx, u.ID, u.GetEmail(), u.ConfirmationToken, models.ConfirmationToken, config.Mailer.OtpExpAsDuration()); err != nil {
return apierrors.NewInternalServerError("Error sending confirmation email").WithInternalError(errors.Wrap(err, "Database error creating confirmation token"))
}

Expand Down Expand Up @@ -386,7 +386,7 @@ func (a *API) sendInvite(r *http.Request, tx *storage.Connection, u *models.User
return apierrors.NewInternalServerError("Error inviting user").WithInternalError(errors.Wrap(err, "Database error updating user for invite"))
}

err = models.CreateOneTimeToken(tx, u.ID, u.GetEmail(), u.ConfirmationToken, models.ConfirmationToken)
err = models.CreateOneTimeToken(tx, u.ID, u.GetEmail(), u.ConfirmationToken, models.ConfirmationToken, config.Mailer.OtpExpAsDuration())
if err != nil {
return apierrors.NewInternalServerError("Error inviting user").WithInternalError(errors.Wrap(err, "Database error creating confirmation token for invite"))
}
Expand Down Expand Up @@ -428,7 +428,7 @@ func (a *API) sendPasswordRecovery(r *http.Request, tx *storage.Connection, u *m
return apierrors.NewInternalServerError("Error sending recovery email").WithInternalError(errors.Wrap(err, "Database error updating user for recovery"))
}

if err := models.CreateOneTimeToken(tx, u.ID, u.GetEmail(), u.RecoveryToken, models.RecoveryToken); err != nil {
if err := models.CreateOneTimeToken(tx, u.ID, u.GetEmail(), u.RecoveryToken, models.RecoveryToken, config.Mailer.OtpExpAsDuration()); err != nil {
return apierrors.NewInternalServerError("Error sending recovery email").WithInternalError(errors.Wrap(err, "Database error creating recovery token"))
}

Expand Down Expand Up @@ -469,7 +469,7 @@ func (a *API) sendReauthenticationOtp(r *http.Request, tx *storage.Connection, u
return apierrors.NewInternalServerError("Error sending reauthentication email").WithInternalError(errors.Wrap(err, "Database error updating user for reauthentication"))
}

if err := models.CreateOneTimeToken(tx, u.ID, u.GetEmail(), u.ReauthenticationToken, models.ReauthenticationToken); err != nil {
if err := models.CreateOneTimeToken(tx, u.ID, u.GetEmail(), u.ReauthenticationToken, models.ReauthenticationToken, config.Mailer.OtpExpAsDuration()); err != nil {
return apierrors.NewInternalServerError("Error sending reauthentication email").WithInternalError(errors.Wrap(err, "Database error creating reauthentication token"))
}

Expand Down Expand Up @@ -512,7 +512,7 @@ func (a *API) sendMagicLink(r *http.Request, tx *storage.Connection, u *models.U
return apierrors.NewInternalServerError("Error sending magic link email").WithInternalError(errors.Wrap(err, "Database error updating user for recovery"))
}

if err := models.CreateOneTimeToken(tx, u.ID, u.GetEmail(), u.RecoveryToken, models.RecoveryToken); err != nil {
if err := models.CreateOneTimeToken(tx, u.ID, u.GetEmail(), u.RecoveryToken, models.RecoveryToken, config.Mailer.OtpExpAsDuration()); err != nil {
return apierrors.NewInternalServerError("Error sending magic link email").WithInternalError(errors.Wrap(err, "Database error creating recovery token"))
}

Expand Down Expand Up @@ -573,13 +573,13 @@ func (a *API) sendEmailChange(r *http.Request, tx *storage.Connection, u *models
}

if u.EmailChangeTokenCurrent != "" {
if err := models.CreateOneTimeToken(tx, u.ID, u.GetEmail(), u.EmailChangeTokenCurrent, models.EmailChangeTokenCurrent); err != nil {
if err := models.CreateOneTimeToken(tx, u.ID, u.GetEmail(), u.EmailChangeTokenCurrent, models.EmailChangeTokenCurrent, config.Mailer.OtpExpAsDuration()); err != nil {
return apierrors.NewInternalServerError("Error sending email change email").WithInternalError(errors.Wrap(err, "Database error creating email change token current"))
}
}

if u.EmailChangeTokenNew != "" {
if err := models.CreateOneTimeToken(tx, u.ID, u.EmailChange, u.EmailChangeTokenNew, models.EmailChangeTokenNew); err != nil {
if err := models.CreateOneTimeToken(tx, u.ID, u.EmailChange, u.EmailChangeTokenNew, models.EmailChangeTokenNew, config.Mailer.OtpExpAsDuration()); err != nil {
return apierrors.NewInternalServerError("Error sending email change email").WithInternalError(errors.Wrap(err, "Database error creating email change token new"))
}
}
Expand Down
90 changes: 90 additions & 0 deletions internal/api/one_time_token_expiry_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package api

import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"

"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/supabase/auth/internal/api/sms_provider"
"github.com/supabase/auth/internal/conf"
"github.com/supabase/auth/internal/models"
)

type OneTimeTokenExpiryTestSuite struct {
suite.Suite
API *API
Config *conf.GlobalConfiguration
}

func TestOneTimeTokenExpiry(t *testing.T) {
api, config, err := setupAPIForTest()
require.NoError(t, err)

ts := &OneTimeTokenExpiryTestSuite{API: api, Config: config}
defer api.db.Close()

suite.Run(t, ts)
}

func (ts *OneTimeTokenExpiryTestSuite) SetupTest() {
models.TruncateAll(ts.API.db)

// Two different windows to ensure we're using the right config values
ts.Config.Mailer.OtpExp = 3600
ts.Config.Sms.OtpExp = 60
}

func (ts *OneTimeTokenExpiryTestSuite) TestEmailTokenMatchesMailerWindow() {
u, err := models.NewUser("", "otp-exp@example.com", "password", ts.Config.JWT.Aud, nil)
require.NoError(ts.T(), err)
require.NoError(ts.T(), ts.API.db.Create(u))

var buffer bytes.Buffer
require.NoError(ts.T(), json.NewEncoder(&buffer).Encode(map[string]interface{}{
"email": "otp-exp@example.com",
}))
req := httptest.NewRequest(http.MethodPost, "http://localhost/recover", &buffer)
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
ts.API.handler.ServeHTTP(w, req)
require.Equal(ts.T(), http.StatusOK, w.Code)

u, err = models.FindUserByID(ts.API.db, u.ID)
require.NoError(ts.T(), err)

ott, err := models.FindOneTimeToken(ts.API.db, u.RecoveryToken, models.RecoveryToken)
require.NoError(ts.T(), err)
require.NotNil(ts.T(), ott.ExpiresAt)

want := u.RecoverySentAt.Add(ts.Config.Mailer.OtpExpAsDuration())
require.WithinDuration(ts.T(), want, *ott.ExpiresAt, 2*time.Second)
}

func (ts *OneTimeTokenExpiryTestSuite) TestPhoneTokenMatchesSmsWindow() {
u, err := models.NewUser("123456789", "", "password", ts.Config.JWT.Aud, nil)
require.NoError(ts.T(), err)
require.NoError(ts.T(), ts.API.db.Create(u))

sms_provider.MockProvider = &TestSmsProvider{}
defer func() { sms_provider.MockProvider = nil }()

req, err := http.NewRequest(http.MethodPost, "http://localhost/otp", nil)
require.NoError(ts.T(), err)
_, err = ts.API.sendPhoneConfirmation(req, ts.API.db, u, "123456789", phoneConfirmationOtp, sms_provider.SMSProvider)
require.NoError(ts.T(), err)

u, err = models.FindUserByID(ts.API.db, u.ID)
require.NoError(ts.T(), err)

ott, err := models.FindOneTimeToken(ts.API.db, u.ConfirmationToken, models.ConfirmationToken)
require.NoError(ts.T(), err)
require.NotNil(ts.T(), ott.ExpiresAt)

want := u.ConfirmationSentAt.Add(ts.Config.Sms.OtpExpAsDuration())
require.WithinDuration(ts.T(), want, *ott.ExpiresAt, 2*time.Second)
}
6 changes: 3 additions & 3 deletions internal/api/phone.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,15 +142,15 @@ func (a *API) sendPhoneConfirmation(r *http.Request, tx *storage.Connection, use
var ottErr error
switch otpType {
case phoneConfirmationOtp:
if err := models.CreateOneTimeToken(tx, user.ID, user.GetPhone(), user.ConfirmationToken, models.ConfirmationToken); err != nil {
if err := models.CreateOneTimeToken(tx, user.ID, user.GetPhone(), user.ConfirmationToken, models.ConfirmationToken, config.Sms.OtpExpAsDuration()); err != nil {
ottErr = errors.Wrap(err, "Database error creating confirmation token for phone")
}
case phoneChangeVerification:
if err := models.CreateOneTimeToken(tx, user.ID, user.PhoneChange, user.PhoneChangeToken, models.PhoneChangeToken); err != nil {
if err := models.CreateOneTimeToken(tx, user.ID, user.PhoneChange, user.PhoneChangeToken, models.PhoneChangeToken, config.Sms.OtpExpAsDuration()); err != nil {
ottErr = errors.Wrap(err, "Database error creating phone change token")
}
case phoneReauthenticationOtp:
if err := models.CreateOneTimeToken(tx, user.ID, user.GetPhone(), user.ReauthenticationToken, models.ReauthenticationToken); err != nil {
if err := models.CreateOneTimeToken(tx, user.ID, user.GetPhone(), user.ReauthenticationToken, models.ReauthenticationToken, config.Sms.OtpExpAsDuration()); err != nil {
ottErr = errors.Wrap(err, "Database error creating reauthentication token for phone")
}
}
Expand Down
12 changes: 6 additions & 6 deletions internal/api/resend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,24 +191,24 @@ func (ts *ResendTestSuite) TestResendSuccess() {
u.EmailChangeSentAt = &now
u.EmailChangeTokenNew = "123456"
require.NoError(ts.T(), ts.API.db.Create(u), "Error saving new test user")
require.NoError(ts.T(), models.CreateOneTimeToken(ts.API.db, u.ID, u.GetEmail(), u.ConfirmationToken, models.ConfirmationToken))
require.NoError(ts.T(), models.CreateOneTimeToken(ts.API.db, u.ID, u.EmailChange, u.EmailChangeTokenNew, models.EmailChangeTokenNew))
require.NoError(ts.T(), models.CreateOneTimeToken(ts.API.db, u.ID, u.GetEmail(), u.ConfirmationToken, models.ConfirmationToken, ts.Config.Mailer.OtpExpAsDuration()))
require.NoError(ts.T(), models.CreateOneTimeToken(ts.API.db, u.ID, u.EmailChange, u.EmailChangeTokenNew, models.EmailChangeTokenNew, ts.Config.Mailer.OtpExpAsDuration()))

phoneUser, err := models.NewUser("1234567890", "", "password", ts.Config.JWT.Aud, nil)
require.NoError(ts.T(), err, "Error creating test user model")
phoneUser.EmailChange = "bar@example.com"
phoneUser.EmailChangeSentAt = &now
phoneUser.EmailChangeTokenNew = "123456"
require.NoError(ts.T(), ts.API.db.Create(phoneUser), "Error saving new test user")
require.NoError(ts.T(), models.CreateOneTimeToken(ts.API.db, phoneUser.ID, phoneUser.EmailChange, phoneUser.EmailChangeTokenNew, models.EmailChangeTokenNew))
require.NoError(ts.T(), models.CreateOneTimeToken(ts.API.db, phoneUser.ID, phoneUser.EmailChange, phoneUser.EmailChangeTokenNew, models.EmailChangeTokenNew, ts.Config.Mailer.OtpExpAsDuration()))

emailUser, err := models.NewUser("", "bar@example.com", "password", ts.Config.JWT.Aud, nil)
require.NoError(ts.T(), err, "Error creating test user model")
phoneUser.PhoneChange = "1234567890"
phoneUser.PhoneChangeSentAt = &now
phoneUser.PhoneChangeToken = "123456"
require.NoError(ts.T(), ts.API.db.Create(emailUser), "Error saving new test user")
require.NoError(ts.T(), models.CreateOneTimeToken(ts.API.db, phoneUser.ID, phoneUser.PhoneChange, phoneUser.PhoneChangeToken, models.PhoneChangeToken))
require.NoError(ts.T(), models.CreateOneTimeToken(ts.API.db, phoneUser.ID, phoneUser.PhoneChange, phoneUser.PhoneChangeToken, models.PhoneChangeToken, ts.Config.Sms.OtpExpAsDuration()))

cases := []struct {
desc string
Expand Down Expand Up @@ -292,7 +292,7 @@ func (ts *ResendTestSuite) TestResendPKCESuccess() {
signupUser.ConfirmationToken = "oldtoken"
signupUser.ConfirmationSentAt = &now
require.NoError(ts.T(), ts.API.db.Create(signupUser))
require.NoError(ts.T(), models.CreateOneTimeToken(ts.API.db, signupUser.ID, signupUser.GetEmail(), signupUser.ConfirmationToken, models.ConfirmationToken))
require.NoError(ts.T(), models.CreateOneTimeToken(ts.API.db, signupUser.ID, signupUser.GetEmail(), signupUser.ConfirmationToken, models.ConfirmationToken, ts.Config.Mailer.OtpExpAsDuration()))

// Fresh user for email_change PKCE resend
emailChangeUser, err := models.NewUser("", "pkce-change@example.com", "password", ts.Config.JWT.Aud, nil)
Expand All @@ -301,7 +301,7 @@ func (ts *ResendTestSuite) TestResendPKCESuccess() {
emailChangeUser.EmailChangeSentAt = &now
emailChangeUser.EmailChangeTokenNew = "oldchangetoken"
require.NoError(ts.T(), ts.API.db.Create(emailChangeUser))
require.NoError(ts.T(), models.CreateOneTimeToken(ts.API.db, emailChangeUser.ID, emailChangeUser.EmailChange, emailChangeUser.EmailChangeTokenNew, models.EmailChangeTokenNew))
require.NoError(ts.T(), models.CreateOneTimeToken(ts.API.db, emailChangeUser.ID, emailChangeUser.EmailChange, emailChangeUser.EmailChangeTokenNew, models.EmailChangeTokenNew, ts.Config.Mailer.OtpExpAsDuration()))

ts.Run("Resend signup confirmation with PKCE", func() {
var buffer bytes.Buffer
Expand Down
Loading
Loading