Browse Source

#387: Revamped forgotten password handling

Rafał Pitoń 11 years ago
parent
commit
1cd58afe5e

+ 0 - 23
misago/templates/misago/emails/forgottenpassword/confirm.html

@@ -1,23 +0,0 @@
-{% extends "misago/emails/base.html" %}
-{% load i18n misago_capture %}
-
-
-{% block content %}
-{% blocktrans trimmed with username=recipient.username %}
-{{ username }}, you are receiving this message because you have started forgotten password change procedure for your forum account.
-{% endblocktrans %}
-<br>
-<br>
-{% blocktrans trimmed %}
-To replace your account password with new one click the link below:
-{% endblocktrans %}
-<br>
-<br>
-<a href="{{ SITE_ADDRESS }}{% url 'misago:reset_password_confirm' user_id=recipient.pk token=confirmation_token %}">{% trans "Change my password" %}</a>
-<br>
-<br>
-{% blocktrans trimmed %}
-New password will be set on your account and sent to you in next e-mail.
-{% endblocktrans %}
-<br>
-{% endblock content %}

+ 0 - 18
misago/templates/misago/emails/forgottenpassword/confirm.txt

@@ -1,18 +0,0 @@
-{% extends "misago/emails/base.txt" %}
-{% load i18n %}
-
-
-{% block content %}
-{% blocktrans trimmed with username=recipient.username %}
-{{ username }}, you are receiving this message because you have started forgotten password change procedure for your forum account.
-{% endblocktrans %}
-
-{% blocktrans trimmed %}
-To replace your account password with new one click the link below:
-{% endblocktrans %}
-{{ SITE_ADDRESS }}{% url 'misago:reset_password_confirm' user_id=recipient.pk token=confirmation_token %}
-
-{% blocktrans trimmed %}
-New password will be set on your account and sent to you in next e-mail.
-{% endblocktrans %}
-{% endblock content %}

+ 18 - 0
misago/templates/misago/emails/forgottenpassword/form_link.html

@@ -0,0 +1,18 @@
+{% extends "misago/emails/base.html" %}
+{% load i18n misago_capture %}
+
+
+{% block content %}
+{% blocktrans trimmed with username=recipient.username %}
+{{ username }}, you are receiving this message because you want to change forgotten password for your forum account.
+{% endblocktrans %}
+<br>
+<br>
+{% blocktrans trimmed %}
+To change your account password click the link below:
+{% endblocktrans %}
+<br>
+<br>
+<a href="{{ SITE_ADDRESS }}{% url 'misago:reset_password_form' user_id=recipient.pk token=confirmation_token %}">{% trans "Set new password" %}</a>
+<br>
+{% endblock content %}

+ 14 - 0
misago/templates/misago/emails/forgottenpassword/form_link.txt

@@ -0,0 +1,14 @@
+{% extends "misago/emails/base.txt" %}
+{% load i18n %}
+
+
+{% block content %}
+{% blocktrans trimmed with username=recipient.username %}
+{{ username }}, you are receiving this message because you want to change forgotten password for your forum account.
+{% endblocktrans %}
+
+{% blocktrans trimmed %}
+To change your account password click the link below:
+{% endblocktrans %}
+{{ SITE_ADDRESS }}{% url 'misago:reset_password_form' user_id=recipient.pk token=confirmation_token %}
+{% endblock content %}

+ 0 - 26
misago/templates/misago/emails/forgottenpassword/new.html

@@ -1,26 +0,0 @@
-{% extends "misago/emails/base.html" %}
-{% load i18n misago_capture %}
-
-
-{% block content %}
-{% blocktrans trimmed with username=recipient.username %}
-{{ username }}, your new password is:
-{% endblocktrans %}
-<br>
-<br>
-<div style="background-color: #eee; padding: 8px 0px; font-size: 18px; text-align: center; font-family: Menlo, Monaco, Consolas, monospace">{{ new_password }}</div>
-<br>
-<br>
-{% capture trimmed as login_link %}
-<a href="{{ SITE_ADDRESS }}{% url LOGIN_URL %}">{% trans "this form" %}</a>
-{% endcapture %}
-{% blocktrans trimmed with login_form=login_link|safe %}
-You can now sign in to your account with new password using {{ login_form }}.
-{% endblocktrans %}
-<br>
-<br>
-{% blocktrans trimmed %}
-Please change your password to custom one as soon as you sign in to your account.
-{% endblocktrans %}
-<br>
-{% endblock content %}

