Merge pull request #18 from alphagov/verify_flow

Verify flow
This commit is contained in:
minglis
2015-12-09 10:13:06 +00:00
11 changed files with 257 additions and 25 deletions
+1 -1
View File
@@ -34,7 +34,7 @@ def create_app(config_name):
init_csrf(application) init_csrf(application)
login_manager.init_app(application) login_manager.init_app(application)
login_manager.login_view = 'main.sign_in.render_sign_in' login_manager.login_view = 'main.render_sign_in'
from app.main import main as main_blueprint from app.main import main as main_blueprint
application.register_blueprint(main_blueprint) application.register_blueprint(main_blueprint)
+1 -1
View File
@@ -3,4 +3,4 @@ from flask import Blueprint
main = Blueprint('main', __name__) main = Blueprint('main', __name__)
from app.main.views import index, sign_in, register from app.main.views import index, sign_in, register, verify
+7
View File
@@ -30,3 +30,10 @@ def increment_failed_login_count(id):
user = User.query.filter_by(id=id).first() user = User.query.filter_by(id=id).first()
user.failed_login_count += 1 user.failed_login_count += 1
db.session.commit() db.session.commit()
def activate_user(id):
user = get_user_by_id(id)
user.state = 'active'
db.session.add(user)
db.session.commit()
+29 -1
View File
@@ -1,7 +1,9 @@
from flask import session
from flask_wtf import Form from flask_wtf import Form
from wtforms import StringField, PasswordField from wtforms import StringField, PasswordField, IntegerField
from wtforms.validators import DataRequired, Email, Length, Regexp from wtforms.validators import DataRequired, Email, Length, Regexp
from app.main.encryption import checkpw
from app.main.validators import Blacklist from app.main.validators import Blacklist
@@ -18,6 +20,7 @@ class LoginForm(Form):
gov_uk_email = "(^[^@^\\s]+@[^@^\\.^\\s]+(\\.[^@^\\.^\\s]*)*.gov.uk)" gov_uk_email = "(^[^@^\\s]+@[^@^\\.^\\s]+(\\.[^@^\\.^\\s]*)*.gov.uk)"
mobile_number = "^\\+44[\\d]{10}$" mobile_number = "^\\+44[\\d]{10}$"
verify_code = "[\\d]{5}$"
class RegisterUserForm(Form): class RegisterUserForm(Form):
@@ -36,3 +39,28 @@ class RegisterUserForm(Form):
validators=[DataRequired(message='Please enter your password'), validators=[DataRequired(message='Please enter your password'),
Length(10, 255, message='Password must be at least 10 characters'), Length(10, 255, message='Password must be at least 10 characters'),
Blacklist(message='That password is blacklisted, too common')]) Blacklist(message='That password is blacklisted, too common')])
class VerifyForm(Form):
sms_code = StringField("Text message confirmation code",
validators=[DataRequired(message='SMS code can not be empty'),
Regexp(regex=verify_code, message='Code must be 5 digits')])
email_code = StringField("Email confirmation code",
validators=[DataRequired(message='Email code can not be empty'),
Regexp(regex=verify_code, message='Code must be 5 digits')])
def validate_email_code(self, a):
if self.email_code.data is not None:
if checkpw(str(self.email_code.data), session['email_code']) is False:
self.email_code.errors.append('Code does not match')
return False
else:
return True
def validate_sms_code(self, a):
if self.sms_code.data is not None:
if checkpw(str(self.sms_code.data), session['sms_code']) is False:
self.sms_code.errors.append('Code does not match')
return False
else:
return True
-5
View File
@@ -19,11 +19,6 @@ def registerfrominvite():
return render_template('register-from-invite.html') return render_template('register-from-invite.html')
@main.route("/verify")
def verify():
return render_template('verify.html')
@main.route("/verify-mobile") @main.route("/verify-mobile")
def verifymobile(): def verifymobile():
return render_template('verify-mobile.html') return render_template('verify-mobile.html')
+1
View File
@@ -36,6 +36,7 @@ def process_register():
session['email_code'] = hashpw(email_code) session['email_code'] = hashpw(email_code)
session['expiry_date'] = str(datetime.now() + timedelta(hours=1)) session['expiry_date'] = str(datetime.now() + timedelta(hours=1))
users_dao.insert_user(user) users_dao.insert_user(user)
session['user_id'] = user.id
except AdminApiClientException as e: except AdminApiClientException as e:
return jsonify(admin_api_client_error=e.value) return jsonify(admin_api_client_error=e.value)
except SQLAlchemyError: except SQLAlchemyError:
+1 -4
View File
@@ -1,13 +1,10 @@
from datetime import datetime
from flask import render_template, redirect, jsonify from flask import render_template, redirect, jsonify
from flask_login import login_user from flask_login import login_user
from app.main import main from app.main import main
from app.main.forms import LoginForm
from app.main.dao import users_dao from app.main.dao import users_dao
from app.models import User
from app.main.encryption import checkpw from app.main.encryption import checkpw
from app.main.forms import LoginForm
@main.route("/sign-in", methods=(['GET'])) @main.route("/sign-in", methods=(['GET']))
+24
View File
@@ -0,0 +1,24 @@
from flask import render_template, redirect, jsonify, session
from flask_login import login_user
from app.main import main
from app.main.dao import users_dao
from app.main.forms import VerifyForm
@main.route('/verify', methods=['GET'])
def render_verify():
return render_template('verify.html', form=VerifyForm())
@main.route('/verify', methods=['POST'])
def process_verify():
form = VerifyForm()
if form.validate_on_submit():
user = users_dao.get_user_by_id(session['user_id'])
users_dao.activate_user(user.id)
login_user(user)
return redirect('/add-service')
else:
print(form.errors)
return jsonify(form.errors), 400
+16 -13
View File
@@ -12,20 +12,23 @@ GOV.UK Notify | Confirm email address and mobile number
<p>We've sent you confirmation codes by email and text message. You need to enter both codes here.</p> <p>We've sent you confirmation codes by email and text message. You need to enter both codes here.</p>
<p> <form autocomplete="off" action="" method="post">
<label class="form-label" for="emailverify">Email confirmation code<br> {{ form.hidden_tag() }}
<input class="form-control-1-4" id="emailverify" type="text"><br> <p>
<span class="font-xsmall"><a href="email-not-received">I haven't received an email</a></span> <label class="form-label">{{ form.email_code.label }}</label>
</p> {{ form.email_code(class="form-control-1-4", autocomplete="off") }}<br>
<p> <span class="font-xsmall"><a href="email-not-received">I haven't received an email</a></span>
<label class="form-label" for="email">Text message confirmation code<br> </p>
<input class="form-control-1-4" id="email" type="text"><br> <p>
<span class="font-xsmall"><a href="text-not-received">I haven't received a text</a></span> <label class="form-label">{{ form.sms_code.label }}</label>
</p> {{ form.sms_code(class="form-control-1-4", autocomplete="off") }} <br>
<span class="font-xsmall"><a href="text-not-received">I haven't received a text</a></span>
</p>
<p> <p>
<a class="button" href="add-service" role="button">Continue</a> <button class="button" href="add-service" role="button">Continue</button>
</p> </p>
</form>
</div> </div>
</div> </div>
+20
View File
@@ -119,3 +119,23 @@ def test_user_is_active_is_false_if_state_is_inactive(notifications_admin, notif
saved_user = users_dao.get_user_by_id(user.id) saved_user = users_dao.get_user_by_id(user.id)
assert saved_user.is_active() is False assert saved_user.is_active() is False
def test_should_update_user_to_active(notifications_admin, notifications_admin_db):
user = User(name='Make user active',
password='somepassword',
email_address='activate@user.gov.uk',
mobile_number='+441234123412',
created_at=datetime.now(),
role_id=1,
state='pending')
users_dao.insert_user(user)
users_dao.activate_user(user.id)
updated_user = users_dao.get_user_by_id(user.id)
assert updated_user.state == 'active'
def test_should_throws_error_when_id_does_not_exist(notifications_admin, notifications_admin_db):
with pytest.raises(AttributeError) as error:
users_dao.activate_user(123)
assert '''object has no attribute 'state''''' in str(error.value)
+157
View File
@@ -0,0 +1,157 @@
from datetime import datetime
from app.main.dao import users_dao
from app.main.encryption import hashpw
from app.models import User
def test_should_return_verify_template(notifications_admin, notifications_admin_db):
response = notifications_admin.test_client().get('/verify')
assert response.status_code == 200
assert 'Activate your account' in response.get_data(as_text=True)
def test_should_redirect_to_add_service_when_code_are_correct(notifications_admin, notifications_admin_db):
with notifications_admin.test_client() as client:
with client.session_transaction() as session:
user = _create_test_user()
session['user_id'] = user.id
session['sms_code'] = hashpw('12345')
session['email_code'] = hashpw('23456')
response = client.post('/verify',
data={'sms_code': '12345',
'email_code': '23456'})
assert response.status_code == 302
assert response.location == 'http://localhost/add-service'
def test_should_activate_user_after_verify(notifications_admin, notifications_admin_db):
with notifications_admin.test_client() as client:
with client.session_transaction() as session:
user = _create_test_user()
session['user_id'] = user.id
session['sms_code'] = hashpw('12345')
session['email_code'] = hashpw('23456')
client.post('/verify',
data={'sms_code': '12345',
'email_code': '23456'})
after_verify = users_dao.get_user_by_id(user.id)
assert after_verify.state == 'active'
def test_should_return_400_when_sms_code_is_wrong(notifications_admin, notifications_admin_db):
with notifications_admin.test_client() as client:
with client.session_transaction() as session:
user = _create_test_user()
session['user_id'] = user.id
session['sms_code'] = hashpw('12345')
session['email_code'] = hashpw('23456')
response = client.post('/verify',
data={'sms_code': '98765',
'email_code': '23456'})
assert response.status_code == 400
assert 'sms_code' in response.get_data(as_text=True)
assert 'Code does not match' in response.get_data(as_text=True)
def test_should_return_400_when_email_code_is_wrong(notifications_admin, notifications_admin_db):
with notifications_admin.test_client() as client:
with client.session_transaction() as session:
user = _create_test_user()
session['user_id'] = user.id
session['sms_code'] = hashpw('12345')
session['email_code'] = hashpw('98456')
response = client.post('/verify',
data={'sms_code': '12345',
'email_code': '23456'})
assert response.status_code == 400
print(response.get_data(as_text=True))
assert 'email_code' in response.get_data(as_text=True)
assert 'Code does not match' in response.get_data(as_text=True)
def test_should_return_400_when_sms_code_is_missing(notifications_admin, notifications_admin_db):
with notifications_admin.test_client() as client:
with client.session_transaction() as session:
user = _create_test_user()
session['user_id'] = user.id
session['sms_code'] = hashpw('12345')
session['email_code'] = hashpw('98456')
response = client.post('/verify',
data={'email_code': '23456'})
assert response.status_code == 400
assert 'SMS code can not be empty' in response.get_data(as_text=True)
def test_should_return_400_when_email_code_is_missing(notifications_admin, notifications_admin_db):
with notifications_admin.test_client() as client:
with client.session_transaction() as session:
user = _create_test_user()
session['user_id'] = user.id
session['sms_code'] = hashpw('23456')
session['email_code'] = hashpw('23456')
response = client.post('/verify',
data={'sms_code': '23456'})
assert response.status_code == 400
assert 'Email code can not be empty' in response.get_data(as_text=True)
def test_should_return_400_when_email_code_has_letter(notifications_admin, notifications_admin_db):
with notifications_admin.test_client() as client:
with client.session_transaction() as session:
user = _create_test_user()
session['user_id'] = user.id
session['sms_code'] = hashpw('23456')
session['email_code'] = hashpw('23456')
response = client.post('/verify',
data={'sms_code': '23456',
'email_code': 'abcde'})
data = response.get_data(as_text=True)
assert response.status_code == 400
assert 'email_code' in data
assert 'Code does not match' in data
assert 'Code must be 5 digits' in data
def test_should_return_400_when_sms_code_is_too_short(notifications_admin, notifications_admin_db):
with notifications_admin.test_client() as client:
with client.session_transaction() as session:
user = _create_test_user()
session['user_id'] = user.id
session['sms_code'] = hashpw('23456')
session['email_code'] = hashpw('23456')
response = client.post('/verify',
data={'sms_code': '2345',
'email_code': '23456'})
assert response.status_code == 400
data = response.get_data(as_text=True)
assert 'sms_code' in data
assert 'Code must be 5 digits' in data
assert 'Code does not match' in data
def test_should_return_302_when_email_code_starts_with_zero(notifications_admin, notifications_admin_db):
with notifications_admin.test_client() as client:
with client.session_transaction() as session:
user = _create_test_user()
session['user_id'] = user.id
session['sms_code'] = hashpw('23456')
session['email_code'] = hashpw('09765')
response = client.post('/verify',
data={'sms_code': '23456',
'email_code': '09765'})
assert response.status_code == 302
assert response.location == 'http://localhost/add-service'
def _create_test_user():
user = User(name='Test User',
password='somepassword',
email_address='test@user.gov.uk',
mobile_number='+441234123412',
created_at=datetime.now(),
role_id=1,
state='pending')
users_dao.insert_user(user)
return user