views: warn user before generating new token (#41792)
This commit is contained in:
parent
7d9e65dc96
commit
4f831fe4d8
|
@ -335,6 +335,9 @@ default_settings = dict(
|
|||
A2_USER_DELETED_KEEP_DATA_DAYS=Setting(
|
||||
default=365,
|
||||
definition='Number of days to keep data on deleted users'),
|
||||
A2_TOKEN_EXISTS_WARNING=Setting(
|
||||
default=True,
|
||||
definition='If an active token exists, warn user before generating a new one.'),
|
||||
)
|
||||
|
||||
app_settings = AppSettings(default_settings)
|
||||
|
|
|
@ -804,8 +804,8 @@ def build_reset_password_url(user, request=None, next_url=None, set_random_passw
|
|||
user.save()
|
||||
lifetime = settings.PASSWORD_RESET_TIMEOUT_DAYS * 3600 * 24
|
||||
# invalidate any token associated with this user
|
||||
Token.objects.filter(kind='pw-reset', content__user=user.pk).delete()
|
||||
token = Token.create('pw-reset', {'user': user.pk}, duration=lifetime)
|
||||
Token.objects.filter(kind='pw-reset', content__user=user.pk, content__email=user.email).delete()
|
||||
token = Token.create('pw-reset', {'user': user.pk, 'email': user.email}, duration=lifetime)
|
||||
reset_url = make_url(
|
||||
'password_reset_confirm',
|
||||
kwargs={'token': token.uuid_b64url},
|
||||
|
|
|
@ -31,7 +31,7 @@ from django import shortcuts
|
|||
from django.core import signing
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.contrib import messages
|
||||
from django.utils import six
|
||||
from django.utils import six, timezone
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.urls import reverse
|
||||
from django.contrib.auth import logout as auth_logout
|
||||
|
@ -659,6 +659,23 @@ class PasswordResetView(FormView):
|
|||
return ctx
|
||||
|
||||
def form_valid(self, form):
|
||||
email = form.cleaned_data['email']
|
||||
|
||||
# if an email has already been sent, warn once before allowing resend
|
||||
token = models.Token.objects.filter(
|
||||
kind='pw-reset', content__email=email, expires__gt=timezone.now()
|
||||
).exists()
|
||||
resend_key = 'pw-reset-allow-resend'
|
||||
if app_settings.A2_TOKEN_EXISTS_WARNING and token and not self.request.session.get(resend_key):
|
||||
self.request.session[resend_key] = True
|
||||
form.add_error(
|
||||
'email',
|
||||
_('An email has already been sent to %s. Click "Validate" again if '
|
||||
'you really want it to be sent again.') % email
|
||||
)
|
||||
return self.form_invalid(form)
|
||||
self.request.session[resend_key] = False
|
||||
|
||||
if is_ratelimited(self.request, key='post:email', group='pw-reset-email',
|
||||
rate=app_settings.A2_EMAILS_ADDRESS_RATELIMIT, increment=True):
|
||||
form.add_error(
|
||||
|
@ -677,7 +694,7 @@ class PasswordResetView(FormView):
|
|||
return self.form_invalid(form)
|
||||
|
||||
form.save()
|
||||
self.request.session['reset_email'] = form.cleaned_data['email']
|
||||
self.request.session['reset_email'] = email
|
||||
return super(PasswordResetView, self).form_valid(form)
|
||||
|
||||
password_reset = PasswordResetView.as_view()
|
||||
|
@ -791,6 +808,23 @@ class BaseRegistrationView(FormView):
|
|||
return super(BaseRegistrationView, self).dispatch(request, *args, **kwargs)
|
||||
|
||||
def form_valid(self, form):
|
||||
email = form.cleaned_data.pop('email')
|
||||
|
||||
# if an email has already been sent, warn once before allowing resend
|
||||
token = models.Token.objects.filter(
|
||||
kind='registration', content__email=email, expires__gt=timezone.now()
|
||||
).exists()
|
||||
resend_key = 'registration-allow-resend'
|
||||
if app_settings.A2_TOKEN_EXISTS_WARNING and token and not self.request.session.get(resend_key):
|
||||
self.request.session[resend_key] = True
|
||||
form.add_error(
|
||||
'email',
|
||||
_('An email has already been sent to %s. Click "Validate" again if '
|
||||
'you really want it to be sent again.') % email
|
||||
)
|
||||
return self.form_invalid(form)
|
||||
self.request.session[resend_key] = False
|
||||
|
||||
if is_ratelimited(self.request, key='post:email', group='registration-email',
|
||||
rate=app_settings.A2_EMAILS_ADDRESS_RATELIMIT, increment=True):
|
||||
form.add_error(
|
||||
|
@ -808,7 +842,6 @@ class BaseRegistrationView(FormView):
|
|||
)
|
||||
return self.form_invalid(form)
|
||||
|
||||
email = form.cleaned_data.pop('email')
|
||||
for field in form.cleaned_data:
|
||||
self.token[field] = form.cleaned_data[field]
|
||||
|
||||
|
|
|
@ -54,3 +54,5 @@ SITE_BASE_URL = 'http://testserver'
|
|||
|
||||
A2_MAX_EMAILS_PER_IP = None
|
||||
A2_MAX_EMAILS_FOR_ADDRESS = None
|
||||
|
||||
A2_TOKEN_EXISTS_WARNING = False
|
||||
|
|
|
@ -187,7 +187,8 @@ def test_fr_postcode(db, app, admin, mailoutbox):
|
|||
qs.delete()
|
||||
|
||||
|
||||
def test_phone_number(db, app, admin, mailoutbox):
|
||||
def test_phone_number(db, app, admin, mailoutbox, settings):
|
||||
settings.A2_EMAILS_ADDRESS_RATELIMIT = None
|
||||
|
||||
def register_john():
|
||||
response = app.get('/accounts/register/')
|
||||
|
|
|
@ -215,3 +215,24 @@ def test_views_email_ratelimit(app, db, simple_user, settings, mailoutbox, freez
|
|||
response.form.set('email', simple_user.email)
|
||||
response = response.form.submit()
|
||||
assert len(mailoutbox) == 12
|
||||
|
||||
|
||||
@pytest.mark.parametrize('view_name', ['registration_register', 'password_reset'])
|
||||
def test_views_email_token_resend(app, simple_user, settings, mailoutbox, view_name):
|
||||
settings.A2_TOKEN_EXISTS_WARNING = True
|
||||
|
||||
response = app.get(reverse(view_name))
|
||||
response.form.set('email', simple_user.email)
|
||||
response = response.form.submit()
|
||||
assert len(mailoutbox) == 1
|
||||
|
||||
# warn user token has already been sent
|
||||
response = app.get(reverse(view_name))
|
||||
response.form.set('email', simple_user.email)
|
||||
response = response.form.submit()
|
||||
assert 'email has already been sent' in response.text
|
||||
assert len(mailoutbox) == 1
|
||||
|
||||
# validating again anyway works
|
||||
response = response.form.submit()
|
||||
assert len(mailoutbox) == 2
|
||||
|
|
Loading…
Reference in New Issue