From d033d5f036cae0b6683a17180c768cf79476c0fa Mon Sep 17 00:00:00 2001 From: Magnus Hagander Date: Fri, 23 Mar 2018 13:18:55 +0100 Subject: [PATCH] Re-implement password reset token sending natively The django version of password reset is broken in multiple way. What's hurting us in particular is it cannot reset the password of a user where the old password was generated by a deprecated hasher. Which, of course, is exactly one of the cases where being able to reset the password is important. We still use the same infrastructure, and we use the actual django code for *changing* the password -- this just replaces the token sender with something that's a lot simpler and less broken. --- pgweb/account/forms.py | 5 ++++- pgweb/account/views.py | 32 +++++++++++++++++++++++++++----- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/pgweb/account/forms.py b/pgweb/account/forms.py index 118443ec..0e7363e5 100644 --- a/pgweb/account/forms.py +++ b/pgweb/account/forms.py @@ -1,5 +1,5 @@ from django import forms -from django.contrib.auth.forms import AuthenticationForm +from django.contrib.auth.forms import AuthenticationForm, PasswordResetForm import re @@ -146,3 +146,6 @@ class ChangeEmailForm(forms.Form): if email1 != email2: raise forms.ValidationError("Email addresses don't match") return email2 + +class PgwebPasswordResetForm(forms.Form): + email = forms.EmailField() diff --git a/pgweb/account/views.py b/pgweb/account/views.py index b9db6265..bd689e8b 100644 --- a/pgweb/account/views.py +++ b/pgweb/account/views.py @@ -36,7 +36,7 @@ from models import CommunityAuthSite, EmailChangeToken from forms import PgwebAuthenticationForm from forms import SignupForm, SignupOauthForm from forms import UserForm, UserProfileForm, ContributorForm -from forms import ChangeEmailForm +from forms import ChangeEmailForm, PgwebPasswordResetForm import logging log = logging.getLogger(__name__) @@ -235,6 +235,9 @@ def changepwd(request): post_change_redirect='/account/changepwd/done/') def resetpwd(request): + # Basic django password reset feature is completely broken. For example, it does not support + # resetting passwords for users with "old hashes", which means they have no way to ever + # recover. So implement our own, since it's quite the trivial feature. if request.method == "POST": try: u = User.objects.get(email__iexact=request.POST['email']) @@ -242,10 +245,29 @@ def resetpwd(request): return HttpServerError(request, "This account cannot change password as it's connected to a third party login site.") except User.DoesNotExist: log.info("Attempting to reset password of {0}, user not found".format(request.POST['email'])) - log.info("Initiating password set from {0}".format(get_client_ip(request))) - return authviews.password_reset(request, template_name='account/password_reset.html', - email_template_name='account/password_reset_email.txt', - post_reset_redirect='/account/reset/done/') + return HttpResponseRedirect('/account/reset/done/') + + form = PgwebPasswordResetForm(data=request.POST) + if form.is_valid(): + log.info("Initiating password set from {0} for {1}".format(get_client_ip(request), form.cleaned_data['email'])) + token = default_token_generator.make_token(u) + send_template_mail(settings.NOREPLY_FROM, + form.cleaned_data['email'], + 'Password reset for your postgresql.org account', + 'account/password_reset_email.txt', + { + 'user': u, + 'uid': urlsafe_base64_encode(force_bytes(u.pk)), + 'token': token, + }, + ) + return HttpResponseRedirect('/account/reset/done/') + else: + form = PgwebPasswordResetForm() + + return render_pgweb(request, 'account', 'account/password_reset.html', { + 'form': form, + }) def change_done(request): log.info("Password change done from {0}".format(get_client_ip(request))) -- 2.39.5