alter login flow to allow for email auth login

This commit is contained in:
chrisw
2017-11-07 16:11:31 +00:00
parent ef5f5ca43a
commit 1effec78e5
10 changed files with 334 additions and 34 deletions

View File

@@ -43,6 +43,7 @@ class Config(object):
}
EMAIL_EXPIRY_SECONDS = 3600 # 1 hour
INVITATION_EXPIRY_SECONDS = 3600 * 24 * 2 # 2 days - also set on api
EMAIL_2FA_EXPIRY_SECONDS = 1800 # 30 Minutes
HEADER_COLOUR = '#FFBF47' # $yellow
HTTP_PROTOCOL = 'http'
MAX_FAILED_LOGIN_COUNT = 10

View File

@@ -46,3 +46,17 @@ def check_and_resend_verification_code():
return redirect(url_for('main.verify'))
else:
return redirect(url_for('main.two_factor'))
@main.route('/email-not-received', methods=['GET'])
@redirect_to_sign_in
def email_not_received():
return render_template('views/email-not-received.html')
@main.route('/send-new-email-token', methods=['GET'])
@redirect_to_sign_in
def resend_email_link():
user_api_client.send_verify_code(session['user_details']['id'], 'email', None)
session.pop('user_details')
return redirect(url_for('main.two_factor_email_sent', email_resent=True))

View File

