Files
notifications-admin/app/main/views/new_password.py
Chris Hill-Scott cb59413581 Update email_access_validated_at on link click
When someone uses a fresh password reset link they have proved that they
have access to their inbox.

At the moment, when revalidating a user’s email address we wait until
after they’ve put in the 2FA code before updating the timestamp which
records when they last validated their email address[1].

We can’t think of a good reason that we need the extra assurance of a
valid 2FA code to assert that the user has access to their email –
they’ve done that just by clicking the link. When the user clicks the
link we already update their failed login count before they 2fa. Think
it makes sense to handle `email_access_validated_at` then too.

As a bonus, the functional tests never go as far as getting a 2FA code
after a password reset[2], so the functional test user never gets its
timestamp updated. This causes the functional tests start failing after
90 days. By moving the update to this point we ensure that the
functional tests will keep passing indefinitely.

1. This code in the API (91542ad33e/app/dao/users_dao.py (L131))
   which is called by this code in the admin app (9ba37249a4/app/utils/login.py (L26))
2. 5837eb01dc/tests/functional/preview_and_dev/test_email_auth.py (L43-L46)
2021-08-19 11:14:47 +01:00

58 lines
2.0 KiB
Python

import json
from flask import (
current_app,
flash,
redirect,
render_template,
request,
session,
url_for,
)
from itsdangerous import SignatureExpired
from notifications_utils.url_safe_token import check_token
from app.main import main
from app.main.forms import NewPasswordForm
from app.models.user import User
from app.utils.login import log_in_user
@main.route('/new-password/<path:token>', methods=['GET', 'POST'])
def new_password(token):
try:
token_data = check_token(token, current_app.config['SECRET_KEY'], current_app.config['DANGEROUS_SALT'],
current_app.config['EMAIL_EXPIRY_SECONDS'])
except SignatureExpired:
flash('The link in the email we sent you has expired. Enter your email address to resend.')
return redirect(url_for('.forgot_password'))
email_address = json.loads(token_data)['email']
user = User.from_email_address(email_address)
if user.password_changed_more_recently_than(json.loads(token_data)['created_at']):
flash('The link in the email has already been used')
return redirect(url_for('main.index'))
if request.method == 'GET':
user.update_email_access_validated_at()
form = NewPasswordForm()
if form.validate_on_submit():
user.reset_failed_login_count()
session['user_details'] = {
'id': user.id,
'email': user.email_address,
'password': form.new_password.data}
if user.email_auth:
# they've just clicked an email link, so have done an email auth journey anyway. Just log them in.
return log_in_user(user.id)
elif user.webauthn_auth:
return redirect(url_for('main.two_factor_webauthn', next=request.args.get('next')))
else:
# send user a 2fa sms code
user.send_verify_code()
return redirect(url_for('main.two_factor_sms', next=request.args.get('next')))
else:
return render_template('views/new-password.html', token=token, form=form, user=user)