diff --git a/dmoj/settings.py b/dmoj/settings.py index ee7a12d25c..cdc899c3e9 100644 --- a/dmoj/settings.py +++ b/dmoj/settings.py @@ -94,6 +94,9 @@ } DMOJ_API_PAGE_SIZE = 1000 +DMOJ_PASSWORD_RESET_LIMIT_WINDOW = 3600 +DMOJ_PASSWORD_RESET_LIMIT_COUNT = 10 + MARKDOWN_STYLES = {} MARKDOWN_DEFAULT_STYLE = {} diff --git a/dmoj/urls.py b/dmoj/urls.py index 1b09932e5c..bca09215f9 100644 --- a/dmoj/urls.py +++ b/dmoj/urls.py @@ -56,7 +56,7 @@ url(r'^password/change/done/$', auth_views.PasswordChangeDoneView.as_view( template_name='registration/password_change_done.html', ), name='password_change_done'), - url(r'^password/reset/$', auth_views.PasswordResetView.as_view( + url(r'^password/reset/$', user.CustomPasswordResetView.as_view( template_name='registration/password_reset.html', html_email_template_name='registration/password_reset_email.html', email_template_name='registration/password_reset_email.txt', diff --git a/judge/views/user.py b/judge/views/user.py index 6a72482395..5457e05476 100644 --- a/judge/views/user.py +++ b/judge/views/user.py @@ -9,7 +9,7 @@ from django.contrib.auth.decorators import login_required from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.models import Permission -from django.contrib.auth.views import LoginView, PasswordChangeView, redirect_to_login +from django.contrib.auth.views import LoginView, PasswordChangeView, PasswordResetView, redirect_to_login from django.contrib.contenttypes.models import ContentType from django.core.cache import cache from django.core.exceptions import PermissionDenied, ValidationError @@ -505,3 +505,13 @@ class UserLogoutView(TitleMixin, TemplateView): def post(self, request, *args, **kwargs): auth_logout(request) return HttpResponseRedirect(request.get_full_path()) + + +class CustomPasswordResetView(PasswordResetView): + def post(self, request, *args, **kwargs): + key = f'pwreset!{request.META["REMOTE_ADDR"]}' + cache.add(key, 0, timeout=settings.DMOJ_PASSWORD_RESET_LIMIT_WINDOW) + if cache.incr(key) > settings.DMOJ_PASSWORD_RESET_LIMIT_COUNT: + return HttpResponse('You sent in too many password reset requests. Please try again later.', + content_type='text/plain', status=429) + return super().post(request, *args, **kwargs)