@@ -48,11 +48,11 @@ def sign_in():
if user:
session['user_details'] = {"email": user.email_address, "id": user.id}
if user.is_active:
user_api_client.send_verify_code(user.id, 'sms', user.mobile_number)
if request.args.get('next'):
return redirect(url_for('.two_factor', next=request.args.get('next')))
if user.auth_type == "email_auth":
return sign_in_email(user.id, user.email_address)
else:
return redirect(url_for('.two_factor'))
return sign_in_sms(user.id, user.mobile_number)
# Vague error message for login in case of user not known, locked, inactive or password not verified
flash(Markup(
(
@@ -70,6 +70,22 @@ def sign_in():
)
def sign_in_email(user_id, to):
if request.args.get('next'):
user_api_client.send_verify_code(user_id, 'email', None, request.args.get('next'))
else:
user_api_client.send_verify_code(user_id, 'email', None)
return redirect(url_for('.two_factor_email_sent'))
def sign_in_sms(user_id, to):
user_api_client.send_verify_code(user_id, 'sms', to)
if request.args.get('next'):
return redirect(url_for('.two_factor', next=request.args.get('next')))
else:
return redirect(url_for('.two_factor'))
@login_manager.unauthorized_handler
def sign_in_again():
return redirect(

View File

@@ -1,16 +1,64 @@
import json
from flask import (
render_template,
redirect,
session,
url_for,
request
request,
current_app,
flash
)
from flask_login import login_user, current_user
from app.main import main
from app.main.forms import TwoFactorForm
from app import service_api_client, user_api_client
from app.utils import redirect_to_sign_in
from notifications_utils.url_safe_token import check_token
from itsdangerous import SignatureExpired
@main.route('/two-factor-email-sent', methods=['GET'])
def two_factor_email_sent():
title = 'Email resent' if request.args.get('email_resent') else 'Check your email'
return render_template(
'views/two-factor-email.html',
title=title
)
@main.route('/email-auth/<token>', methods=['GET'])
def two_factor_email(token):
if current_user.is_authenticated:
return redirect_when_logged_in(current_user.id)
# checks url is valid, and hasn't timed out
try:
token_data = json.loads(check_token(
token,
current_app.config['SECRET_KEY'],
current_app.config['DANGEROUS_SALT'],
current_app.config['EMAIL_2FA_EXPIRY_SECONDS']
))
except SignatureExpired as exc:
# lets decode again, without the expiry, to get the user id out
orig_data = json.loads(check_token(
token,
current_app.config['SECRET_KEY'],
current_app.config['DANGEROUS_SALT'],
None
))
session['user_details'] = {'id': orig_data['user_id']}
flash("The link in the email we sent you has expired. Weve sent you a new one.")
return redirect(url_for('.resend_email_link'))
user_id = token_data['user_id']
# checks if code was already used
logged_in, msg = user_api_client.check_verify_code(user_id, token_data['secret_code'], "email")
if not logged_in:
flash("Theres something wrong with the code")
return redirect(url_for('.two_factor_email_sent'))
return log_in_user(user_id)
@main.route('/two-factor', methods=['GET', 'POST'])
@@ -24,29 +72,7 @@ def two_factor():
form = TwoFactorForm(_check_code)
if form.validate_on_submit():
try:
user = user_api_client.get_user(user_id)
# the user will have a new current_session_id set by the API - store it in the cookie for future requests
session['current_session_id'] = user.current_session_id
services = service_api_client.get_active_services({'user_id': str(user_id)}).get('data', [])
# Check if coming from new password page
if 'password' in session['user_details']:
user = user_api_client.update_password(user.id, password=session['user_details']['password'])
activated_user = user_api_client.activate_user(user)
login_user(activated_user)
finally:
del session['user_details']
next_url = request.args.get('next')
if next_url and _is_safe_redirect_url(next_url):
return redirect(next_url)
if current_user.platform_admin:
return redirect(url_for('main.platform_admin'))
if len(services) == 1:
return redirect(url_for('main.service_dashboard', service_id=services[0]['id']))
else:
return redirect(url_for('main.choose_service'))
return log_in_user(user_id)
return render_template('views/two-factor.html', form=form)
@@ -58,3 +84,34 @@ def _is_safe_redirect_url(target):
redirect_url = urlparse(urljoin(request.host_url, target))
return redirect_url.scheme in ('http', 'https') and \
host_url.netloc == redirect_url.netloc
def log_in_user(user_id):
try:
user = user_api_client.get_user(user_id)
# the user will have a new current_session_id set by the API - store it in the cookie for future requests
session['current_session_id'] = user.current_session_id
# Check if coming from new password page
if 'password' in session.get('user_details', {}):
user = user_api_client.update_password(user.id, password=session['user_details']['password'])
activated_user = user_api_client.activate_user(user)
login_user(activated_user)
finally:
session.pop("user_details", None)
return redirect_when_logged_in(user_id)
def redirect_when_logged_in(user_id):
next_url = request.args.get('next')
if next_url and _is_safe_redirect_url(next_url):
return redirect(next_url)
if current_user.platform_admin:
return redirect(url_for('main.platform_admin'))
services = service_api_client.get_active_services({'user_id': str(user_id)}).get('data', [])
if len(services) == 1:
return redirect(url_for('main.service_dashboard', service_id=services[0]['id']))
else:
return redirect(url_for('main.choose_service'))

View File

@@ -88,8 +88,10 @@ class UserApiClient(NotifyAdminAPIClient):
if e.status_code == 400 or e.status_code == 404:
return False
def send_verify_code(self, user_id, code_type, to):
def send_verify_code(self, user_id, code_type, to, next_string=None):
data = {'to': to}
if next_string:
data['next'] = next_string
endpoint = '/user/{0}/{1}-code'.format(user_id, code_type)
self.post(endpoint, data=data)

View File

@@ -0,0 +1,24 @@
{% extends "withoutnav_template.html" %}
{% from "components/page-footer.html" import page_footer %}
{% block per_page_title %}
Resend email link
{% endblock %}
{% block maincolumn_content %}
<div class="grid-row">
<div class="column-two-thirds">
<h1 class="heading-large">Resend email link</h1>
<p> Email messages sometimes take a few minutes to arrive. If you do not recieve the email, you can resend it.</p>
<p> If you no longer have access to the email address you registered for this service, speak to your service manager to reset the email.</p>
<p>
<a class="button" href="{{url_for('main.resend_email_link')}}" role="button">Resend email link</a>
</p>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,21 @@
{% extends "withoutnav_template.html" %}
{% from "components/page-footer.html" import page_footer %}
{% block per_page_title %}
Email verification
{% endblock %}
{% block maincolumn_content %}
<div class="grid-row">
<div class="column-two-thirds">
<h1 class="heading-large">{{ title }}</h1>
<p> Weve sent you an email with your login link</p>
{{ page_footer(
secondary_link=url_for('main.email_not_received'),
secondary_link_text='Not received an email?'
) }}
</div>
</div>
{% endblock %}