feat(otp): add support for test email OTPs - #2790
Open
itsybitsci wants to merge 1 commit into
Open
Conversation
Add GOTRUE_MAILER_TEST_OTP and GOTRUE_MAILER_TEST_OTP_VALID_UNTIL, the email counterpart of GOTRUE_SMS_TEST_OTP. When a magic link or signup confirmation is requested for a listed email, the configured code is used instead of a random one and no email is sent. The token hash is stored exactly like a regular OTP, so verification is unchanged. Closes supabase#901
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What kind of change does this PR introduce?
Feature. Closes #901.
What is the current behavior?
Test OTPs exist for SMS (
GOTRUE_SMS_TEST_OTP), but every email OTP is randomly generated and always sent. Teams cannot provide a fixed email + code pair for app store review accounts or end-to-end tests without a real inbox.What is the new behavior?
Adds
GOTRUE_MAILER_TEST_OTPandGOTRUE_MAILER_TEST_OTP_VALID_UNTIL, mirroring the SMS feature.When a request targets a listed email and the configuration has not expired:
crypto.GenerateOtp. The stored value is the normalcrypto.GenerateTokenHash(email, otp)with the PKCE flow prefix, and the one-time token row is created as usual, soverify.gois unchanged:POST /verifywith{ type, email, token }recomputes the same hash.sendEmailreturns early before the global rate limiter, the Send Email hook and the mailer, and does not incrementemailSendCounter.SMTP.MaxFrequency) still applies, matching the SMS path which keepsSms.MaxFrequencyahead of the test OTP lookup.ApplyDefaults, matchingvalidateEmail.Why not the send-email hook?
A send-email hook can already swallow mail for test addresses, but it requires deploying and maintaining an HTTP or Postgres function just to get a fixed code, and it still burns the email rate limit. SMS users get this as plain configuration today; this PR gives email users the same thing, so both channels behave consistently and hosted-platform users can be offered it as a setting.
Scope is deliberately limited to the flows the issue asks for: magic link / email OTP sign-in (
sendMagicLink) and signup confirmation (sendConfirmation, whichMagicLinkgoes through for a brand-new user when autoconfirm is off). Recovery, invite, reauthentication, email change andadminGenerateLinkkeep generating real OTPs. Each of those can adopt test OTPs later by swappingcrypto.GenerateOtpfor the newa.generateEmailOtp(email)helper and passingisTestOTPtosendEmail.Design
MailerConfiguration.TestOTP map[string]stringandTestOTPValidUntil conf.Time, plusGetTestOTP(email, now), copied fromSmsProviderConfiguration.(*API).generateEmailOtp(email) (otp string, isTestOTP bool)ininternal/api/mail.go.sendEmailParams.isTestOTP;sendEmailreturnsnilright after the authorization checks when it is set.Tests
internal/conf:MailerConfiguration.GetTestOTP(nil map, hit, case-insensitive hit, miss, valid-until future, expired) andApplyDefaultskey normalization.internal/api/email_test_otp_test.go: direct calls tosendMagicLinkandsendConfirmationwith and without a test OTP (implicit and PKCE), asserting the mock mailer receives nothing and the stored hash equals the expected test hash; authorized-address enforcement; and an HTTP flow:POST /otpfor a new email sends nothing, a wrong code is rejected, the configured code returns a session, the confirmed user's next/otpgoes through the magic link sender, and an expiredTestOTPValidUntilfalls back to a real OTP that is sent.Additional context
Docs:
example.env,hack/test.env, and a README entry under the mailer settings. Follow-ups outside this repo: aconfig.tomlkey in supabase/cli and a docs section in supabase/supabase next to the SMS test OTP text.