mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-02-05 10:53:28 -05:00
Merge pull request #293 from alphagov/single-verify-code
Changed registration flow to first send email verification link that
This commit is contained in:
@@ -43,20 +43,11 @@ def increment_failed_login_count(id):
|
||||
|
||||
|
||||
def activate_user(user):
|
||||
user.state = 'active'
|
||||
return user_api_client.update_user(user)
|
||||
return user_api_client.activate_user(user)
|
||||
|
||||
|
||||
def is_email_unique(email_address):
|
||||
try:
|
||||
if user_api_client.get_user_by_email(email_address):
|
||||
return False
|
||||
return True
|
||||
except HTTPError as ex:
|
||||
if ex.status_code == 404:
|
||||
return True
|
||||
else:
|
||||
raise ex
|
||||
return user_api_client.is_email_unique(email_address)
|
||||
|
||||
|
||||
def send_verify_code(user_id, code_type, to):
|
||||
|
||||
@@ -150,49 +150,6 @@ class TwoFactorForm(Form):
|
||||
raise ValidationError(reason)
|
||||
|
||||
|
||||
class VerifySmsForm(Form):
|
||||
def __init__(self, validate_code_func, *args, **kwargs):
|
||||
'''
|
||||
Keyword arguments:
|
||||
validate_code_func -- Validates the code with the API.
|
||||
'''
|
||||
self.validate_code_func = validate_code_func
|
||||
super(VerifySmsForm, self).__init__(*args, **kwargs)
|
||||
|
||||
sms_code = sms_code()
|
||||
|
||||
def validate_sms_code(self, field):
|
||||
is_valid, reason = self.validate_code_func(field.data, 'sms')
|
||||
if not is_valid:
|
||||
raise ValidationError(reason)
|
||||
|
||||
|
||||
class VerifyForm(Form):
|
||||
def __init__(self, validate_code_func, *args, **kwargs):
|
||||
'''
|
||||
Keyword arguments:
|
||||
validate_code_func -- Validates the code with the API.
|
||||
'''
|
||||
self.validate_code_func = validate_code_func
|
||||
super(VerifyForm, self).__init__(*args, **kwargs)
|
||||
|
||||
sms_code = sms_code()
|
||||
email_code = email_code()
|
||||
|
||||
def _validate_code(self, cde, code_type):
|
||||
is_valid, reason = self.validate_code_func(cde, code_type)
|
||||
if not is_valid:
|
||||
raise ValidationError(reason)
|
||||
|
||||
def validate_email_code(self, field):
|
||||
if self.sms_code.data:
|
||||
self._validate_code(field.data, 'email')
|
||||
|
||||
def validate_sms_code(self, field):
|
||||
if self.email_code.data:
|
||||
self._validate_code(field.data, 'sms')
|
||||
|
||||
|
||||
class EmailNotReceivedForm(Form):
|
||||
email_address = email_address()
|
||||
|
||||
|
||||
@@ -8,25 +8,25 @@ from flask import (
|
||||
from flask_login import login_required
|
||||
|
||||
from app.main import main
|
||||
from app.main.dao import services_dao, users_dao
|
||||
from app.main.dao import services_dao
|
||||
from app.main.forms import AddServiceForm
|
||||
from app.notify_client.models import InvitedUser
|
||||
|
||||
from app import (
|
||||
invite_api_client,
|
||||
user_api_client,
|
||||
notifications_api_client)
|
||||
notifications_api_client
|
||||
)
|
||||
|
||||
|
||||
@main.route("/add-service", methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def add_service():
|
||||
|
||||
invited_user = session.get('invited_user')
|
||||
if invited_user:
|
||||
invitation = InvitedUser(**invited_user)
|
||||
# if invited user add to service and redirect to dashboard
|
||||
user = users_dao.get_user_by_id(session['user_id'])
|
||||
user = user_api_client.get_user(session['user_id'])
|
||||
service_id = invited_user['service']
|
||||
user_api_client.add_user_to_service(service_id, user.id, invitation.permissions)
|
||||
invite_api_client.accept_invite(service_id, invitation.id)
|
||||
|
||||
@@ -1,35 +1,32 @@
|
||||
from flask import (
|
||||
render_template, redirect, session, url_for)
|
||||
|
||||
from flask_login import current_user
|
||||
render_template,
|
||||
redirect,
|
||||
session,
|
||||
url_for
|
||||
)
|
||||
|
||||
from app import user_api_client
|
||||
from app.main import main
|
||||
from app.main.dao import users_dao
|
||||
from app.main.forms import EmailNotReceivedForm, TextNotReceivedForm
|
||||
from app.main.forms import TextNotReceivedForm
|
||||
|
||||
|
||||
@main.route('/email-not-received', methods=['GET', 'POST'])
|
||||
def check_and_resend_email_code():
|
||||
@main.route('/resend-email-verification')
|
||||
def resend_email_verification():
|
||||
# TODO there needs to be a way to regenerate a session id
|
||||
user = users_dao.get_user_by_email(session['user_details']['email'])
|
||||
form = EmailNotReceivedForm(email_address=user.email_address)
|
||||
if form.validate_on_submit():
|
||||
users_dao.send_verify_code(user.id, 'email', to=form.email_address.data)
|
||||
user.email_address = form.email_address.data
|
||||
users_dao.update_user(user)
|
||||
return redirect(url_for('.verify'))
|
||||
return render_template('views/email-not-received.html', form=form)
|
||||
user = user_api_client.get_user_by_email(session['user_details']['email'])
|
||||
user_api_client.send_verify_email(user.id, user.email_address)
|
||||
return render_template('views/resend-email-verification.html', email=user.email_address)
|
||||
|
||||
|
||||
@main.route('/text-not-received', methods=['GET', 'POST'])
|
||||
def check_and_resend_text_code():
|
||||
# TODO there needs to be a way to regenerate a session id
|
||||
user = users_dao.get_user_by_email(session['user_details']['email'])
|
||||
user = user_api_client.get_user_by_email(session['user_details']['email'])
|
||||
form = TextNotReceivedForm(mobile_number=user.mobile_number)
|
||||
if form.validate_on_submit():
|
||||
users_dao.send_verify_code(user.id, 'sms', to=form.mobile_number.data)
|
||||
user_api_client.send_verify_code(user.id, 'sms', to=form.mobile_number.data)
|
||||
user.mobile_number = form.mobile_number.data
|
||||
users_dao.update_user(user)
|
||||
user_api_client.update_user(user)
|
||||
return redirect(url_for('.verify'))
|
||||
return render_template('views/text-not-received.html', form=form)
|
||||
|
||||
@@ -42,6 +39,6 @@ def verification_code_not_received():
|
||||
@main.route('/send-new-code', methods=['GET'])
|
||||
def check_and_resend_verification_code():
|
||||
# TODO there needs to be a way to generate a new session id
|
||||
user = users_dao.get_user_by_email(session['user_details']['email'])
|
||||
users_dao.send_verify_code(user.id, 'sms', user.mobile_number)
|
||||
user = user_api_client.get_user_by_email(session['user_details']['email'])
|
||||
user_api_client.send_verify_code(user.id, 'sms', user.mobile_number)
|
||||
return redirect(url_for('main.two_factor'))
|
||||
|
||||
@@ -3,11 +3,15 @@ from flask import (
|
||||
url_for,
|
||||
session,
|
||||
flash,
|
||||
render_template)
|
||||
render_template
|
||||
)
|
||||
|
||||
|
||||
from notifications_python_client.errors import HTTPError
|
||||
|
||||
from app.main import main
|
||||
from app.main.dao.services_dao import get_service_by_id_or_404
|
||||
|
||||
from app import (
|
||||
invite_api_client,
|
||||
user_api_client
|
||||
@@ -32,7 +36,11 @@ def accept_invite(token):
|
||||
|
||||
session['invited_user'] = invited_user.serialize()
|
||||
|
||||
existing_user = user_api_client.get_user_by_email(invited_user.email_address)
|
||||
try:
|
||||
existing_user = user_api_client.get_user_by_email(invited_user.email_address)
|
||||
except HTTPError as ex:
|
||||
if ex.status_code == 404:
|
||||
existing_user = False
|
||||
|
||||
service_users = user_api_client.get_users_for_service(invited_user.service)
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@ from flask import (
|
||||
request,
|
||||
render_template,
|
||||
redirect,
|
||||
abort,
|
||||
url_for,
|
||||
flash)
|
||||
flash
|
||||
)
|
||||
|
||||
from flask_login import (
|
||||
login_required,
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import (
|
||||
datetime,
|
||||
timedelta
|
||||
)
|
||||
|
||||
from flask import (
|
||||
render_template,
|
||||
@@ -12,7 +15,7 @@ from flask import (
|
||||
from flask.ext.login import current_user
|
||||
|
||||
from app.main import main
|
||||
from app.main.dao import users_dao
|
||||
|
||||
from app.main.forms import (
|
||||
RegisterUserForm,
|
||||
RegisterUserFromInviteForm
|
||||
@@ -28,9 +31,9 @@ def register():
|
||||
|
||||
form = RegisterUserForm()
|
||||
if form.validate_on_submit():
|
||||
registered = _do_registration(form)
|
||||
registered = _do_registration(form, send_sms=False)
|
||||
if registered:
|
||||
return redirect(url_for('main.verify'))
|
||||
return redirect(url_for('main.registration_continue'))
|
||||
else:
|
||||
flash('There was an error registering your account')
|
||||
return render_template('views/register.html', form=form), 400
|
||||
@@ -40,7 +43,6 @@ def register():
|
||||
|
||||
@main.route('/register-from-invite', methods=['GET', 'POST'])
|
||||
def register_from_invite():
|
||||
|
||||
form = RegisterUserFromInviteForm()
|
||||
invited_user = session.get('invited_user')
|
||||
if not invited_user:
|
||||
@@ -61,8 +63,8 @@ def register_from_invite():
|
||||
return render_template('views/register-from-invite.html', email_address=invited_user['email_address'], form=form)
|
||||
|
||||
|
||||
def _do_registration(form, service=None, send_email=True):
|
||||
if users_dao.is_email_unique(form.email_address.data):
|
||||
def _do_registration(form, service=None, send_sms=True, send_email=True):
|
||||
if user_api_client.is_email_unique(form.email_address.data):
|
||||
user = user_api_client.register_user(form.name.data,
|
||||
form.email_address.data,
|
||||
form.mobile_number.data,
|
||||
@@ -73,11 +75,19 @@ def _do_registration(form, service=None, send_email=True):
|
||||
# How do we report to the user there is a problem with
|
||||
# sending codes apart from service unavailable?
|
||||
# at the moment i believe http 500 is fine.
|
||||
users_dao.send_verify_code(user.id, 'sms', user.mobile_number)
|
||||
|
||||
if send_email:
|
||||
users_dao.send_verify_code(user.id, 'email', user.email_address)
|
||||
user_api_client.send_verify_email(user.id, user.email_address)
|
||||
|
||||
if send_sms:
|
||||
user_api_client.send_verify_code(user.id, 'sms', user.mobile_number)
|
||||
session['expiry_date'] = str(datetime.now() + timedelta(hours=1))
|
||||
session['user_details'] = {"email": user.email_address, "id": user.id}
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
@main.route('/registration-continue')
|
||||
def registration_continue():
|
||||
return render_template('views/registration-continue.html')
|
||||
|
||||
@@ -3,15 +3,22 @@ from flask import (
|
||||
redirect,
|
||||
url_for,
|
||||
session,
|
||||
abort,
|
||||
flash,
|
||||
request
|
||||
)
|
||||
|
||||
from flask.ext.login import (current_user, login_fresh, confirm_login)
|
||||
from flask.ext.login import (
|
||||
current_user,
|
||||
login_fresh,
|
||||
confirm_login
|
||||
)
|
||||
|
||||
from app.main import main
|
||||
from app.main.dao import (users_dao, services_dao)
|
||||
from app.main.dao import services_dao
|
||||
|
||||
from app import user_api_client
|
||||
|
||||
|
||||
from app.main.forms import LoginForm
|
||||
|
||||
|
||||
@@ -22,7 +29,7 @@ def sign_in():
|
||||
|
||||
form = LoginForm()
|
||||
if form.validate_on_submit():
|
||||
user = users_dao.get_user_by_email(form.email_address.data)
|
||||
user = user_api_client.get_user_by_email(form.email_address.data)
|
||||
user = _get_and_verify_user(user, form.password.data)
|
||||
if user:
|
||||
# Remember me login
|
||||
@@ -41,7 +48,7 @@ def sign_in():
|
||||
if user.state == 'pending':
|
||||
return redirect(url_for('.verify'))
|
||||
elif user.is_active():
|
||||
users_dao.send_verify_code(user.id, 'sms', user.mobile_number)
|
||||
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')))
|
||||
else:
|
||||
@@ -62,7 +69,7 @@ def _get_and_verify_user(user, password):
|
||||
return None
|
||||
elif user.is_locked():
|
||||
return None
|
||||
elif not users_dao.verify_password(user.id, password):
|
||||
elif not user_api_client.verify_password(user.id, password):
|
||||
return None
|
||||
else:
|
||||
return user
|
||||
|
||||
@@ -1,16 +1,35 @@
|
||||
from flask import (
|
||||
request, render_template, redirect, url_for, session)
|
||||
request,
|
||||
render_template,
|
||||
redirect,
|
||||
url_for,
|
||||
session
|
||||
)
|
||||
|
||||
from flask.ext.login import current_user
|
||||
from flask_login import login_required
|
||||
from app.main import main
|
||||
|
||||
from app.main.dao.users_dao import (
|
||||
verify_password, update_user, check_verify_code, is_email_unique,
|
||||
send_verify_code)
|
||||
from app.main.forms import (
|
||||
ChangePasswordForm, ChangeNameForm, ChangeEmailForm, ConfirmEmailForm,
|
||||
ChangeMobileNumberForm, ConfirmMobileNumberForm, ConfirmPasswordForm
|
||||
verify_password,
|
||||
update_user,
|
||||
check_verify_code,
|
||||
is_email_unique,
|
||||
send_verify_code
|
||||
)
|
||||
|
||||
from app.main.forms import (
|
||||
ChangePasswordForm,
|
||||
ChangeNameForm,
|
||||
ChangeEmailForm,
|
||||
ConfirmEmailForm,
|
||||
ChangeMobileNumberForm,
|
||||
ConfirmMobileNumberForm,
|
||||
ConfirmPasswordForm
|
||||
)
|
||||
|
||||
from app import user_api_client
|
||||
|
||||
NEW_EMAIL = 'new-email'
|
||||
NEW_MOBILE = 'new-mob'
|
||||
NEW_EMAIL_PASSWORD_CONFIRMED = 'new-email-password-confirmed'
|
||||
@@ -63,7 +82,6 @@ def user_profile_email():
|
||||
@main.route("/user-profile/email/authenticate", methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def user_profile_email_authenticate():
|
||||
|
||||
# Validate password for form
|
||||
def _check_password(pwd):
|
||||
return verify_password(current_user.id, pwd)
|
||||
|
||||
@@ -1,42 +1,72 @@
|
||||
import json
|
||||
|
||||
from flask import (
|
||||
render_template,
|
||||
redirect,
|
||||
session,
|
||||
url_for
|
||||
url_for,
|
||||
current_app,
|
||||
flash
|
||||
)
|
||||
|
||||
from itsdangerous import SignatureExpired
|
||||
|
||||
from flask_login import login_user
|
||||
|
||||
from notifications_python_client.errors import HTTPError
|
||||
|
||||
from app.main import main
|
||||
from app.main.dao import users_dao
|
||||
from app.main.forms import (
|
||||
VerifyForm,
|
||||
VerifySmsForm
|
||||
)
|
||||
from app.main.forms import TwoFactorForm
|
||||
|
||||
from app import user_api_client
|
||||
|
||||
|
||||
@main.route('/verify', methods=['GET', 'POST'])
|
||||
def verify():
|
||||
|
||||
# TODO there needs to be a way to regenerate a session id
|
||||
# or handle gracefully.
|
||||
user_id = session['user_details']['id']
|
||||
|
||||
def _check_code(code, code_type):
|
||||
return users_dao.check_verify_code(user_id, code, code_type)
|
||||
def _check_code(code):
|
||||
return user_api_client.check_verify_code(user_id, code, 'sms')
|
||||
|
||||
if session.get('invited_user'):
|
||||
form = VerifySmsForm(_check_code)
|
||||
else:
|
||||
form = VerifyForm(_check_code)
|
||||
form = TwoFactorForm(_check_code)
|
||||
|
||||
if form.validate_on_submit():
|
||||
try:
|
||||
user = users_dao.get_user_by_id(user_id)
|
||||
activated_user = users_dao.activate_user(user)
|
||||
user = user_api_client.get_user(user_id)
|
||||
activated_user = user_api_client.activate_user(user)
|
||||
login_user(activated_user)
|
||||
return redirect(url_for('main.add_service', first='first'))
|
||||
finally:
|
||||
session.pop('user_details', None)
|
||||
|
||||
return render_template('views/verify.html', form=form)
|
||||
return render_template('views/two-factor.html', form=form)
|
||||
|
||||
|
||||
@main.route('/verify-email/<token>')
|
||||
def verify_email(token):
|
||||
from utils.url_safe_token import check_token
|
||||
try:
|
||||
token_data = check_token(token,
|
||||
current_app.config['SECRET_KEY'],
|
||||
current_app.config['DANGEROUS_SALT'],
|
||||
current_app.config['EMAIL_EXPIRY_SECONDS'])
|
||||
|
||||
token_data = json.loads(token_data)
|
||||
verified = user_api_client.check_verify_code(token_data['user_id'], token_data['secret_code'], 'email')
|
||||
|
||||
if verified[0]:
|
||||
user = user_api_client.get_user(token_data['user_id'])
|
||||
user_api_client.send_verify_code(user.id, 'sms', user.mobile_number)
|
||||
session['user_details'] = {"email": user.email_address, "id": user.id}
|
||||
return redirect('verify')
|
||||
else:
|
||||
message = "There was a problem verifying your account. Error message: '{}'".format(verified[1])
|
||||
flash(message)
|
||||
# TODO could this ask for a resend instead?
|
||||
return redirect(url_for('main.index'))
|
||||
|
||||
except SignatureExpired:
|
||||
flash('The link in the email we sent you has expired')
|
||||
return redirect(url_for('main.resend_email_verification'))
|
||||
|
||||
Reference in New Issue
Block a user