0004-views-warn-user-before-generating-new-token-41792.patch
src/authentic2/app_settings.py | ||
---|---|---|
320 | 320 |
A2_EMAILS_ADDRESS_RATELIMIT=Setting( |
321 | 321 |
default='3/d', |
322 | 322 |
definition='Maximum rate of emails sent to the same email address.'), |
323 |
A2_TOKEN_EXISTS_WARNING=Setting( |
|
324 |
default=True, |
|
325 |
definition='If an active token exists, warn user before generating a new one.') |
|
323 | 326 |
) |
324 | 327 | |
325 | 328 |
app_settings = AppSettings(default_settings) |
src/authentic2/utils/__init__.py | ||
---|---|---|
801 | 801 |
user.save() |
802 | 802 |
lifetime = settings.PASSWORD_RESET_TIMEOUT_DAYS * 3600 * 24 |
803 | 803 |
# invalidate any token associated with this user |
804 |
Token.objects.filter(kind='pw-reset', content__user=user.pk).delete() |
|
805 |
token = Token.create('pw-reset', {'user': user.pk}, duration=lifetime) |
|
804 |
Token.objects.filter(kind='pw-reset', content__user=user.pk, content__email=user.email).delete()
|
|
805 |
token = Token.create('pw-reset', {'user': user.pk, 'email': user.email}, duration=lifetime)
|
|
806 | 806 |
reset_url = make_url( |
807 | 807 |
'password_reset_confirm', |
808 | 808 |
kwargs={'token': token.uuid_b64url}, |
src/authentic2/views.py | ||
---|---|---|
33 | 33 |
from django.core import signing |
34 | 34 |
from django.core.exceptions import ValidationError |
35 | 35 |
from django.contrib import messages |
36 |
from django.utils import six |
|
36 |
from django.utils import six, timezone
|
|
37 | 37 |
from django.utils.translation import ugettext as _ |
38 | 38 |
from django.urls import reverse |
39 | 39 |
from django.contrib.auth import logout as auth_logout |
... | ... | |
659 | 659 |
return ctx |
660 | 660 | |
661 | 661 |
def form_valid(self, form): |
662 |
email = form.cleaned_data['email'] |
|
663 | ||
664 |
# if an email has already been sent, warn once before allowing resend |
|
665 |
token = models.Token.objects.filter( |
|
666 |
kind='pw-reset', content__email=email, expires__gt=timezone.now() |
|
667 |
).exists() |
|
668 |
resend_key = 'pw-reset-allow-resend' |
|
669 |
if app_settings.A2_TOKEN_EXISTS_WARNING and token and not self.request.session.get(resend_key): |
|
670 |
self.request.session[resend_key] = True |
|
671 |
form.add_error( |
|
672 |
'email', |
|
673 |
_('An email has already been sent to %s. Click "Validate" again if ' |
|
674 |
'you really want it to be sent again.') % email |
|
675 |
) |
|
676 |
return self.form_invalid(form) |
|
677 |
self.request.session[resend_key] = False |
|
678 | ||
662 | 679 |
if is_ratelimited(self.request, key='post:email', group='pw-reset-email', |
663 | 680 |
rate=app_settings.A2_EMAILS_ADDRESS_RATELIMIT, increment=True): |
664 | 681 |
form.add_error( |
... | ... | |
677 | 694 |
return self.form_invalid(form) |
678 | 695 | |
679 | 696 |
form.save() |
680 |
self.request.session['reset_email'] = form.cleaned_data['email']
|
|
697 |
self.request.session['reset_email'] = email
|
|
681 | 698 |
return super(PasswordResetView, self).form_valid(form) |
682 | 699 | |
683 | 700 |
password_reset = PasswordResetView.as_view() |
... | ... | |
791 | 808 |
return super(BaseRegistrationView, self).dispatch(request, *args, **kwargs) |
792 | 809 | |
793 | 810 |
def form_valid(self, form): |
811 |
email = form.cleaned_data.pop('email') |
|
812 | ||
813 |
# if an email has already been sent, warn once before allowing resend |
|
814 |
token = models.Token.objects.filter( |
|
815 |
kind='registration', content__email=email, expires__gt=timezone.now() |
|
816 |
).exists() |
|
817 |
resend_key = 'registration-allow-resend' |
|
818 |
if app_settings.A2_TOKEN_EXISTS_WARNING and token and not self.request.session.get(resend_key): |
|
819 |
self.request.session[resend_key] = True |
|
820 |
form.add_error( |
|
821 |
'email', |
|
822 |
_('An email has already been sent to %s. Click "Validate" again if ' |
|
823 |
'you really want it to be sent again.') % email |
|
824 |
) |
|
825 |
return self.form_invalid(form) |
|
826 |
self.request.session[resend_key] = False |
|
827 | ||
794 | 828 |
if is_ratelimited(self.request, key='post:email', group='registration-email', |
795 | 829 |
rate=app_settings.A2_EMAILS_ADDRESS_RATELIMIT, increment=True): |
796 | 830 |
form.add_error( |
... | ... | |
808 | 842 |
) |
809 | 843 |
return self.form_invalid(form) |
810 | 844 | |
811 |
email = form.cleaned_data.pop('email') |
|
812 | 845 |
for field in form.cleaned_data: |
813 | 846 |
self.token[field] = form.cleaned_data[field] |
814 | 847 |
tests/settings.py | ||
---|---|---|
48 | 48 |
TEMPLATES[0]['DIRS'].append('tests/templates') |
49 | 49 | |
50 | 50 |
SITE_BASE_URL = 'http://localhost' |
51 | ||
52 |
A2_TOKEN_EXISTS_WARNING = False |
tests/test_attribute_kinds.py | ||
---|---|---|
187 | 187 |
qs.delete() |
188 | 188 | |
189 | 189 | |
190 |
def test_phone_number(db, app, admin, mailoutbox): |
|
190 |
def test_phone_number(db, app, admin, mailoutbox, settings): |
|
191 |
settings.A2_EMAILS_ADDRESS_RATELIMIT = None |
|
191 | 192 | |
192 | 193 |
def register_john(): |
193 | 194 |
response = app.get('/accounts/register/') |
tests/test_views.py | ||
---|---|---|
203 | 203 |
response.form.set('email', simple_user.email) |
204 | 204 |
response = response.form.submit() |
205 | 205 |
assert len(mailoutbox) == 12 |
206 | ||
207 | ||
208 |
@pytest.mark.parametrize('view_name', ['registration_register', 'password_reset']) |
|
209 |
def test_views_email_token_resend(app, simple_user, settings, mailoutbox, view_name): |
|
210 |
settings.A2_TOKEN_EXISTS_WARNING = True |
|
211 | ||
212 |
response = app.get(reverse(view_name)) |
|
213 |
response.form.set('email', simple_user.email) |
|
214 |
response = response.form.submit() |
|
215 |
assert len(mailoutbox) == 1 |
|
216 | ||
217 |
# warn user token has already been sent |
|
218 |
response = app.get(reverse(view_name)) |
|
219 |
response.form.set('email', simple_user.email) |
|
220 |
response = response.form.submit() |
|
221 |
assert 'email has already been sent' in response.text |
|
222 |
assert len(mailoutbox) == 1 |
|
223 | ||
224 |
# validating again anyway works |
|
225 |
response = response.form.submit() |
|
226 |
assert len(mailoutbox) == 2 |
|
206 |
- |