Add endpoints for forgot-password.

This commit is contained in:
Rebecca Law
2016-01-04 14:00:39 +00:00
parent 567ac123e3
commit 6696426dbc
10 changed files with 145 additions and 11 deletions

View File

@@ -2,8 +2,7 @@ from flask import Blueprint
main = Blueprint('main', __name__) main = Blueprint('main', __name__)
from app.main.views import ( from app.main.views import (
index, sign_in, sign_out, register, two_factor, verify, sms, add_service, index, sign_in, sign_out, register, two_factor, verify, sms, add_service,
code_not_received, jobs, dashboard, templates, service_settings code_not_received, jobs, dashboard, templates, service_settings, forgot_password
) )

View File

@@ -1,3 +1,5 @@
from datetime import datetime
from app import db, login_manager from app import db, login_manager
from app.models import User from app.models import User
from app.main.encryption import hashpw from app.main.encryption import hashpw
@@ -53,3 +55,11 @@ def update_mobile_number(id, mobile_number):
user.mobile_number = mobile_number user.mobile_number = mobile_number
db.session.add(user) db.session.add(user)
db.session.commit() db.session.commit()
def update_password(id, password):
user = get_user_by_id(id)
user.password = hashpw(password)
user.password_changed_at = datetime.now()
db.session.add(user)
db.session.commit()

View File

@@ -1,10 +1,7 @@
from datetime import datetime
from flask_wtf import Form from flask_wtf import Form
from wtforms import StringField, PasswordField, ValidationError from wtforms import StringField, PasswordField, ValidationError
from wtforms.validators import DataRequired, Email, Length, Regexp from wtforms.validators import DataRequired, Email, Length, Regexp
from app.main.dao import verify_codes_dao
from app.main.encryption import check_hash
from app.main.validators import Blacklist, ValidateUserCodes from app.main.validators import Blacklist, ValidateUserCodes
@@ -123,3 +120,14 @@ class AddServiceForm(Form):
def validate_service_name(self, a): def validate_service_name(self, a):
if self.service_name.data in self.service_names: if self.service_name.data in self.service_names:
raise ValidationError('Service name already exists') raise ValidationError('Service name already exists')
class ForgotPassword(Form):
email_address = StringField('Email address',
validators=[Length(min=5, max=255),
DataRequired(message='Email cannot be empty'),
Email(message='Please enter a valid email address'),
Regexp(regex=gov_uk_email, message='Please enter a gov.uk email address')
])

View File

@@ -31,3 +31,17 @@ def send_email_code(user_id, email):
raise AdminApiClientException('Exception when sending email.') raise AdminApiClientException('Exception when sending email.')
return email_code return email_code
def send_change_password_email(email):
code = create_verify_code()
link_to_change_password = 'thelink' + code
# TODO needs an expiry date to check?
try:
admin_api_client.send_email(email_address=email,
from_str='notify@digital.cabinet-office.gov.uk',
message=link_to_change_password,
subject='Verification code',
token=admin_api_client.auth_token)
except:
raise AdminApiClientException('Exception when sending email.')

View File

@@ -0,0 +1,21 @@
from flask import render_template, jsonify
from app.main import main
from app.main.forms import ForgotPassword
from app.main.views import send_change_password_email
@main.route('/forgot-password', methods=['GET'])
def render_forgot_my_password():
return render_template('views/forgot-password.html', form=ForgotPassword())
@main.route('/forgot-password', methods=['POST'])
def change_password():
form = ForgotPassword()
if form.validate_on_submit():
send_change_password_email(form.email_address)
return 'You have been sent an email with a link to change your password'
else:
return jsonify(form.errors), 400

View File

