Completion of forgot-password endpoints.

Start implementation for new-password endpoints.
Created PasswordResetToken model
ToDo: create and save token, send valid url to user,
check validity of token, update user's password, redirect to /two-factor.
This commit is contained in:
Rebecca Law
2016-01-05 17:52:09 +00:00
parent 6696426dbc
commit 2cb896fa81
15 changed files with 187 additions and 33 deletions

View File

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

View File

@@ -0,0 +1,14 @@
from datetime import datetime, timedelta
from app import db
from app.models import PasswordResetToken
def insert(token):
token.expiry_date = datetime.now() + timedelta(hours=1)
db.session.add(token)
db.session.commit()
def get_token(token):
return PasswordResetToken.query.filter_by(token=token).first()

View File

@@ -1,5 +1,7 @@
from datetime import datetime
from sqlalchemy.orm import load_only
from app import db, login_manager
from app.models import User
from app.main.encryption import hashpw
@@ -63,3 +65,7 @@ def update_password(id, password):
user.password_changed_at = datetime.now()
db.session.add(user)
db.session.commit()
def find_all_email_address():
return [x.email_address for x in User.query.options(load_only("email_address")).all()]

View File

@@ -122,7 +122,11 @@ class AddServiceForm(Form):
raise ValidationError('Service name already exists')
class ForgotPassword(Form):
class ForgotPasswordForm(Form):
def __init__(self, email_addresses, *args, **kargs):
self.email_addresses = email_addresses
super(ForgotPasswordForm, self).__init__(*args, **kargs)
email_address = StringField('Email address',
validators=[Length(min=5, max=255),
DataRequired(message='Email cannot be empty'),
@@ -130,4 +134,8 @@ class ForgotPassword(Form):
Regexp(regex=gov_uk_email, message='Please enter a gov.uk email address')
])
def validate_email_address(self, a):
if self.email_address.data not in self.email_addresses:
raise ValidationError('Please enter the email address that you registered with')

View File

@@ -1,4 +1,9 @@
import traceback
import uuid
from random import randint
from flask import url_for
from app import admin_api_client
from app.main.exceptions import AdminApiClientException
from app.main.dao import verify_codes_dao
@@ -34,14 +39,13 @@ def send_email_code(user_id, email):
def send_change_password_email(email):
code = create_verify_code()
link_to_change_password = 'thelink' + code
# TODO needs an expiry date to check?
try:
link_to_change_password = url_for('.new_password', token=str(uuid.uuid4()))
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:
traceback.print_exc()
raise AdminApiClientException('Exception when sending email.')

View File

@@ -1,21 +1,16 @@
from flask import render_template, jsonify
from flask import render_template
from app.main import main
from app.main.forms import ForgotPassword
from app.main.dao import users_dao
from app.main.forms import ForgotPasswordForm
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()
@main.route('/forgot-password', methods=['GET', 'POST'])
def forgot_password():
form = ForgotPasswordForm(users_dao.find_all_email_address())
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'
send_change_password_email(form.email_address.data)
return render_template('views/password-reset-sent.html')
else:
return jsonify(form.errors), 400
return render_template('views/forgot-password.html', form=form)

View File

@@ -0,0 +1,16 @@
from flask import request
from app.main import main
@main.route('/new-password/<token>', methods=['GET', 'POST'])
def new_password():
# Validate token
token = request.args.get('token')
# get password token (better name)
# is it expired
# add NewPasswordForm
# update password
# create password_token table (id, token, user_id, expiry_date
return 'Got here'

View File

@@ -111,6 +111,13 @@ class Service(db.Model):
return filter_null_value_fields(serialized)
class PasswordResetToken(db.Model):
id = db.Column(db.Integer, primary_key=True)
token = db.Column(db.String, unique=True, index=True, nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), unique=False, nullable=False)
expiry_date = db.Column(db.DateTime, nullable=False)
def filter_null_value_fields(obj):
return dict(
filter(lambda x: x[1] is not None, obj.items())

View File

@@ -14,11 +14,7 @@ GOV.UK Notify
<form autocomplete="off" action="" method="post">
{{ form.hidden_tag() }}
<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>
{{ render_field(form.email_address, class='form-control-2-3') }}
<p>
<button class="button" href="sign-in" role="button">Send email</button>
</p>

View File

@@ -18,7 +18,7 @@ Sign in
{{ render_field(form.email_address, class='form-control-2-3') }}
{{ render_field(form.password, class='form-control-2-3') }}
<p>
<span class="font-xsmall"><a href="">Forgotten password?</a></span>
<span class="font-xsmall"><a href="{{url_for('main.forgot_password')}}">Forgotten password?</a></span>
</p>
{{ page_footer("Continue") }}
</form>