fix: enhance password reset flow to prevent username enumeration

This commit is contained in:
shariquerik 2026-04-14 15:23:04 +05:30
parent db4afbd021
commit f00c4b7738
2 changed files with 18 additions and 26 deletions

View file

@ -1127,24 +1127,24 @@ def sign_up(email: str, full_name: str, redirect_to: str) -> tuple[int, str]:
@frappe.whitelist(allow_guest=True, methods=["POST"])
@rate_limit(limit=get_password_reset_limit, seconds=60 * 60)
def reset_password(user: str) -> str:
# Always return the same generic response regardless of whether the user
# exists, is disabled, or is restricted. This prevents username enumeration
# via different messages or HTTP status codes (CWE-204).
try:
user: User = frappe.get_doc("User", user)
if user.name == "Administrator":
return "not allowed"
if not user.enabled:
return "disabled"
user.validate_reset_password()
user.reset_password(send_email=True)
return frappe.msgprint(
msg=_("Password reset instructions have been sent to {}'s email").format(user.full_name),
title=_("Password Email Sent"),
)
user_doc: User = frappe.get_doc("User", user)
if user_doc.name != "Administrator" and user_doc.enabled:
user_doc.validate_reset_password()
user_doc.reset_password(send_email=True)
# For Administrator or disabled users: silently skip — same response below
except frappe.DoesNotExistError:
frappe.local.response["http_status_code"] = 404
frappe.clear_messages()
return "not found"
# Do not reveal whether the account exists — fall through to generic response
return frappe.msgprint(
msg=_("If an account with this email exists, password reset instructions have been sent."),
title=_("Password Reset"),
)
@frappe.whitelist()

View file

@ -247,17 +247,9 @@ login.login_handlers = (function () {
window.location.href = data.home_page;
}
} else if (window.location.hash === '#forgot') {
if (data.message === 'not found') {
login.set_status({{ _("Not a valid user") | tojson }}, 'red');
} else if (data.message == 'not allowed') {
login.set_status({{ _("Not Allowed") | tojson }}, 'red');
} else if (data.message == 'disabled') {
login.set_status({{ _("Not Allowed: Disabled User") | tojson }}, 'red');
} else {
login.set_status({{ _("Instructions Emailed") | tojson }}, 'green');
}
// Always show the same message regardless of whether the account
// exists or not, to prevent username enumeration (CWE-204).
login.set_status({{ _("Instructions Emailed") | tojson }}, 'green');
} else if (window.location.hash === '#signup') {
if (cint(data.message[0]) == 0) {
login.set_status(data.message[1], 'red');