@@ -768,6 +768,13 @@ export const auth = betterAuth({
768768 } ,
769769 emailAndPassword : {
770770 enabled : true ,
771+ /**
772+ * Same flag that hides the email/password signup form (DISABLE_EMAIL_SIGNUP).
773+ * Blocks /sign-up/email at the better-auth layer so ripping out the frontend
774+ * form cannot be bypassed by calling the endpoint directly. Existing users
775+ * can still sign in.
776+ */
777+ disableSignUp : isEmailSignupDisabled ,
771778 requireEmailVerification : isEmailVerificationEnabled ,
772779 /**
773780 * When someone signs up with an already-registered email, better-auth returns a
@@ -891,11 +898,6 @@ export const auth = betterAuth({
891898 } )
892899 }
893900
894- if ( isEmailSignupDisabled && ctx . path . startsWith ( '/sign-up/email' ) )
895- throw new APIError ( 'FORBIDDEN' , {
896- message : 'Email sign-up is disabled. Please use Google, Microsoft, or GitHub.' ,
897- } )
898-
899901 const isSignIn = ctx . path . startsWith ( '/sign-in' )
900902 const isSignUp = ctx . path . startsWith ( '/sign-up' )
901903
@@ -1039,6 +1041,16 @@ export const auth = betterAuth({
10391041 throw error
10401042 }
10411043 } ,
1044+ /**
1045+ * Without this, /sign-in/email-otp auto-registers any unknown email —
1046+ * bypassing the signup gate entirely (no captcha, no /sign-up path).
1047+ * Gated by the same DISABLE_EMAIL_SIGNUP flag as the signup form (and by
1048+ * DISABLE_REGISTRATION, whose /sign-up path check has the same blind
1049+ * spot); when set, better-auth also silently skips sending OTPs to
1050+ * unknown emails (enumeration-safe) while existing users keep OTP
1051+ * sign-in.
1052+ */
1053+ disableSignUp : isEmailSignupDisabled || isRegistrationDisabled ,
10421054 sendVerificationOnSignUp : false ,
10431055 otpLength : 6 , // Explicitly set the OTP length
10441056 expiresIn : 15 * 60 , // 15 minutes in seconds
0 commit comments