+ 0 - 20
misago/templates/misago/emails/forgottenpassword/new.txt

@@ -1,20 +0,0 @@
-{% extends "misago/emails/base.txt" %}
-{% load i18n %}
-
-
-{% block content %}
-{% blocktrans trimmed with username=recipient.username %}
-{{ username }}, your new password is:
-{% endblocktrans %}
-
-{{ new_password }}
-
-{% blocktrans trimmed %}
-You can now sign in to your account with new password using the form below:
-{% endblocktrans %}
-{{ SITE_ADDRESS }}{% url LOGIN_URL %}
-
-{% blocktrans trimmed %}
-Please change your password to custom one as soon as you sign in to your account.
-{% endblocktrans %}
-{% endblock content %}

+ 0 - 29
misago/templates/misago/forgottenpassword/confirmation_sent.html

@@ -1,29 +0,0 @@
-{% extends "misago/base.html" %}
-{% load i18n %}
-
-
-{% block title %}{% trans "Change password confirmation sent" %} | {{ block.super }}{% endblock %}
-
-
-{% block content %}
-<div class="page-header">
-  <div class="container">
-    <h1>
-      <span class="fa fa-key">
-      {% trans "Change password confirmation sent" %}
-    </h1>
-  </div>
-</div>
-
-<div class="container">
-  <div class="misago-markup">
-
-    <p class="lead">
-      {% blocktrans trimmed with email=requesting_user.email %}
-      We have sent an e-mail to {{ email }} with a link that you have to click to confirm your request for new password.
-      {% endblocktrans %}
-    </p>
-
-  </div>
-</div>
-{% endblock content %}

+ 3 - 3
misago/templates/misago/forgottenpassword/password_sent.html → misago/templates/misago/forgottenpassword/link_sent.html

@@ -2,7 +2,7 @@
 {% load i18n %}
 
 
-{% block title %}{% trans "Password changed" %} | {{ block.super }}{% endblock %}
+{% block title %}{% trans "Change password link sent" %} | {{ block.super }}{% endblock %}
 
 
 {% block content %}
@@ -10,7 +10,7 @@
   <div class="container">
     <h1>
       <span class="fa fa-key">
-      {% trans "Password changed" %}
+      {% trans "Change password link sent" %}
     </h1>
   </div>
 </div>
@@ -20,7 +20,7 @@
 
     <p class="lead">
       {% blocktrans trimmed with email=requesting_user.email %}
-      New password has been set on your account and was send to {{ email }}.
+      We have sent an e-mail to {{ email }} with a link to form that will let you change your password.
       {% endblocktrans %}
     </p>
 

+ 4 - 4
misago/templates/misago/forgottenpassword/request.html

@@ -21,9 +21,9 @@
 
       <h2>{% trans "Recovering password" %}</h2>
 
-      <p>{% trans "Because user passwords are processed in an irreversible way before being saved to database, it is not possible for us to simply send you your password. Instead your current password can be replaced with new randomly generated password." %}</p>
+      <p>{% trans "Because user passwords are processed in an irreversible way before being saved to database, it is not possible for us to simply send you your password." %}</p>
 
-      <p>{% trans "In order to protect our users from fraudulent password change, you need to first confirm your wish to change password by clicking the confirmation link that will be generated and sent to e-mail address associated with your account." %}</p>
+      <p>{% trans "Instead you will be sent link to form that will let you set new password on your account. This link will expire after you change password or seven days pass." %}</p>
 
     </div>
     <div class="col-md-4">
@@ -33,7 +33,7 @@
           {% csrf_token %}
 
           <div class="form-header">
