Re-implement password reset token sending natively
authorMagnus Hagander <magnus@hagander.net>
Fri, 23 Mar 2018 12:18:55 +0000 (13:18 +0100)
committerMagnus Hagander <magnus@hagander.net>
Fri, 23 Mar 2018 12:21:16 +0000 (13:21 +0100)
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
pgweb/account/views.py

index 118443ecbdb4c8074ea31c9b2f20012940fd560a..0e7363e580dd96c350ce1d40f005012743ba61ec 100644 (file)
@@ -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()
index b9db62658c79b7c0f5fdffadf4f7ab6802c402be..bd689e8b24358b34cb7519291c3877ecce9b8a1c 100644 (file)
@@ -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)))