mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-09-09 17:19:48 -04:00
resolve merge conflicts
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
[](https://api.travis-ci.org/alphagov/notifications-admin.svg?branch=master)
|
[](https://travis-ci.org/alphagov/notifications-admin)
|
||||||
|
[](https://requires.io/github/alphagov/notifications-admin/requirements/?branch=master)
|
||||||
|
|
||||||
|
|
||||||
# notifications-admin
|
# notifications-admin
|
||||||
|
|||||||
@@ -1,14 +1,22 @@
|
|||||||
|
%banner,
|
||||||
.banner {
|
.banner {
|
||||||
|
|
||||||
@include core-19;
|
@include core-19;
|
||||||
background: $turquoise;
|
background: $turquoise;
|
||||||
color: $white;
|
color: $white;
|
||||||
display: block;
|
display: block;
|
||||||
padding: $gutter-half $gutter;
|
padding: $gutter-half;
|
||||||
margin: 0 0 $gutter 0;
|
margin: 0 0 $gutter 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.banner-with-tick {
|
||||||
|
|
||||||
|
@extend %banner;
|
||||||
|
padding: $gutter-half $gutter;
|
||||||
|
|
||||||
&:before {
|
&:before {
|
||||||
@include core-24;
|
@include core-24;
|
||||||
content: '✔';
|
content: '✔';
|
||||||
|
|||||||
@@ -45,27 +45,4 @@
|
|||||||
margin: -$gutter-half 0 $gutter 0;
|
margin: -$gutter-half 0 $gutter 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-history {
|
|
||||||
|
|
||||||
background: $turquoise;
|
|
||||||
color: $white;
|
|
||||||
padding: $gutter-half;
|
|
||||||
@include bold-19;
|
|
||||||
margin: 0 0 $gutter 0;
|
|
||||||
|
|
||||||
&-heading {
|
|
||||||
|
|
||||||
@include bold-19;
|
|
||||||
margin: 0;
|
|
||||||
|
|
||||||
&-time {
|
|
||||||
@include inline-block;
|
|
||||||
margin-left: 10px;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
@import '../govuk_elements/public/sass/elements/forms';
|
@import '../govuk_elements/public/sass/elements/forms';
|
||||||
@import '../govuk_elements/public/sass/elements/forms/form-validation';
|
@import '../govuk_elements/public/sass/elements/forms/form-validation';
|
||||||
@import '../govuk_elements/public/sass/elements/forms/form-block-labels';
|
@import '../govuk_elements/public/sass/elements/forms/form-block-labels';
|
||||||
|
@import '../govuk_elements/public/sass/elements/forms/form-validation';
|
||||||
@import '../govuk_elements/public/sass/elements/icons';
|
@import '../govuk_elements/public/sass/elements/icons';
|
||||||
@import '../govuk_elements/public/sass/elements/layout';
|
@import '../govuk_elements/public/sass/elements/layout';
|
||||||
@import '../govuk_elements/public/sass/elements/lists';
|
@import '../govuk_elements/public/sass/elements/lists';
|
||||||
|
|||||||
@@ -4,5 +4,5 @@ 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, forgot_password, new_password
|
code_not_received, jobs, dashboard, templates, service_settings, forgot_password, new_password, styleguide
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
from flask_wtf import Form
|
from flask_wtf import Form
|
||||||
from wtforms import StringField, PasswordField, ValidationError, FileField
|
|
||||||
|
from wtforms import (
|
||||||
|
StringField,
|
||||||
|
PasswordField,
|
||||||
|
ValidationError,
|
||||||
|
TextAreaField,
|
||||||
|
FileField
|
||||||
|
)
|
||||||
from wtforms.validators import DataRequired, Email, Length, Regexp
|
from wtforms.validators import DataRequired, Email, Length, Regexp
|
||||||
|
|
||||||
from app.main.validators import Blacklist, ValidateUserCodes, CsvFileValidator
|
from app.main.validators import Blacklist, ValidateUserCodes, CsvFileValidator
|
||||||
@@ -124,6 +131,19 @@ class AddServiceForm(Form):
|
|||||||
raise ValidationError('Service name already exists')
|
raise ValidationError('Service name already exists')
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceNameForm(Form):
|
||||||
|
service_name = StringField(u'New name')
|
||||||
|
|
||||||
|
|
||||||
|
class ConfirmPasswordForm(Form):
|
||||||
|
password = PasswordField(u'Enter password')
|
||||||
|
|
||||||
|
|
||||||
|
class TemplateForm(Form):
|
||||||
|
template_name = StringField(u'Template name')
|
||||||
|
template_body = TextAreaField(u'Message')
|
||||||
|
|
||||||
|
|
||||||
class ForgotPasswordForm(Form):
|
class ForgotPasswordForm(Form):
|
||||||
email_address = email_address()
|
email_address = email_address()
|
||||||
|
|
||||||
@@ -133,6 +153,5 @@ class NewPasswordForm(Form):
|
|||||||
|
|
||||||
|
|
||||||
class CsvUploadForm(Form):
|
class CsvUploadForm(Form):
|
||||||
file = FileField('File to upload',
|
file = FileField('File to upload', validators=[DataRequired(
|
||||||
validators=[DataRequired(message='Please pick a file'),
|
message='Please pick a file'), CsvFileValidator()])
|
||||||
CsvFileValidator()])
|
|
||||||
|
|||||||
0
app/main/notifications/__init__.py
Normal file
0
app/main/notifications/__init__.py
Normal file
@@ -1,54 +0,0 @@
|
|||||||
from random import randint
|
|
||||||
|
|
||||||
from flask import url_for, current_app
|
|
||||||
|
|
||||||
from app import admin_api_client
|
|
||||||
from app.main.dao import verify_codes_dao
|
|
||||||
|
|
||||||
|
|
||||||
def create_verify_code():
|
|
||||||
return ''.join(["%s" % randint(0, 9) for _ in range(0, 5)])
|
|
||||||
|
|
||||||
|
|
||||||
def send_sms_code(user_id, mobile_number):
|
|
||||||
sms_code = create_verify_code()
|
|
||||||
verify_codes_dao.add_code(user_id=user_id, code=sms_code, code_type='sms')
|
|
||||||
admin_api_client.send_sms(mobile_number=mobile_number, message=sms_code, token=admin_api_client.auth_token)
|
|
||||||
|
|
||||||
return sms_code
|
|
||||||
|
|
||||||
|
|
||||||
def send_email_code(user_id, email):
|
|
||||||
email_code = create_verify_code()
|
|
||||||
verify_codes_dao.add_code(user_id=user_id, code=email_code, code_type='email')
|
|
||||||
admin_api_client.send_email(email_address=email,
|
|
||||||
from_str='notify@digital.cabinet-office.gov.uk',
|
|
||||||
message=email_code,
|
|
||||||
subject='Verification code',
|
|
||||||
token=admin_api_client.auth_token)
|
|
||||||
return email_code
|
|
||||||
|
|
||||||
|
|
||||||
def send_change_password_email(email):
|
|
||||||
link_to_change_password = url_for('.new_password', token=generate_token(email), _external=True)
|
|
||||||
admin_api_client.send_email(email_address=email,
|
|
||||||
from_str='notify@digital.cabinet-office.gov.uk',
|
|
||||||
message=link_to_change_password,
|
|
||||||
subject='Reset password for GOV.UK Notify',
|
|
||||||
token=admin_api_client.auth_token)
|
|
||||||
|
|
||||||
|
|
||||||
def generate_token(email):
|
|
||||||
from itsdangerous import TimestampSigner
|
|
||||||
signer = TimestampSigner(current_app.config['SECRET_KEY'])
|
|
||||||
return signer.sign(email).decode('utf8')
|
|
||||||
|
|
||||||
|
|
||||||
def check_token(token):
|
|
||||||
from itsdangerous import TimestampSigner, SignatureExpired
|
|
||||||
signer = TimestampSigner(current_app.config['SECRET_KEY'])
|
|
||||||
try:
|
|
||||||
email = signer.unsign(token, max_age=current_app.config['TOKEN_MAX_AGE_SECONDS'])
|
|
||||||
return email
|
|
||||||
except SignatureExpired as e:
|
|
||||||
current_app.logger.info('token expired %s' % e)
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
from flask import (
|
from flask import (
|
||||||
render_template, redirect, jsonify, session, url_for)
|
render_template, redirect, session, url_for)
|
||||||
|
|
||||||
from app.main import main
|
from app.main import main
|
||||||
from app.main.dao import users_dao
|
from app.main.dao import users_dao
|
||||||
from app.main.forms import EmailNotReceivedForm, TextNotReceivedForm
|
from app.main.forms import EmailNotReceivedForm, TextNotReceivedForm
|
||||||
from app.main.views import send_sms_code, send_email_code
|
from app.notify_client.sender import send_sms_code, send_email_code
|
||||||
|
|
||||||
|
|
||||||
@main.route('/email-not-received', methods=['GET', 'POST'])
|
@main.route('/email-not-received', methods=['GET', 'POST'])
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
from flask import render_template, flash, current_app
|
from flask import render_template, current_app
|
||||||
from app.main import main
|
from app.main import main
|
||||||
from app.main.dao import users_dao
|
from app.main.dao import users_dao
|
||||||
from app.main.forms import ForgotPasswordForm
|
from app.main.forms import ForgotPasswordForm
|
||||||
from app.main.views import send_change_password_email
|
from app.notify_client.sender import send_change_password_email
|
||||||
|
|
||||||
|
|
||||||
@main.route('/forgot-password', methods=['GET', 'POST'])
|
@main.route('/forgot-password', methods=['GET', 'POST'])
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from flask import (render_template, url_for, redirect, flash)
|
|||||||
from app.main import main
|
from app.main import main
|
||||||
from app.main.dao import users_dao
|
from app.main.dao import users_dao
|
||||||
from app.main.forms import NewPasswordForm
|
from app.main.forms import NewPasswordForm
|
||||||
from app.main.views import send_sms_code, check_token
|
from app.notify_client.sender import check_token, send_sms_code
|
||||||
|
|
||||||
|
|
||||||
@main.route('/new-password/<path:token>', methods=['GET', 'POST'])
|
@main.route('/new-password/<path:token>', methods=['GET', 'POST'])
|
||||||
@@ -13,7 +13,7 @@ def new_password(token):
|
|||||||
flash('The link in the email we sent you has expired. Enter your email address to resend.')
|
flash('The link in the email we sent you has expired. Enter your email address to resend.')
|
||||||
return redirect(url_for('.forgot_password'))
|
return redirect(url_for('.forgot_password'))
|
||||||
|
|
||||||
user = users_dao.get_user_by_email(email_address=email_address.decode('utf-8'))
|
user = users_dao.get_user_by_email(email_address=email_address)
|
||||||
if user and user.state != 'request_password_reset':
|
if user and user.state != 'request_password_reset':
|
||||||
flash('The link in the email we sent you has already been used.')
|
flash('The link in the email we sent you has already been used.')
|
||||||
return redirect(url_for('.index'))
|
return redirect(url_for('.index'))
|
||||||
|
|||||||
@@ -5,12 +5,14 @@ from flask import render_template, redirect, session
|
|||||||
from app.main import main
|
from app.main import main
|
||||||
from app.main.dao import users_dao
|
from app.main.dao import users_dao
|
||||||
from app.main.forms import RegisterUserForm
|
from app.main.forms import RegisterUserForm
|
||||||
from app.main.views import send_sms_code, send_email_code
|
|
||||||
from app.models import User
|
from app.models import User
|
||||||
|
|
||||||
|
|
||||||
# TODO how do we handle duplicate unverifed email addresses?
|
# TODO how do we handle duplicate unverifed email addresses?
|
||||||
# malicious or otherwise.
|
# malicious or otherwise.
|
||||||
|
from app.notify_client.sender import send_sms_code, send_email_code
|
||||||
|
|
||||||
|
|
||||||
@main.route('/register', methods=['GET', 'POST'])
|
@main.route('/register', methods=['GET', 'POST'])
|
||||||
def register():
|
def register():
|
||||||
form = RegisterUserForm(users_dao.get_user_by_email)
|
form = RegisterUserForm(users_dao.get_user_by_email)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from flask import render_template, redirect, request, url_for, abort
|
|||||||
from flask_login import login_required
|
from flask_login import login_required
|
||||||
|
|
||||||
from app.main import main
|
from app.main import main
|
||||||
|
from app.main.forms import ConfirmPasswordForm, ServiceNameForm
|
||||||
|
|
||||||
service = {
|
service = {
|
||||||
'name': 'Service name',
|
'name': 'Service name',
|
||||||
@@ -20,10 +21,15 @@ def service_settings():
|
|||||||
|
|
||||||
@main.route("/service-settings/name", methods=['GET', 'POST'])
|
@main.route("/service-settings/name", methods=['GET', 'POST'])
|
||||||
def name():
|
def name():
|
||||||
|
|
||||||
|
form = ServiceNameForm()
|
||||||
|
form.service_name.data = 'Service name'
|
||||||
|
|
||||||
if request.method == 'GET':
|
if request.method == 'GET':
|
||||||
return render_template(
|
return render_template(
|
||||||
'views/service-settings/name.html',
|
'views/service-settings/name.html',
|
||||||
service=service
|
service=service,
|
||||||
|
form=form
|
||||||
)
|
)
|
||||||
elif request.method == 'POST':
|
elif request.method == 'POST':
|
||||||
return redirect(url_for('.confirm_name_change'))
|
return redirect(url_for('.confirm_name_change'))
|
||||||
@@ -31,10 +37,14 @@ def name():
|
|||||||
|
|
||||||
@main.route("/service-settings/name/confirm", methods=['GET', 'POST'])
|
@main.route("/service-settings/name/confirm", methods=['GET', 'POST'])
|
||||||
def confirm_name_change():
|
def confirm_name_change():
|
||||||
|
|
||||||
|
form = ConfirmPasswordForm()
|
||||||
|
|
||||||
if request.method == 'GET':
|
if request.method == 'GET':
|
||||||
return render_template(
|
return render_template(
|
||||||
'views/service-settings/confirm.html',
|
'views/service-settings/confirm.html',
|
||||||
heading='Change your service name'
|
heading='Change your service name',
|
||||||
|
form=form
|
||||||
)
|
)
|
||||||
elif request.method == 'POST':
|
elif request.method == 'POST':
|
||||||
return redirect(url_for('.service_settings'))
|
return redirect(url_for('.service_settings'))
|
||||||
@@ -64,11 +74,15 @@ def status():
|
|||||||
|
|
||||||
@main.route("/service-settings/status/confirm", methods=['GET', 'POST'])
|
@main.route("/service-settings/status/confirm", methods=['GET', 'POST'])
|
||||||
def confirm_status_change():
|
def confirm_status_change():
|
||||||
|
|
||||||
|
form = ConfirmPasswordForm()
|
||||||
|
|
||||||
if request.method == 'GET':
|
if request.method == 'GET':
|
||||||
return render_template(
|
return render_template(
|
||||||
'views/service-settings/confirm.html',
|
'views/service-settings/confirm.html',
|
||||||
heading='Turn off all outgoing notifications',
|
heading='Turn off all outgoing notifications',
|
||||||
destructive=True
|
destructive=True,
|
||||||
|
form=form
|
||||||
)
|
)
|
||||||
elif request.method == 'POST':
|
elif request.method == 'POST':
|
||||||
return redirect(url_for('.service_settings'))
|
return redirect(url_for('.service_settings'))
|
||||||
@@ -87,11 +101,15 @@ def delete():
|
|||||||
|
|
||||||
@main.route("/service-settings/delete/confirm", methods=['GET', 'POST'])
|
@main.route("/service-settings/delete/confirm", methods=['GET', 'POST'])
|
||||||
def confirm_delete():
|
def confirm_delete():
|
||||||
|
|
||||||
|
form = ConfirmPasswordForm()
|
||||||
|
|
||||||
if request.method == 'GET':
|
if request.method == 'GET':
|
||||||
return render_template(
|
return render_template(
|
||||||
'views/service-settings/confirm.html',
|
'views/service-settings/confirm.html',
|
||||||
heading='Delete this service from Notify',
|
heading='Delete this service from Notify',
|
||||||
destructive=True
|
destructive=True,
|
||||||
|
form=form
|
||||||
)
|
)
|
||||||
elif request.method == 'POST':
|
elif request.method == 'POST':
|
||||||
return redirect(url_for('.dashboard'))
|
return redirect(url_for('.dashboard'))
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
from flask import (
|
from flask import (
|
||||||
render_template, redirect, jsonify, url_for)
|
render_template, redirect, url_for)
|
||||||
from flask import session
|
from flask import session
|
||||||
|
|
||||||
from app.main import main
|
from app.main import main
|
||||||
from app.main.dao import users_dao
|
from app.main.dao import users_dao
|
||||||
from app.main.encryption import check_hash
|
from app.main.encryption import check_hash
|
||||||
from app.main.forms import LoginForm
|
from app.main.forms import LoginForm
|
||||||
from app.main.views import send_sms_code
|
from app.notify_client.sender import send_sms_code
|
||||||
|
|
||||||
|
|
||||||
@main.route('/sign-in', methods=(['GET', 'POST']))
|
@main.route('/sign-in', methods=(['GET', 'POST']))
|
||||||
|
|||||||
24
app/main/views/styleguide.py
Normal file
24
app/main/views/styleguide.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
from flask import render_template
|
||||||
|
from flask_wtf import Form
|
||||||
|
from wtforms import StringField, PasswordField, TextAreaField, validators
|
||||||
|
from app.main import main
|
||||||
|
|
||||||
|
|
||||||
|
@main.route('/_styleguide')
|
||||||
|
def styleguide():
|
||||||
|
|
||||||
|
class FormExamples(Form):
|
||||||
|
username = StringField(u'Username')
|
||||||
|
password = PasswordField(u'Password', [validators.required()])
|
||||||
|
message = TextAreaField(u'Message')
|
||||||
|
|
||||||
|
form = FormExamples()
|
||||||
|
|
||||||
|
form.message.data = "Your vehicle tax for ((registration number)) is due on ((date)). Renew online at www.gov.uk/vehicle-tax" # noqa
|
||||||
|
|
||||||
|
form.validate()
|
||||||
|
|
||||||
|
return render_template(
|
||||||
|
'views/styleguide.html',
|
||||||
|
form=form
|
||||||
|
)
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
from flask import request, render_template, redirect, url_for
|
from flask import request, render_template, redirect, url_for
|
||||||
|
|
||||||
from app.main import main
|
from app.main import main
|
||||||
|
from app.main.forms import TemplateForm
|
||||||
|
|
||||||
|
|
||||||
@main.route("/templates")
|
@main.route("/templates")
|
||||||
@@ -10,12 +11,17 @@ def manage_templates():
|
|||||||
|
|
||||||
@main.route("/templates/template", methods=['GET', 'POST'])
|
@main.route("/templates/template", methods=['GET', 'POST'])
|
||||||
def add_template():
|
def add_template():
|
||||||
|
|
||||||
|
form = TemplateForm()
|
||||||
|
|
||||||
|
form.template_name.data = 'Reminder'
|
||||||
|
form.template_body.data = 'Vehicle tax: Your vehicle tax for ((registration number)) expires on ((date)). Tax your vehicle at www.gov.uk/vehicle-tax' # noqa
|
||||||
|
|
||||||
if request.method == 'GET':
|
if request.method == 'GET':
|
||||||
return render_template(
|
return render_template(
|
||||||
'views/edit-template.html',
|
'views/edit-template.html',
|
||||||
template_name='Reminder',
|
h1='Edit template',
|
||||||
template_body='Vehicle tax: Your vehicle tax for ((registration number)) expires on ((date)). Tax your vehicle at www.gov.uk/vehicle-tax', # noqa
|
form=form
|
||||||
h1='Edit template'
|
|
||||||
)
|
)
|
||||||
elif request.method == 'POST':
|
elif request.method == 'POST':
|
||||||
return redirect(url_for('.manage_templates'))
|
return redirect(url_for('.manage_templates'))
|
||||||
@@ -23,10 +29,14 @@ def add_template():
|
|||||||
|
|
||||||
@main.route("/templates/template/add", methods=['GET', 'POST'])
|
@main.route("/templates/template/add", methods=['GET', 'POST'])
|
||||||
def edit_template():
|
def edit_template():
|
||||||
|
|
||||||
|
form = TemplateForm()
|
||||||
|
|
||||||
if request.method == 'GET':
|
if request.method == 'GET':
|
||||||
return render_template(
|
return render_template(
|
||||||
'views/edit-template.html',
|
'views/edit-template.html',
|
||||||
h1='Add template'
|
h1='Add template',
|
||||||
|
form=form
|
||||||
)
|
)
|
||||||
elif request.method == 'POST':
|
elif request.method == 'POST':
|
||||||
return redirect(url_for('.manage_templates'))
|
return redirect(url_for('.manage_templates'))
|
||||||
|
|||||||
52
app/notify_client/sender.py
Normal file
52
app/notify_client/sender.py
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
from random import randint
|
||||||
|
from flask import url_for, current_app
|
||||||
|
from itsdangerous import URLSafeTimedSerializer, SignatureExpired
|
||||||
|
from app import admin_api_client
|
||||||
|
from app.main.dao import verify_codes_dao
|
||||||
|
|
||||||
|
|
||||||
|
def create_verify_code():
|
||||||
|
return ''.join(["%s" % randint(0, 9) for _ in range(0, 5)])
|
||||||
|
|
||||||
|
|
||||||
|
def send_sms_code(user_id, mobile_number):
|
||||||
|
sms_code = create_verify_code()
|
||||||
|
verify_codes_dao.add_code(user_id=user_id, code=sms_code, code_type='sms')
|
||||||
|
admin_api_client.send_sms(mobile_number=mobile_number, message=sms_code, token=admin_api_client.auth_token)
|
||||||
|
|
||||||
|
return sms_code
|
||||||
|
|
||||||
|
|
||||||
|
def send_email_code(user_id, email):
|
||||||
|
email_code = create_verify_code()
|
||||||
|
verify_codes_dao.add_code(user_id=user_id, code=email_code, code_type='email')
|
||||||
|
admin_api_client.send_email(email_address=email,
|
||||||
|
from_str='notify@digital.cabinet-office.gov.uk',
|
||||||
|
message=email_code,
|
||||||
|
subject='Verification code',
|
||||||
|
token=admin_api_client.auth_token)
|
||||||
|
return email_code
|
||||||
|
|
||||||
|
|
||||||
|
def send_change_password_email(email):
|
||||||
|
link_to_change_password = url_for('.new_password', token=generate_token(email), _external=True)
|
||||||
|
admin_api_client.send_email(email_address=email,
|
||||||
|
from_str='notify@digital.cabinet-office.gov.uk',
|
||||||
|
message=link_to_change_password,
|
||||||
|
subject='Reset password for GOV.UK Notify',
|
||||||
|
token=admin_api_client.auth_token)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_token(email):
|
||||||
|
ser = URLSafeTimedSerializer(current_app.config['SECRET_KEY'])
|
||||||
|
return ser.dumps(email, current_app.config.get('DANGEROUS_SALT'))
|
||||||
|
|
||||||
|
|
||||||
|
def check_token(token):
|
||||||
|
ser = URLSafeTimedSerializer(current_app.config['SECRET_KEY'])
|
||||||
|
try:
|
||||||
|
email = ser.loads(token, max_age=current_app.config['TOKEN_MAX_AGE_SECONDS'],
|
||||||
|
salt=current_app.config.get('DANGEROUS_SALT'))
|
||||||
|
return email
|
||||||
|
except SignatureExpired as e:
|
||||||
|
current_app.logger.info('token expired %s' % e)
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
{%- from "components/form-field.html" import render_field %}
|
|
||||||
{% extends "govuk_template.html" %}
|
{% extends "govuk_template.html" %}
|
||||||
|
|
||||||
{% block head %}
|
{% block head %}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
{% macro banner(body) %}
|
{% macro banner(body, with_tick=False) %}
|
||||||
<div class='banner'>{{ body }}</div>
|
<div class='banner{% if with_tick %}-with-tick{% endif %}'>
|
||||||
|
{{ body }}
|
||||||
|
</div>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
{% macro render_field(field) %}
|
|
||||||
<dt>{{ field.label }}
|
|
||||||
<dd>{{ field(**kwargs)|safe }}
|
|
||||||
{% if field.errors %}
|
|
||||||
<ul class=error>
|
|
||||||
{% for error in field.errors %}
|
|
||||||
<li>{{ error }}</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% endif %}
|
|
||||||
</dd>
|
|
||||||
{% endmacro %}
|
|
||||||
@@ -10,11 +10,3 @@
|
|||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
{% macro message_status(status, time) %}
|
|
||||||
<div class="sms-message-history">
|
|
||||||
<p class="sms-message-history-heading">
|
|
||||||
{{ status }} <span class="sms-message-history-heading-time">{{ time }}</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
{% endmacro %}
|
|
||||||
|
|||||||
@@ -1,15 +1,21 @@
|
|||||||
{% macro textbox(name, label, value='', small=True, highlight_tags=False, password=False) %}
|
{% macro textbox(field, hint=False, highlight_tags=False) %}
|
||||||
<div class="form-group">
|
<div class="form-group{% if field.errors %} error{% endif %}">
|
||||||
<label class="form-label" for="{{ name }}">{{ label }}</label>
|
<label class="form-label" for="{{ field.name }}">
|
||||||
{% if small %}
|
{{ field.label }}
|
||||||
<input class="form-control" id="{{ name }}" name="{{ name }}" type="{{ 'password' if password else 'text' }}" value="{{ value }}">
|
{% if hint %}
|
||||||
{% else %}
|
<span class="form-hint">
|
||||||
<textarea
|
{{ hint }}
|
||||||
class="form-control {% if highlight_tags %}textbox-highlight-textbox{% endif %}"
|
</span>
|
||||||
id="{{ name }}" name="{{ name }}"
|
{% endif %}
|
||||||
cols="30" rows="10"
|
{% if field.errors %}
|
||||||
{% if highlight_tags %}data-module='highlight-tags'{% endif %}
|
<span class="error-message">
|
||||||
>{{ value }}</textarea>
|
{{ field.errors[0] }}
|
||||||
{% endif %}
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</label>
|
||||||
|
{{ field(**{
|
||||||
|
'class': 'form-control textbox-highlight-textbox' if highlight_tags else 'form-control',
|
||||||
|
'data-module': 'highlight-tags' if highlight_tags else ''
|
||||||
|
}) }}
|
||||||
</div>
|
</div>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{% extends "admin_template.html" %}
|
{% extends "admin_template.html" %}
|
||||||
{% from "components/form-field.html" import render_field %}
|
{% from "components/textbox.html" import textbox %}
|
||||||
|
|
||||||
{% block page_title %}
|
{% block page_title %}
|
||||||
GOV.UK Notify | Set up service
|
GOV.UK Notify | Set up service
|
||||||
@@ -17,9 +17,8 @@ GOV.UK Notify | Set up service
|
|||||||
<li>as your email sender name</li>
|
<li>as your email sender name</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<form autocomplete="off" action="" method="post">
|
<form autocomplete="off" method="post">
|
||||||
{{ form.hidden_tag() }}
|
{{ textbox(form.service_name) }}
|
||||||
{{ render_field(form.service_name, class='form-control-2-3') }}
|
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<button class="button" href="dashboard" role="button">Continue</button>
|
<button class="button" href="dashboard" role="button">Continue</button>
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ GOV.UK Notify | Edit template
|
|||||||
<h1 class="heading-xlarge">{{ h1 }}</h1>
|
<h1 class="heading-xlarge">{{ h1 }}</h1>
|
||||||
|
|
||||||
<form method="post">
|
<form method="post">
|
||||||
{{ textbox(name='template_name', label='Template name', value=template_name) }}
|
{{ textbox(form.template_name) }}
|
||||||
{{ textbox(name='template_body', label='Message', small=False, value=template_body, highlight_tags=True) }}
|
{{ textbox(form.template_body, highlight_tags=True) }}
|
||||||
{{ page_footer(
|
{{ page_footer(
|
||||||
'Save and continue',
|
'Save and continue',
|
||||||
back_link=url_for('.dashboard'),
|
back_link=url_for('.dashboard'),
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{% extends "admin_template.html" %}
|
{% extends "admin_template.html" %}
|
||||||
|
{% from "components/textbox.html" import textbox %}
|
||||||
{% from "components/page-footer.html" import page_footer %}
|
{% from "components/page-footer.html" import page_footer %}
|
||||||
|
|
||||||
{% block page_title %}
|
{% block page_title %}
|
||||||
@@ -14,13 +15,10 @@ GOV.UK Notify
|
|||||||
<p>Check your email address is correct and then resend the confirmation code.</p>
|
<p>Check your email address is correct and then resend the confirmation code.</p>
|
||||||
<p>
|
<p>
|
||||||
</p>
|
</p>
|
||||||
<form autocomplete="off" action="" method="post">
|
<form autocomplete="off" method="post">
|
||||||
{{ form.hidden_tag() }}
|
{{ textbox(form.email_address) }}
|
||||||
{{ render_field(form.email_address, class='form-control-2-3') }}
|
<span class="font-xsmall">Your email address must end in .gov.uk</span>
|
||||||
<span class="font-xsmall">Your email address must end in .gov.uk</span>
|
{{ page_footer('Resend confirmation code') }}
|
||||||
<p>
|
|
||||||
</p>
|
|
||||||
{{ page_footer('Resend confirmation code') }}
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
{% extends "admin_template.html" %}
|
{% extends "admin_template.html" %}
|
||||||
|
{% from "components/textbox.html" import textbox %}
|
||||||
|
{% from "components/page-footer.html" import page_footer %}
|
||||||
|
|
||||||
{% block page_title %}
|
{% block page_title %}
|
||||||
GOV.UK Notify
|
GOV.UK Notify
|
||||||
@@ -12,13 +14,11 @@ 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>
|
||||||
|
|
||||||
<form autocomplete="off" action="" method="post">
|
<form autocomplete="off" method="post">
|
||||||
{{ form.hidden_tag() }}
|
{{ textbox(form.email_address) }}
|
||||||
{{ render_field(form.email_address, class='form-control-2-3') }}
|
{{ page_footer("Send email") }}
|
||||||
<p>
|
|
||||||
<button class="button" role="button">Send email</button>
|
|
||||||
</p>
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ GOV.UK Notify | Notifications activity
|
|||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
{{ banner(flash_message) }}
|
{{ banner(flash_message, with_tick=True) }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<ul class="grid-row job-totals">
|
<ul class="grid-row job-totals">
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
{% extends "admin_template.html" %}
|
{% extends "admin_template.html" %}
|
||||||
|
{% from "components/textbox.html" import textbox %}
|
||||||
|
{% from "components/page-footer.html" import page_footer %}
|
||||||
|
|
||||||
{% block page_title %}
|
{% block page_title %}
|
||||||
GOV.UK Notify
|
GOV.UK Notify
|
||||||
@@ -13,17 +15,9 @@ GOV.UK Notify
|
|||||||
|
|
||||||
<p> You can now create a new password for your account.</p>
|
<p> You can now create a new password for your account.</p>
|
||||||
|
|
||||||
<form action="" autocomplete="off" method="post">
|
<form method="post" autocomplete="off">
|
||||||
{{ form.hidden_tag() }}
|
{{ textbox(form.new_password, hint="Your password must have at least 10 characters") }}
|
||||||
<p>
|
{{ page_footer("Continue") }}
|
||||||
{{ render_field(form.new_password, class="form-control-1-4", type="password") }}
|
|
||||||
<span class="font-xsmall">Your password must have at least 10 characters</span>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<button class="button" role="button">Continue</button>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
{% else %}
|
{% else %}
|
||||||
Message about email address does not exist. Some one needs to figure out the words here.
|
Message about email address does not exist. Some one needs to figure out the words here.
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{% extends "withnav_template.html" %}
|
{% extends "withnav_template.html" %}
|
||||||
{% from "components/sms-message.html" import sms_message, message_status %}
|
{% from "components/sms-message.html" import sms_message, message_status %}
|
||||||
|
{% from "components/banner.html" import banner %}
|
||||||
{% from "components/page-footer.html" import page_footer %}
|
{% from "components/page-footer.html" import page_footer %}
|
||||||
|
|
||||||
{% block page_title %}
|
{% block page_title %}
|
||||||
@@ -18,7 +19,9 @@ GOV.UK Notify | Notifications activity
|
|||||||
{{ sms_message(message.message, message.phone) }}
|
{{ sms_message(message.message, message.phone) }}
|
||||||
</div>
|
</div>
|
||||||
<div class="column-one-third">
|
<div class="column-one-third">
|
||||||
{{ message_status(message.status, delivered_at) }}
|
{{ banner(
|
||||||
|
"{} {}".format(message.status, delivered_at)
|
||||||
|
) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{% extends "admin_template.html" %}
|
{% extends "admin_template.html" %}
|
||||||
|
{% from "components/textbox.html" import textbox %}
|
||||||
{% from "components/page-footer.html" import page_footer %}
|
{% from "components/page-footer.html" import page_footer %}
|
||||||
|
|
||||||
{% block page_title %}
|
{% block page_title %}
|
||||||
@@ -16,12 +17,10 @@ GOV.UK Notify | Create an account
|
|||||||
<form autocomplete="off" action="" method="post">
|
<form autocomplete="off" action="" method="post">
|
||||||
{{ form.hidden_tag() }}
|
{{ form.hidden_tag() }}
|
||||||
|
|
||||||
{{ render_field(form.name, class='form-control-2-3') }}
|
{{ textbox(form.name) }}
|
||||||
{{ render_field(form.email_address, class='form-control-2-3') }}
|
{{ textbox(form.email_address, hint="Your email address must end in .gov.uk") }}
|
||||||
<span class="font-xsmall">Your email address must end in .gov.uk</span>
|
{{ textbox(form.mobile_number) }}
|
||||||
{{ render_field(form.mobile_number, class='form-control-2-3') }}
|
{{ textbox(form.password, hint="Your password must have at least 10 characters") }}
|
||||||
{{ render_field(form.password, class='form-control-2-3') }}
|
|
||||||
<span class="font-xsmall">Your password must have at least 10 characters</span></label>
|
|
||||||
{{ page_footer("Continue") }}
|
{{ page_footer("Continue") }}
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{% extends "withnav_template.html" %}
|
{% extends "withnav_template.html" %}
|
||||||
{% from "components/sms-message.html" import sms_message %}
|
{% from "components/sms-message.html" import sms_message %}
|
||||||
{% from "components/page-footer.html" import page_footer %}
|
{% from "components/page-footer.html" import page_footer %}
|
||||||
{% from "components/form-field.html" import render_field %}
|
{% from "components/textbox.html" import textbox %}
|
||||||
|
|
||||||
{% block page_title %}
|
{% block page_title %}
|
||||||
GOV.UK Notify | Send text messages
|
GOV.UK Notify | Send text messages
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
You can also <a href="#">download an example CSV</a>.
|
You can also <a href="#">download an example CSV</a>.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
{{render_field(form.file)}}
|
{{textbox(form.file)}}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{{ page_footer("Continue") }}
|
{{ page_footer("Continue") }}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ GOV.UK Notify | Service settings
|
|||||||
<div class="column-three-quarters">
|
<div class="column-three-quarters">
|
||||||
|
|
||||||
<form method="post">
|
<form method="post">
|
||||||
{{ textbox('new_name', 'Enter your password', password=True) }}
|
{{ textbox(form.password) }}
|
||||||
{{ page_footer(
|
{{ page_footer(
|
||||||
'Confirm',
|
'Confirm',
|
||||||
destructive=destructive,
|
destructive=destructive,
|
||||||
|
|||||||
@@ -13,12 +13,15 @@ GOV.UK Notify | Service settings
|
|||||||
<div class="grid-row">
|
<div class="grid-row">
|
||||||
<div class="column-three-quarters">
|
<div class="column-three-quarters">
|
||||||
|
|
||||||
<p>
|
<p>Users will see your service name:</p>
|
||||||
Your service name ({{ service.name }}) is included in every sent notification
|
|
||||||
</p>
|
<ul class="list-bullet">
|
||||||
|
<li>at the start of every text message, eg ‘Vehicle tax: we received your payment, thank you’</li>
|
||||||
|
<li>as your email sender name</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
<form method="post">
|
<form method="post">
|
||||||
{{ textbox('new_name', 'New name', value=service.name) }}
|
{{ textbox(form.service_name) }}
|
||||||
{{ page_footer(
|
{{ page_footer(
|
||||||
'Save',
|
'Save',
|
||||||
back_link=url_for('.service_settings')
|
back_link=url_for('.service_settings')
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{% extends "admin_template.html" %}
|
{% extends "admin_template.html" %}
|
||||||
|
{% from "components/textbox.html" import textbox %}
|
||||||
{% from "components/page-footer.html" import page_footer %}
|
{% from "components/page-footer.html" import page_footer %}
|
||||||
|
|
||||||
{% block page_title %}
|
{% block page_title %}
|
||||||
@@ -13,14 +14,10 @@ Sign in
|
|||||||
|
|
||||||
<p>If you do not have an account, you can <a href="register">register for one now</a>.</p>
|
<p>If you do not have an account, you can <a href="register">register for one now</a>.</p>
|
||||||
|
|
||||||
<form autocomplete="off" action="" method="post">
|
<form autocomplete="off" method="post">
|
||||||
{{ form.hidden_tag() }}
|
{{ textbox(form.email_address) }}
|
||||||
{{ render_field(form.email_address, class='form-control-2-3') }}
|
{{ textbox(form.password) }}
|
||||||
{{ render_field(form.password, class='form-control-2-3') }}
|
{{ page_footer("Continue", back_link="#", back_link_text="Forgotten password?") }}
|
||||||
<p>
|
|
||||||
<span class="font-xsmall"><a href="{{url_for('main.forgot_password')}}">Forgotten password?</a></span>
|
|
||||||
</p>
|
|
||||||
{{ page_footer("Continue") }}
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
155
app/templates/views/styleguide.html
Normal file
155
app/templates/views/styleguide.html
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
{% extends "admin_template.html" %}
|
||||||
|
|
||||||
|
{% from "components/banner.html" import banner %}
|
||||||
|
{% from "components/big-number.html" import big_number %}
|
||||||
|
{% from "components/browse-list.html" import browse_list %}
|
||||||
|
{% from "components/page-footer.html" import page_footer %}
|
||||||
|
{% from "components/sms-message.html" import sms_message %}
|
||||||
|
{% from "components/table.html" import mapping_table, list_table, row, field %}
|
||||||
|
{% from "components/textbox.html" import textbox %}
|
||||||
|
|
||||||
|
{% block page_title %}
|
||||||
|
Styleguide – GOV.UK Notify
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block fullwidth_content %}
|
||||||
|
|
||||||
|
<h1 class="heading-xlarge">
|
||||||
|
Styleguide
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p><a href="https://github.com/alphagov/notifications-admin/blob/master/app/templates/views/styleguide.html">View source</a></p>
|
||||||
|
|
||||||
|
<h2 class="heading-large">Banner</h2>
|
||||||
|
<p>Used to show the result of a user’s action.</p>
|
||||||
|
{{ banner("This is a banner", with_tick=True) }}
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="column-one-third">
|
||||||
|
{{ banner("Delivered 10:20") }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 class="heading-large">Big number</h2>
|
||||||
|
|
||||||
|
<p>Used to show some important statistics.</p>
|
||||||
|
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="column-one-third">
|
||||||
|
{{ big_number("567") }}
|
||||||
|
</div>
|
||||||
|
<div class="column-one-third">
|
||||||
|
{{ big_number("2", "Messages delivered") }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 class="heading-large">Browse list</h2>
|
||||||
|
|
||||||
|
<p>Used to navigate to child pages.</p>
|
||||||
|
|
||||||
|
{{ browse_list([
|
||||||
|
{
|
||||||
|
'title': 'Change your username',
|
||||||
|
'link': 'http://example.com',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'title': 'Change your password',
|
||||||
|
'link': 'http://example.com',
|
||||||
|
'hint': 'Your password is used to log in'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'title': 'Delete everything',
|
||||||
|
'link': 'http://example.com',
|
||||||
|
'hint': 'You can’t undo this',
|
||||||
|
'destructive': True
|
||||||
|
}
|
||||||
|
]) }}
|
||||||
|
|
||||||
|
<h2 class="heading-large">Page footer</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Used to submit forms and optionally provide a link to go back to the
|
||||||
|
previous page.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Must be used inside a form.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Adds a hidden CSRF token to the page.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{{ page_footer(
|
||||||
|
button_text='Save and continue'
|
||||||
|
) }}
|
||||||
|
|
||||||
|
{{ page_footer(
|
||||||
|
button_text='Delete', destructive=True
|
||||||
|
) }}
|
||||||
|
|
||||||
|
{{ page_footer(
|
||||||
|
button_text='Send', back_link='http://example.com', back_link_text="Back to dashboard"
|
||||||
|
) }}
|
||||||
|
|
||||||
|
<h2 class="heading-large">SMS message</h2>
|
||||||
|
|
||||||
|
<p>Used to show or preview an SMS message.</p>
|
||||||
|
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="column-half">
|
||||||
|
{{ sms_message("Your vehicle tax for LC12 BFL is due on 1 March 2016. Renew online at www.gov.uk/vehicle-tax") }}
|
||||||
|
{{ sms_message("Your vehicle tax for ((registration number)) is due on ((date)). Renew online at www.gov.uk/vehicle-tax") }}
|
||||||
|
{{ sms_message(
|
||||||
|
"Your vehicle tax for registration number is due on date. Renew online at www.gov.uk/vehicle-tax",
|
||||||
|
"+44 7700 900 306"
|
||||||
|
) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 class="heading-large">Tables</h2>
|
||||||
|
|
||||||
|
{% call mapping_table(
|
||||||
|
caption='Account settings',
|
||||||
|
field_headings=['Label', 'Value'],
|
||||||
|
field_headings_visible=True,
|
||||||
|
caption_visible=True
|
||||||
|
) %}
|
||||||
|
{% call row() %}
|
||||||
|
{% call field() %}
|
||||||
|
Username
|
||||||
|
{% endcall %}
|
||||||
|
{% call field() %}
|
||||||
|
admin
|
||||||
|
{% endcall %}
|
||||||
|
{% endcall %}
|
||||||
|
{% endcall %}
|
||||||
|
|
||||||
|
{% call(item) list_table(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
'file': 'dispatch_20151114.csv', 'status': 'Queued'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'file': 'dispatch_20151117.csv', 'status': 'Delivered'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'file': 'remdinder_monday.csv', 'status': 'Delivered'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
caption='Messages',
|
||||||
|
field_headings=['File', 'Status'],
|
||||||
|
field_headings_visible=False,
|
||||||
|
caption_visible=True
|
||||||
|
) %}
|
||||||
|
{% call field() %}
|
||||||
|
{{ item.file }}
|
||||||
|
{% endcall %}
|
||||||
|
{% call field() %}
|
||||||
|
{{ item.status }}
|
||||||
|
{% endcall %}
|
||||||
|
{% endcall %}
|
||||||
|
|
||||||
|
<h2 class="heading-large">Textbox</h2>
|
||||||
|
{{ textbox(form.username) }}
|
||||||
|
{{ textbox(form.password) }}
|
||||||
|
{{ textbox(form.message, highlight_tags=True) }}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
{% extends "admin_template.html" %}
|
{% extends "admin_template.html" %}
|
||||||
|
{% from "components/textbox.html" import textbox %}
|
||||||
{% from "components/page-footer.html" import page_footer %}
|
{% from "components/page-footer.html" import page_footer %}
|
||||||
|
|
||||||
{% block page_title %}
|
{% block page_title %}
|
||||||
@@ -13,9 +14,8 @@ GOV.UK Notify
|
|||||||
|
|
||||||
<p>Check your mobile phone number is correct and then resend the confirmation code.</p>
|
<p>Check your mobile phone number is correct and then resend the confirmation code.</p>
|
||||||
|
|
||||||
<form autocomplete="off" action="" method="post">
|
<form autocomplete="off" method="post">
|
||||||
{{ form.hidden_tag() }}
|
{{ textbox(form.mobile_number) }}
|
||||||
{{ render_field(form.mobile_number, class='form-control-2-3') }}
|
|
||||||
{{ page_footer("Resend confirmation code") }}
|
{{ page_footer("Resend confirmation code") }}
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{% extends "admin_template.html" %}
|
{% extends "admin_template.html" %}
|
||||||
|
{% from "components/textbox.html" import textbox %}
|
||||||
{% from "components/page-footer.html" import page_footer %}
|
{% from "components/page-footer.html" import page_footer %}
|
||||||
|
|
||||||
{% block page_title %}
|
{% block page_title %}
|
||||||
@@ -14,9 +15,8 @@ GOV.UK Notify | Text verification
|
|||||||
<p>We've sent you a text message with a verification code.</p>
|
<p>We've sent you a text message with a verification code.</p>
|
||||||
|
|
||||||
|
|
||||||
<form autocomplete="off" action="" method="post">
|
<form autocomplete="off" method="post">
|
||||||
{{ form.hidden_tag() }}
|
{{ textbox(form.sms_code) }}
|
||||||
{{ render_field(form.sms_code, class='form-control-1-4') }}
|
|
||||||
<span class="font-xsmall"><a href="{{ url_for('.verification_code_not_received') }}">I haven't received a text</a></span>
|
<span class="font-xsmall"><a href="{{ url_for('.verification_code_not_received') }}">I haven't received a text</a></span>
|
||||||
{{ page_footer("Continue") }}
|
{{ page_footer("Continue") }}
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{% extends "admin_template.html" %}
|
{% extends "admin_template.html" %}
|
||||||
|
{% from "components/textbox.html" import textbox %}
|
||||||
{% from "components/page-footer.html" import page_footer %}
|
{% from "components/page-footer.html" import page_footer %}
|
||||||
|
|
||||||
{% block page_title %}
|
{% block page_title %}
|
||||||
@@ -13,11 +14,10 @@ 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>
|
||||||
|
|
||||||
<form autocomplete="off" action="" method="post">
|
<form autocomplete="off" method="post">
|
||||||
{{ form.hidden_tag() }}
|
{{ textbox(form.email_code) }}
|
||||||
{{ render_field(form.email_code, class='form-control-1-4') }}
|
|
||||||
<span class="font-xsmall"><a href="{{ url_for('.check_and_resend_email_code')}}">I haven't received an email</a></span>
|
<span class="font-xsmall"><a href="{{ url_for('.check_and_resend_email_code')}}">I haven't received an email</a></span>
|
||||||
{{ render_field(form.sms_code, class='form-control-1-4') }}
|
{{ textbox(form.sms_code) }}
|
||||||
<span class="font-xsmall"><a href="{{ url_for('.check_and_resend_text_code') }}">I haven't received a text</a></span>
|
<span class="font-xsmall"><a href="{{ url_for('.check_and_resend_text_code') }}">I haven't received a text</a></span>
|
||||||
{{ page_footer("Continue") }}
|
{{ page_footer("Continue") }}
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class Config(object):
|
|||||||
SECRET_KEY = 'secret-key'
|
SECRET_KEY = 'secret-key'
|
||||||
HTTP_PROTOCOL = 'http'
|
HTTP_PROTOCOL = 'http'
|
||||||
DANGEROUS_SALT = 'itsdangeroussalt'
|
DANGEROUS_SALT = 'itsdangeroussalt'
|
||||||
TOKEN_MAX_AGE_SECONDS = 120000
|
TOKEN_MAX_AGE_SECONDS = 3600
|
||||||
|
|
||||||
MAX_CONTENT_LENGTH = 10 * 1024 * 1024 # 10mb
|
MAX_CONTENT_LENGTH = 10 * 1024 * 1024 # 10mb
|
||||||
|
|
||||||
|
|||||||
0
tests/app/main/notify_client/__init__.py
Normal file
0
tests/app/main/notify_client/__init__.py
Normal file
35
tests/app/main/notify_client/test_sender.py
Normal file
35
tests/app/main/notify_client/test_sender.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
from itsdangerous import BadSignature
|
||||||
|
from pytest import fail
|
||||||
|
|
||||||
|
from app.notify_client.sender import generate_token, check_token
|
||||||
|
|
||||||
|
|
||||||
|
def test_should_return_email_from_signed_token(notifications_admin,
|
||||||
|
notifications_admin_db,
|
||||||
|
notify_db_session):
|
||||||
|
email = 'email@something.com'
|
||||||
|
token = generate_token(email)
|
||||||
|
assert email == check_token(token)
|
||||||
|
|
||||||
|
|
||||||
|
def test_should_throw_exception_when_token_is_tampered_with(notifications_admin,
|
||||||
|
notifications_admin_db,
|
||||||
|
notify_db_session):
|
||||||
|
email = 'email@something.com'
|
||||||
|
token = generate_token(email)
|
||||||
|
try:
|
||||||
|
check_token(token + 'qerqwer')
|
||||||
|
fail()
|
||||||
|
except BadSignature:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def test_return_none_when_token_is_expired(notifications_admin,
|
||||||
|
notifications_admin_db,
|
||||||
|
notify_db_session):
|
||||||
|
with notifications_admin.test_request_context():
|
||||||
|
notifications_admin.config['TOKEN_MAX_AGE_SECONDS'] = -1000
|
||||||
|
email = 'email@something.com'
|
||||||
|
token = generate_token(email)
|
||||||
|
assert check_token(token) is None
|
||||||
|
notifications_admin.config['TOKEN_MAX_AGE_SECONDS'] = 120000
|
||||||
@@ -1,15 +1,14 @@
|
|||||||
from flask import url_for
|
from flask import url_for
|
||||||
|
|
||||||
from app.main.dao import users_dao
|
from app.main.dao import users_dao
|
||||||
from app.main.views import generate_token
|
|
||||||
from tests.app.main import create_test_user
|
from tests.app.main import create_test_user
|
||||||
|
|
||||||
|
|
||||||
def test_should_render_forgot_password(notifications_admin, notifications_admin_db, notify_db_session):
|
def test_should_render_forgot_password(notifications_admin, notifications_admin_db, notify_db_session):
|
||||||
response = notifications_admin.test_client().get('/forgot-password')
|
with notifications_admin.test_request_context():
|
||||||
assert response.status_code == 200
|
response = notifications_admin.test_client().get(url_for('.forgot_password'))
|
||||||
assert 'If you have forgotten your password, we can send you an email to create a new password.' \
|
assert response.status_code == 200
|
||||||
in response.get_data(as_text=True)
|
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_redirect_to_password_reset_sent_and_state_updated(notifications_admin,
|
def test_should_redirect_to_password_reset_sent_and_state_updated(notifications_admin,
|
||||||
@@ -17,25 +16,11 @@ def test_should_redirect_to_password_reset_sent_and_state_updated(notifications_
|
|||||||
mocker,
|
mocker,
|
||||||
notify_db_session):
|
notify_db_session):
|
||||||
mocker.patch("app.admin_api_client.send_email")
|
mocker.patch("app.admin_api_client.send_email")
|
||||||
user = create_test_user('active')
|
|
||||||
response = notifications_admin.test_client().post('/forgot-password',
|
|
||||||
data={'email_address': user.email_address})
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert 'You have been sent an email containing a link to reset your password.' in response.get_data(
|
|
||||||
as_text=True)
|
|
||||||
assert users_dao.get_user_by_id(user.id).state == 'request_password_reset'
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_redirect_to_forgot_password_with_flash_message_when_token_is_expired(notifications_admin,
|
|
||||||
notifications_admin_db,
|
|
||||||
notify_db_session):
|
|
||||||
with notifications_admin.test_request_context():
|
with notifications_admin.test_request_context():
|
||||||
with notifications_admin.test_client() as client:
|
user = create_test_user('active')
|
||||||
notifications_admin.config['TOKEN_MAX_AGE_SECONDS'] = -1000
|
response = notifications_admin.test_client().post(url_for('.forgot_password'),
|
||||||
user = create_test_user('active')
|
data={'email_address': user.email_address})
|
||||||
token = generate_token(user.email_address)
|
assert response.status_code == 200
|
||||||
response = client.post('/new-password/{}'.format(token),
|
assert 'You have been sent an email containing a link to reset your password.' in response.get_data(
|
||||||
data={'new_password': 'a-new_password'})
|
as_text=True)
|
||||||
assert response.status_code == 302
|
assert users_dao.get_user_by_id(user.id).state == 'request_password_reset'
|
||||||
assert response.location == url_for('.forgot_password', _external=True)
|
|
||||||
notifications_admin.config['TOKEN_MAX_AGE_SECONDS'] = 86400
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from flask import url_for
|
|||||||
|
|
||||||
from app.main.dao import users_dao
|
from app.main.dao import users_dao
|
||||||
from app.main.encryption import check_hash
|
from app.main.encryption import check_hash
|
||||||
from app.main.views import generate_token
|
from app.notify_client.sender import generate_token
|
||||||
from tests.app.main import create_test_user
|
from tests.app.main import create_test_user
|
||||||
|
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ def test_should_redirect_to_forgot_password_with_flash_message_when_token_is_exp
|
|||||||
response = client.post(url_for('.new_password', token=token), data={'new_password': 'a-new_password'})
|
response = client.post(url_for('.new_password', token=token), data={'new_password': 'a-new_password'})
|
||||||
assert response.status_code == 302
|
assert response.status_code == 302
|
||||||
assert response.location == url_for('.forgot_password', _external=True)
|
assert response.location == url_for('.forgot_password', _external=True)
|
||||||
notifications_admin.config['TOKEN_MAX_AGE_SECONDS'] = 86400
|
notifications_admin.config['TOKEN_MAX_AGE_SECONDS'] = 3600
|
||||||
|
|
||||||
|
|
||||||
def test_should_redirect_to_forgot_password_when_user_is_active_should_be_request_password_reset(notifications_admin,
|
def test_should_redirect_to_forgot_password_when_user_is_active_should_be_request_password_reset(notifications_admin,
|
||||||
|
|||||||
4
tests/app/main/views/test_styleguide.py
Normal file
4
tests/app/main/views/test_styleguide.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
def test_styleguide_can_render(notifications_admin):
|
||||||
|
response = notifications_admin.test_client().get('/_styleguide')
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
Reference in New Issue
Block a user