-            <h3>{% trans "Request confirmation link" %}</h3>
+            <h3>{% trans "Request link" %}</h3>
           </div>
 
           {% include "misago/auth_form_errors.html" %}
@@ -49,7 +49,7 @@
           </div>
 
           <div class="form-footer">
-            <button class="btn btn-primary btn-block">{% trans "Send confirmation link" %}</button>
+            <button class="btn btn-primary btn-block">{% trans "Send link" %}</button>
           </div>
 
         </form>

+ 56 - 0
misago/templates/misago/forgottenpassword/reset_password_form.html

@@ -0,0 +1,56 @@
+{% extends "misago/base.html" %}
+{% load i18n %}
+
+
+{% block title %}{% blocktrans trimmed with username=requesting_user.username %}
+Set new password for {{ username }}
+{% endblocktrans %} | {{ block.super }}{% endblock %}
+
+
+{% block content %}
+<div class="page-header">
+  <div class="container">
+    <h1>
+      <span class="fa fa-key">
+      {% blocktrans trimmed with username=requesting_user.username %}
+      Set new password for {{ username }}
+      {% endblocktrans %}
+    </h1>
+  </div>
+</div>
+<div class="container">
+
+  <div class="row">
+    <div class="col-md-4 col-md-offset-4">
+
+      <div class="form-panel">
+        <form method="POST" role="form">
+          {% csrf_token %}
+
+          <div class="form-header">
+            <h2>{% trans "Set new password" %}</h2>
+          </div>
+
+          {% include "misago/form_errors.html" %}
+
+          <div class="form-body no-fieldsets">
+            <div class="form-group">
+              <div class="control-input">
+                <input type="password" name="new_password" class="form-control input-lg" placeholder="{% trans "Enter new password" %}">
+              </div>
+            </div>
+
+          </div>
+
+          <div class="form-footer">
+            <button class="btn btn-primary btn-block">{% trans "Change password" %}</button>
+          </div>
+
+        </form>
+      </div>
+
+    </div>
+  </div>
+
+</div>
+{% endblock content %}

+ 17 - 0
misago/users/forms/auth.py

@@ -7,6 +7,7 @@ from django.utils.translation import ugettext_lazy as _
 
 from misago.core import forms
 from misago.users.bans import get_user_ban
+from misago.users.validators import validate_password
 
 
 class MisagoAuthMixin(object):
@@ -166,3 +167,19 @@ class ResetPasswordForm(GetUserForm):
     def confirm_allowed(self, user):
         self.confirm_user_not_banned(user)
         self.confirm_user_active(user)
+
+
+class SetNewPasswordForm(MisagoAuthMixin, forms.Form):
+    new_password = forms.CharField(label=_("New password"),
+                                   widget=forms.PasswordInput)
+
+    def clean(self):
+        data = super(SetNewPasswordForm, self).clean()
+
+        new_password = data.get('new_password')
+        if not new_password or len(new_password) > 250:
+            raise forms.ValidationError(_("You have to fill out form."))
+
+        validate_password(new_password)
+
+        return data

+ 18 - 13
misago/users/tests/test_forgottenpassword_views.py

@@ -14,7 +14,7 @@ class ForgottenPasswordViewsTests(TestCase):
         self.assertEqual(response.status_code, 200)
 
     def test_view_submit(self):
-        """request new password view sends confirmation mail"""
+        """request new password view sends reset link mail"""
         User = get_user_model()
         User.objects.create_user('Bob', 'bob@test.com', 'Pass.123')
 
@@ -24,7 +24,7 @@ class ForgottenPasswordViewsTests(TestCase):
 
         self.assertEqual(response.status_code, 302)
 
-        self.assertIn('password change', mail.outbox[0].subject)
+        self.assertIn('Change Bob password', mail.outbox[0].subject)
 
     def test_view_submit_banned(self):
         """request new password view errors for banned users"""
