From 2d310766d6b41c0561695d4958f66864f35c23b3 Mon Sep 17 00:00:00 2001 From: Maximilien de Dinechin Date: Fri, 17 Oct 2025 18:43:33 +0200 Subject: [PATCH] feat(security): add option to require current password for password updates Adds a new configuration option GOTRUE_SECURITY_UPDATE_PASSWORD_REQUIRE_CURRENT_PASSWORD that addresses session hijacking vulnerabilities by requiring users to provide their current password when changing to a new password. When enabled, the PUT /user endpoint requires a current_password field in the request body alongside the new password. The current password is verified before allowing the password change, preventing attackers with stolen session tokens from changing passwords without knowledge of the current password. This security feature works alongside the existing UpdatePasswordRequireReauthentication option and is disabled by default for backwards compatibility. Related to discussion #32972 --- example.env | 1 + internal/api/user.go | 17 +++++++++++++++++ internal/conf/configuration.go | 1 + 3 files changed, 19 insertions(+) diff --git a/example.env b/example.env index cc6568ebf..f02f26cea 100644 --- a/example.env +++ b/example.env @@ -244,6 +244,7 @@ GOTRUE_LOG_LEVEL="debug" GOTRUE_SECURITY_REFRESH_TOKEN_ROTATION_ENABLED="false" GOTRUE_SECURITY_REFRESH_TOKEN_REUSE_INTERVAL="0" GOTRUE_SECURITY_UPDATE_PASSWORD_REQUIRE_REAUTHENTICATION="false" +GOTRUE_SECURITY_UPDATE_PASSWORD_REQUIRE_CURRENT_PASSWORD="false" GOTRUE_OPERATOR_TOKEN="unused-operator-token" GOTRUE_RATE_LIMIT_HEADER="X-Forwarded-For" GOTRUE_RATE_LIMIT_EMAIL_SENT="100" diff --git a/internal/api/user.go b/internal/api/user.go index 723bce144..2458e8837 100644 --- a/internal/api/user.go +++ b/internal/api/user.go @@ -18,6 +18,7 @@ import ( type UserUpdateParams struct { Email string `json:"email"` Password *string `json:"password"` + CurrentPassword *string `json:"current_password"` Nonce string `json:"nonce"` Data map[string]interface{} `json:"data"` AppData map[string]interface{} `json:"app_metadata,omitempty"` @@ -147,6 +148,22 @@ func (a *API) UserUpdate(w http.ResponseWriter, r *http.Request) error { } if params.Password != nil { + if config.Security.UpdatePasswordRequireCurrentPassword { + if params.CurrentPassword == nil || *params.CurrentPassword == "" { + return apierrors.NewBadRequestError(apierrors.ErrorCodeValidationFailed, "Current password is required to update password") + } + + if user.HasPassword() { + authenticated, _, err := user.Authenticate(ctx, db, *params.CurrentPassword, config.Security.DBEncryption.DecryptionKeys, false, "") + if err != nil { + return apierrors.NewInternalServerError("Error verifying current password").WithInternalError(err) + } + if !authenticated { + return apierrors.NewBadRequestError(apierrors.ErrorCodeInvalidCredentials, InvalidLoginMessage) + } + } + } + if config.Security.UpdatePasswordRequireReauthentication { now := time.Now() // we require reauthentication if the user hasn't signed in recently in the current session diff --git a/internal/conf/configuration.go b/internal/conf/configuration.go index 84ec2ee16..6c01804f8 100644 --- a/internal/conf/configuration.go +++ b/internal/conf/configuration.go @@ -726,6 +726,7 @@ type SecurityConfiguration struct { RefreshTokenRotationEnabled bool `json:"refresh_token_rotation_enabled" split_words:"true" default:"true"` RefreshTokenReuseInterval int `json:"refresh_token_reuse_interval" split_words:"true"` UpdatePasswordRequireReauthentication bool `json:"update_password_require_reauthentication" split_words:"true"` + UpdatePasswordRequireCurrentPassword bool `json:"update_password_require_current_password" split_words:"true" default:"false"` ManualLinkingEnabled bool `json:"manual_linking_enabled" split_words:"true" default:"false"` DBEncryption DatabaseEncryptionConfiguration `json:"database_encryption" split_words:"true"`