From 97875e922e59285d65c0d1c166d731cfd2625184 Mon Sep 17 00:00:00 2001 From: Abhijeet Rane Date: Fri, 24 Apr 2026 12:02:12 +0530 Subject: [PATCH] fix(otp): respect minimum_password_length when generating dummy password MagicLink (email OTP) and SmsOtp (phone OTP) signup generate a temporary password for new users and then invoke Signup, which validates the password against config.Password.MinLength. The hardcoded lengths (33 and 64) caused a 422 WeakPasswordError whenever GOTRUE_PASSWORD_MIN_LENGTH was configured above those values, even though the caller never supplied a password. Generate a password that is at least config.Password.MinLength, clamped to MaxPasswordLength (72) to stay within bcrypt's limit. Closes #2456 Co-Authored-By: Claude Opus 4.7 (1M context) --- internal/api/magic_link.go | 6 ++++-- internal/api/otp.go | 6 ++++-- internal/api/otp_test.go | 22 ++++++++++++++++++++++ 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/internal/api/magic_link.go b/internal/api/magic_link.go index 2393059deb..5994bfb0b9 100644 --- a/internal/api/magic_link.go +++ b/internal/api/magic_link.go @@ -83,8 +83,10 @@ func (a *API) MagicLink(w http.ResponseWriter, r *http.Request) error { } if isNewUser { // User either doesn't exist or hasn't completed the signup process. - // Sign them up with temporary password. - password := crypto.GeneratePassword(config.Password.RequiredCharacters, 33) + // Sign them up with temporary password that satisfies the configured + // minimum password length, clamped to the bcrypt limit. + passwordLen := min(max(33, config.Password.MinLength), MaxPasswordLength) + password := crypto.GeneratePassword(config.Password.RequiredCharacters, passwordLen) signUpParams := &SignupParams{ Email: params.Email, diff --git a/internal/api/otp.go b/internal/api/otp.go index 5f12b0bbe3..f88e2e29ce 100644 --- a/internal/api/otp.go +++ b/internal/api/otp.go @@ -138,8 +138,10 @@ func (a *API) SmsOtp(w http.ResponseWriter, r *http.Request) error { } if isNewUser { // User either doesn't exist or hasn't completed the signup process. - // Sign them up with temporary password. - password, err := password.Generate(64, 10, 1, false, true) + // Sign them up with temporary password that satisfies the configured + // minimum password length, clamped to the bcrypt limit. + passwordLen := min(max(64, config.Password.MinLength), MaxPasswordLength) + password, err := password.Generate(passwordLen, 10, 1, false, true) if err != nil { return apierrors.NewInternalServerError("error creating user").WithInternalError(err) } diff --git a/internal/api/otp_test.go b/internal/api/otp_test.go index 7a99f3d9c2..813c0c929f 100644 --- a/internal/api/otp_test.go +++ b/internal/api/otp_test.go @@ -262,6 +262,28 @@ func (ts *OtpTestSuite) TestNoSignupsForOtp() { }) } +func (ts *OtpTestSuite) TestOtpRespectsMinPasswordLength() { + // Regression test for https://github.com/supabase/auth/issues/2456. + // OTP signup internally generates a temporary password that must satisfy + // the configured minimum password length, otherwise the signup call fails + // with a 422 WeakPasswordError even though the caller never supplied a + // password. + ts.Config.Password.MinLength = 40 + + var buffer bytes.Buffer + require.NoError(ts.T(), json.NewEncoder(&buffer).Encode(map[string]interface{}{ + "email": "min-length@example.com", + })) + + req := httptest.NewRequest(http.MethodPost, "/otp", &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) +} + func (ts *OtpTestSuite) TestSubsequentOtp() { ts.Config.SMTP.MaxFrequency = 0 userEmail := "foo@example.com"