@@ -67,9 +67,8 @@ class ForgottenPasswordViewsTests(TestCase):
         password_token = make_password_reset_token(test_user)
 
         response = self.client.get(
-            reverse('misago:reset_password_confirm',
-                    kwargs={'user_id': test_user.pk,
-                            'token': password_token}))
+            reverse('misago:reset_password_form',
+                    kwargs={'user_id': test_user.pk, 'token': password_token}))
         self.assertEqual(response.status_code, 302)
 
         test_user = User.objects.get(pk=test_user.pk)
@@ -87,9 +86,8 @@ class ForgottenPasswordViewsTests(TestCase):
         password_token = make_password_reset_token(test_user)
 
         response = self.client.get(
-            reverse('misago:reset_password_confirm',
-                    kwargs={'user_id': test_user.pk,
-                            'token': password_token}))
+            reverse('misago:reset_password_form',
+                    kwargs={'user_id': test_user.pk, 'token': password_token}))
         self.assertEqual(response.status_code, 302)
 
         test_user = User.objects.get(pk=test_user.pk)
@@ -106,12 +104,19 @@ class ForgottenPasswordViewsTests(TestCase):
         password_token = make_password_reset_token(test_user)
 
         response = self.client.get(
-            reverse('misago:reset_password_confirm',
-                    kwargs={'user_id': test_user.pk,
-                            'token': password_token}))
+            reverse('misago:reset_password_form',
+                    kwargs={'user_id': test_user.pk, 'token': password_token}))
+        self.assertEqual(response.status_code, 200)
+        self.assertIn('Set new password for Bob', response.content)
+
+        test_user = User.objects.get(pk=test_user.pk)
+        self.assertEqual(test_user.password, old_password)
+
+        response = self.client.post(
+            reverse('misago:reset_password_form',
+                    kwargs={'user_id': test_user.pk, 'token': password_token}),
+            data={'new_password': 'loremipsum123'})
         self.assertEqual(response.status_code, 302)
 
         test_user = User.objects.get(pk=test_user.pk)
         self.assertNotEqual(test_user.password, old_password)
-
-        self.assertIn('New password', mail.outbox[0].subject)

+ 2 - 3
misago/users/urls.py