@@ -39,6 +39,21 @@ def forgotpassword():
return render_template('views/forgot-password.html') return render_template('views/forgot-password.html')
@main.route("/jobs")
def showjobs():
return render_template('views/jobs.html')
@main.route("/jobs/job")
def showjob():
return render_template('views/job.html')
@main.route("/jobs/job/notification")
def shownotification():
return render_template('views/notification.html')
@main.route("/new-password") @main.route("/new-password")
def newpassword(): def newpassword():
return render_template('views/new-password.html') return render_template('views/new-password.html')
@@ -62,3 +77,12 @@ def apikeys():
@main.route("/verification-not-received") @main.route("/verification-not-received")
def verificationnotreceived(): def verificationnotreceived():
return render_template('views/verification-not-received.html') return render_template('views/verification-not-received.html')
@main.route("/manage-templates")
def managetemplates():
return render_template('views/manage-templates.html')
@main.route("/edit-template")
def edittemplate():
return render_template('views/edit-template.html')

View File

@@ -12,15 +12,17 @@ GOV.UK Notify
<p>If you have forgotten your password, we can send you an email to create a new password.</p> <p>If you have forgotten your password, we can send you an email to create a new password.</p>
<p> <form autocomplete="off" action="" method="post">
<label class="form-label" for="email">Email address</label> {{ form.hidden_tag() }}
<input class="form-control" id="email" type="text" value=""> <p>
<label class="form-label">Email address </label>
{{ form.email_address(class="form-control-2-3", autocomplete="off") }} <br>
<span class="font-xsmall">Your email address must end in .gov.uk</span>
</p> </p>
<p> <p>
<a class="button" href="sign-in" role="button">Send email</a> <button class="button" href="sign-in" role="button">Send email</button>
</p> </p>
</form>
</div> </div>
</div> </div>

View File

@@ -0,0 +1,18 @@
{% extends "admin_template.html" %}
{% block page_title %}
GOV.UK Notify |
{% endblock %}
{% block content %}
<div class="grid-row">
<div class="column-two-thirds">
<h1 class="heading-xlarge">GOV.UK Notify</h1>
<p>You have been sent an email containing a url to reset your password.</p>
</div>
</div>
{% endblock %}

View File

@@ -3,6 +3,7 @@ from datetime import datetime
import pytest import pytest
import sqlalchemy import sqlalchemy
from app.main.encryption import check_hash
from app.models import User from app.models import User
from app.main.dao import users_dao from app.main.dao import users_dao
@@ -161,3 +162,24 @@ def test_should_update_email_address(notifications_admin, notifications_admin_db
users_dao.update_email_address(user.id, 'new_email@testit.gov.uk') users_dao.update_email_address(user.id, 'new_email@testit.gov.uk')
updated = users_dao.get_user_by_id(user.id) updated = users_dao.get_user_by_id(user.id)
assert updated.email_address == 'new_email@testit.gov.uk' assert updated.email_address == 'new_email@testit.gov.uk'
def test_should_update_password(notifications_admin, notifications_admin_db, notify_db_session):
user = User(name='Update Email',
password='somepassword',
email_address='test@it.gov.uk',
mobile_number='+441234123412',
created_at=datetime.now(),
role_id=1,
state='active')
start = datetime.now()
users_dao.insert_user(user)
saved = users_dao.get_user_by_id(user.id)
assert check_hash('somepassword', saved.password)
assert saved.password_changed_at is None
users_dao.update_password(saved.id, 'newpassword')
updated = users_dao.get_user_by_id(user.id)
assert check_hash('newpassword', updated.password)
assert updated.password_changed_at < datetime.now()
assert updated.password_changed_at > start

View File

@@ -0,0 +1,16 @@
from flask import current_app
def test_should_render_forgot_password(notifications_admin, notifications_admin_db, notify_db_session):
response = notifications_admin.test_client().get('/forgot-password')
assert response.status_code == 200
assert 'If you have forgotten your password, we can send you an email to create a new password.' \
in response.get_data(as_text=True)
def test_should_return_400_when_email_is_invalid(notifications_admin, notifications_admin_db, notify_db_session):
response = notifications_admin.test_client().post('/forgot-password',
data={'email_address': 'not_a_valid_email'})
x = current_app._get_current_object()
assert response.status_code == 400
assert 'Please enter a valid email address' in response.get_data(as_text=True)