reset_password: Show user-facing page on rate-limit.

This commit is contained in:
Alex Vandiver 2021-11-04 18:21:26 -07:00 committed by Tim Abbott
parent d3ecbf96a8
commit c8badbd858
3 changed files with 29 additions and 7 deletions

View File

@ -326,13 +326,13 @@ class ZulipPasswordResetForm(PasswordResetForm):
rate_limit_password_reset_form_by_email(email) rate_limit_password_reset_form_by_email(email)
rate_limit_request_by_ip(request, domain="sends_email_by_ip") rate_limit_request_by_ip(request, domain="sends_email_by_ip")
except RateLimited: except RateLimited:
# TODO: Show an informative, user-facing error message.
logging.info( logging.info(
"Too many password reset attempts for email %s from %s", "Too many password reset attempts for email %s from %s",
email, email,
request.META["REMOTE_ADDR"], request.META["REMOTE_ADDR"],
) )
return # The view will handle the RateLimit exception and render an appropriate page
raise
user: Optional[UserProfile] = None user: Optional[UserProfile] = None
try: try:

View File

@ -227,6 +227,18 @@ class RateLimitTests(ZulipTestCase):
is_json=False, is_json=False,
) )
@rate_limit_rule(1, 5, domain="sends_email_by_ip")
def test_password_reset_rate_limiting(self) -> None:
with self.assertLogs(level="INFO") as m:
self.do_test_hit_ratelimits(
lambda: self.client_post("/accounts/password/reset/", {"email": "new@zulip.com"}),
is_json=False,
)
self.assertEqual(
m.output,
["INFO:root:Too many password reset attempts for email new@zulip.com from 127.0.0.1"],
)
# Test whether submitting multiple emails is handled correctly. # Test whether submitting multiple emails is handled correctly.
# The limit is set to 10 per second, so 5 requests with 2 emails # The limit is set to 10 per second, so 5 requests with 2 emails
# submitted in each should be allowed. # submitted in each should be allowed.

View File

@ -51,6 +51,7 @@ from zerver.lib.exceptions import (
JsonableError, JsonableError,
PasswordAuthDisabledError, PasswordAuthDisabledError,
PasswordResetRequiredError, PasswordResetRequiredError,
RateLimited,
RealmDeactivatedError, RealmDeactivatedError,
UserDeactivatedError, UserDeactivatedError,
) )
@ -992,11 +993,20 @@ def password_reset(request: HttpRequest) -> HttpResponse:
) )
return HttpResponseRedirect(redirect_url) return HttpResponseRedirect(redirect_url)
try:
response = DjangoPasswordResetView.as_view( response = DjangoPasswordResetView.as_view(
template_name="zerver/reset.html", template_name="zerver/reset.html",
form_class=ZulipPasswordResetForm, form_class=ZulipPasswordResetForm,
success_url="/accounts/password/reset/done/", success_url="/accounts/password/reset/done/",
)(request) )(request)
except RateLimited as e:
assert e.secs_to_freedom is not None
return render(
request,
"zerver/rate_limit_exceeded.html",
context={"retry_after": int(e.secs_to_freedom)},
status=429,
)
assert isinstance(response, HttpResponse) assert isinstance(response, HttpResponse)
return response return response