@@ -22,9 +22,8 @@ urlpatterns += patterns('misago.users.views.activation',
 
 urlpatterns += patterns('misago.users.views.forgottenpassword',
     url(r'^forgotten-password/$', 'request_reset', name='request_password_reset'),
-    url(r'^forgotten-password/confirmation-sent/$', 'confirmation_sent', name='reset_password_confirmation_sent'),
-    url(r'^forgotten-password/(?P<user_id>\d+)/(?P<token>[a-zA-Z0-9]+)/$', 'reset_password', name='reset_password_confirm'),
-    url(r'^forgotten-password/password-sent/$', 'new_password_sent', name='request_password_new_sent'),
+    url(r'^forgotten-password/link-sent/$', 'link_sent', name='reset_password_link_sent'),
+    url(r'^forgotten-password/(?P<user_id>\d+)/(?P<token>[a-zA-Z0-9]+)/$', 'reset_password_form', name='reset_password_form'),
 )
 
 

+ 23 - 41
misago/users/views/forgottenpassword.py

@@ -10,7 +10,7 @@ from misago.core.mail import mail_user
 
 from misago.users.bans import get_user_ban
 from misago.users.decorators import deny_authenticated, deny_banned_ips
-from misago.users.forms.auth import ResetPasswordForm
+from misago.users.forms.auth import ResetPasswordForm, SetNewPasswordForm
 from misago.users.models import ACTIVATION_REQUIRED_NONE
 from misago.users.tokens import (make_password_reset_token,
                                  is_password_reset_token_valid)
@@ -32,9 +32,9 @@ def request_reset(request):
         form = ResetPasswordForm(request.POST)
         if form.is_valid():
             requesting_user = form.user_cache
-            request.session['confirmation_sent_to'] = requesting_user.pk
+            request.session['reset_password_link_sent_to'] = requesting_user.pk
 
-            mail_subject = _("Confirm %(username)s password change "
+            mail_subject = _("Change %(username)s password "
                              "on %(forum_title)s forums")
             subject_formats = {'username': requesting_user.username,
                                'forum_title': settings.forum_name}
@@ -44,25 +44,25 @@ def request_reset(request):
 
             mail_user(
                 request, requesting_user, mail_subject,
-                'misago/emails/forgottenpassword/confirm',
+                'misago/emails/forgottenpassword/form_link',
                 {'confirmation_token': confirmation_token})
 
-            return redirect('misago:reset_password_confirmation_sent')
+            return redirect('misago:reset_password_link_sent')
 
     return render(request, 'misago/forgottenpassword/request.html',
                   {'form': form})
 
 
 @reset_view
-def confirmation_sent(request):
-    requesting_user_pk = request.session.get('confirmation_sent_to')
+def link_sent(request):
+    requesting_user_pk = request.session.get('reset_password_link_sent_to')
     if not requesting_user_pk:
         raise Http404()
 
     User = get_user_model()
     requesting_user = get_object_or_404(User.objects, pk=requesting_user_pk)
 
-    return render(request, 'misago/forgottenpassword/confirmation_sent.html',
+    return render(request, 'misago/forgottenpassword/link_sent.html',
                   {'requesting_user': requesting_user})
 
 
@@ -76,7 +76,7 @@ class ResetError(Exception):
 
 
 @reset_view
-def reset_password(request, user_id, token):
+def reset_password_form(request, user_id, token):
     User = get_user_model()
     requesting_user = get_object_or_404(User.objects, pk=user_id)
 
@@ -98,8 +98,8 @@ def reset_password(request, user_id, token):
             message = message % {'username': requesting_user.username}
             raise ResetError(message)
         if not is_password_reset_token_valid(requesting_user, token):
-            message = _("%(username)s, your confirmation link is invalid. "
-                        "Try again or request new confirmation message.")
+            message = _("%(username)s, your link is invalid. "
+                        "Try again or request new link.")
             message = message % {'username': requesting_user.username}
             raise ResetError(message)
     except ResetStopped as e:
@@ -109,36 +109,18 @@ def reset_password(request, user_id, token):
         messages.error(request, e.args[0])
         return redirect('misago:request_password_reset')
 
-    fake = Factory.create()
-    new_password = ' '.join([fake.word() for x in xrange(4)])
-    while len(new_password) < settings.password_length_min:
-        new_password = '%s %s' % (new_password, fake.word())
-
-    requesting_user.set_password(new_password)
-    requesting_user.save(update_fields=['password'])
-
-    mail_subject = _("New password on %(forum_title)s forums")
-    mail_subject = mail_subject % {'forum_title': settings.forum_name}
-
-    confirmation_token = make_password_reset_token(requesting_user)
-
-    mail_user(
-        request, requesting_user, mail_subject,
-        'misago/emails/forgottenpassword/new',
-        {'new_password': new_password})
-
-    request.session['password_sent_to'] = requesting_user.pk
-    return redirect('misago:request_password_new_sent')
-
+    form = SetNewPasswordForm()
+    if request.method == 'POST':
+        form = SetNewPasswordForm(request.POST)
+        if form.is_valid():
+            requesting_user.set_password(form.cleaned_data['new_password'])
+            requesting_user.save(update_fields=['password'])
 
-@reset_view
-def new_password_sent(request):
-    requesting_user_pk = request.session.get('password_sent_to')
-    if not requesting_user_pk:
-        raise Http404()
+            message = _("%(username)s, your password has been changed.")
+            message = message % {'username': requesting_user.username}
+            messages.success(request, message)
+            return redirect(settings.LOGIN_URL)
 
-    User = get_user_model()
-    requesting_user = get_object_or_404(User.objects, pk=requesting_user_pk)
 
-    return render(request, 'misago/forgottenpassword/password_sent.html',
-                  {'requesting_user': requesting_user})
+    return render(request, 'misago/forgottenpassword/reset_password_form.html',
+                  {'requesting_user': requesting_user, 'form': form})