Skip to content

Commit a2607f1

Browse files
JulienBergeroclaude
andcommitted
fix: exempt recovery sessions from current password requirement
When updating a password via the recovery (password reset) flow, the user cannot know their current password — that is the whole point of the flow. This commit: 1. verifyPost: issues refresh tokens with models.Recovery auth method for RecoveryVerification type, so the AMR claim is correctly stored as "recovery" instead of "otp". 2. user.go: checks for the recovery AMR claim when GOTRUE_SECURITY_UPDATE_PASSWORD_REQUIRE_CURRENT_PASSWORD is enabled, and skips the current-password check for recovery sessions. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent a9edb4e commit a2607f1

File tree

2 files changed

+50
-10
lines changed

2 files changed

+50
-10
lines changed

internal/api/user.go

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -149,17 +149,41 @@ func (a *API) UserUpdate(w http.ResponseWriter, r *http.Request) error {
149149

150150
if params.Password != nil {
151151
if config.Security.UpdatePasswordRequireCurrentPassword {
152-
if params.CurrentPassword == nil || *params.CurrentPassword == "" {
153-
return apierrors.NewBadRequestError(apierrors.ErrorCodeValidationFailed, "Current password is required to update password")
152+
// Recovery sessions (password reset flow) are exempt — the user
153+
// cannot know their current password, that is why they are resetting it.
154+
isRecoverySession := false
155+
if session != nil {
156+
amrMethods := []string{}
157+
for _, claim := range session.AMRClaims {
158+
amrMethods = append(amrMethods, claim.GetAuthenticationMethod())
159+
if claim.GetAuthenticationMethod() == models.Recovery.String() {
160+
isRecoverySession = true
161+
break
162+
}
163+
}
164+
logrus.WithFields(logrus.Fields{
165+
"session_id": session.ID,
166+
"amr_methods": amrMethods,
167+
"amr_claims_count": len(session.AMRClaims),
168+
"is_recovery_session": isRecoverySession,
169+
}).Info("[delos] password update: session AMR check")
170+
} else {
171+
logrus.Info("[delos] password update: session is nil")
154172
}
155173

156-
if user.HasPassword() {
157-
authenticated, _, err := user.Authenticate(ctx, db, *params.CurrentPassword, config.Security.DBEncryption.DecryptionKeys, false, "")
158-
if err != nil {
159-
return apierrors.NewInternalServerError("Error verifying current password").WithInternalError(err)
174+
if !isRecoverySession {
175+
if params.CurrentPassword == nil || *params.CurrentPassword == "" {
176+
return apierrors.NewBadRequestError(apierrors.ErrorCodeValidationFailed, "Current password is required to update password")
160177
}
161-
if !authenticated {
162-
return apierrors.NewBadRequestError(apierrors.ErrorCodeInvalidCredentials, InvalidLoginMessage)
178+
179+
if user.HasPassword() {
180+
authenticated, _, err := user.Authenticate(ctx, db, *params.CurrentPassword, config.Security.DBEncryption.DecryptionKeys, false, "")
181+
if err != nil {
182+
return apierrors.NewInternalServerError("Error verifying current password").WithInternalError(err)
183+
}
184+
if !authenticated {
185+
return apierrors.NewBadRequestError(apierrors.ErrorCodeInvalidCredentials, InvalidLoginMessage)
186+
}
163187
}
164188
}
165189
}

internal/api/verify.go

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,15 @@ func (a *API) verifyGet(w http.ResponseWriter, r *http.Request, params *VerifyPa
188188
}
189189

190190
if isImplicitFlow(flowType) {
191-
token, terr = a.issueRefreshToken(r, tx, user, models.OTP, grantParams)
191+
issuedAuthMethod := models.OTP
192+
if params.Type == mail.RecoveryVerification {
193+
issuedAuthMethod = models.Recovery
194+
}
195+
logrus.WithFields(logrus.Fields{
196+
"params_type": params.Type,
197+
"issued_auth_method": issuedAuthMethod.String(),
198+
}).Info("[delos] verify: issuing refresh token with auth method")
199+
token, terr = a.issueRefreshToken(r, tx, user, issuedAuthMethod, grantParams)
192200
if terr != nil {
193201
return terr
194202
}
@@ -288,7 +296,15 @@ func (a *API) verifyPost(w http.ResponseWriter, r *http.Request, params *VerifyP
288296
if terr := tx.Reload(user); terr != nil {
289297
return terr
290298
}
291-
token, terr = a.issueRefreshToken(r, tx, user, models.OTP, grantParams)
299+
issuedAuthMethod := models.OTP
300+
if params.Type == mail.RecoveryVerification {
301+
issuedAuthMethod = models.Recovery
302+
}
303+
logrus.WithFields(logrus.Fields{
304+
"params_type": params.Type,
305+
"issued_auth_method": issuedAuthMethod.String(),
306+
}).Info("[delos] verifyPost: issuing refresh token with auth method")
307+
token, terr = a.issueRefreshToken(r, tx, user, issuedAuthMethod, grantParams)
292308
if terr != nil {
293309
return terr
294310
}

0 commit comments

Comments
 (0)