From ff4617196f827f1a7bd6cb2bc24c3d8ef2c5ad56 Mon Sep 17 00:00:00 2001 From: Edward Betts Date: Thu, 23 Feb 2017 21:02:58 +0000 Subject: [PATCH] add password reset --- sourcing/forms.py | 10 +++-- sourcing/templates/auth/password_reset.html | 10 +++++ .../auth/password_reset_complete.html | 19 ++++++++ .../auth/password_reset_confirm.html | 10 +++++ .../templates/auth/password_reset_sent.html | 17 +++++++ sourcing/templates/login.html | 12 ++++- sourcing/view.py | 45 ++++++++++++++++++- 7 files changed, 117 insertions(+), 6 deletions(-) create mode 100644 sourcing/templates/auth/password_reset.html create mode 100644 sourcing/templates/auth/password_reset_complete.html create mode 100644 sourcing/templates/auth/password_reset_confirm.html create mode 100644 sourcing/templates/auth/password_reset_sent.html diff --git a/sourcing/forms.py b/sourcing/forms.py index 475df64..37687db 100644 --- a/sourcing/forms.py +++ b/sourcing/forms.py @@ -17,7 +17,7 @@ class SignupForm(Form): [InputRequired(), Email(), Length(min=5, max=EMAIL_LEN)], description="we never share your e-mail address") - password = PasswordField('password', + password = StringField('password', [InputRequired(), Length(min=4, max=PASSWORD_LEN)]) def validate_username(form, field): @@ -51,8 +51,12 @@ class LoginForm(Form): return False class ForgotPasswordForm(Form): - username_or_email = StringField('username or e-mail address', - [InputRequired(), Length(max=EMAIL_LEN)]) + user_or_email = StringField('username or e-mail address', + [InputRequired(), Length(max=EMAIL_LEN)]) + +class PasswordForm(Form): + password = PasswordField('new password', + [InputRequired(), Length(min=4, max=PASSWORD_LEN)]) class AccountSettingsForm(Form): full_name = StringField('full name', [Length(max=64)]) diff --git a/sourcing/templates/auth/password_reset.html b/sourcing/templates/auth/password_reset.html new file mode 100644 index 0000000..77f7b95 --- /dev/null +++ b/sourcing/templates/auth/password_reset.html @@ -0,0 +1,10 @@ +{% from "form/controls.html" import render_field, checkbox, submit %} + +{% set title="Reset password" %} +{% set label="send e-mail" %} + +{% set fields %} +{{ render_field(form.user_or_email) }} +{% endset %} + +{% include "form/simple.html" %} diff --git a/sourcing/templates/auth/password_reset_complete.html b/sourcing/templates/auth/password_reset_complete.html new file mode 100644 index 0000000..697d5bc --- /dev/null +++ b/sourcing/templates/auth/password_reset_complete.html @@ -0,0 +1,19 @@ +{% from "form/controls.html" import render_field, checkbox, submit %} + +{% set title="Reset password" %} +{% set label="send e-mail" %} + +{% include "head.html" %} + +
+
+

{{ title }}

+ +

Your password has been set. You may go ahead and log in now.

+ +

Log in

+ +
+
+ +{% include "foot.html" %} diff --git a/sourcing/templates/auth/password_reset_confirm.html b/sourcing/templates/auth/password_reset_confirm.html new file mode 100644 index 0000000..e7a677e --- /dev/null +++ b/sourcing/templates/auth/password_reset_confirm.html @@ -0,0 +1,10 @@ +{% from "form/controls.html" import render_field %} + +{% set title="Reset password" %} +{% set label="reset" %} + +{% set fields %} +{{ render_field(form.password) }} +{% endset %} + +{% include "form/simple.html" %} diff --git a/sourcing/templates/auth/password_reset_sent.html b/sourcing/templates/auth/password_reset_sent.html new file mode 100644 index 0000000..c872655 --- /dev/null +++ b/sourcing/templates/auth/password_reset_sent.html @@ -0,0 +1,17 @@ +{% from "form/controls.html" import render_field, checkbox, submit %} + +{% set title="Reset password" %} +{% set label="send e-mail" %} + +{% include "head.html" %} + +
+
+

{{ title }}

+

We've emailed you instructions for setting your password, if an account exists with the email you entered. You should receive them shortly.

+ +

If you don't receive an email, please make sure you've entered the address you registered with, and check your spam folder.

+
+
+ +{% include "foot.html" %} diff --git a/sourcing/templates/login.html b/sourcing/templates/login.html index 3b97ea7..420b442 100644 --- a/sourcing/templates/login.html +++ b/sourcing/templates/login.html @@ -7,7 +7,17 @@ {% set fields %} {{ render_field(form.user_or_email) }} {{ render_field(form.password) }} -{{ checkbox(form.remember) }} + +
+ + ยท + forgot password? +
+ + {% endset %} {% include "form/simple.html" %} diff --git a/sourcing/view.py b/sourcing/view.py index 1735b16..3e53769 100644 --- a/sourcing/view.py +++ b/sourcing/view.py @@ -1,9 +1,10 @@ from flask import (Blueprint, render_template, request, redirect, flash, - url_for, abort, jsonify, Response) + url_for, abort, jsonify, Response, current_app) from flask_login import (login_user, current_user, logout_user, login_required, LoginManager) from .forms import (LoginForm, SignupForm, AccountSettingsForm, - UploadSourceDocForm, SourceDocForm, ItemForm) + UploadSourceDocForm, SourceDocForm, ItemForm, + ForgotPasswordForm, PasswordForm) from .model import User, SourceDoc, Item, XanaDoc, XanaLink from .url import get_url from .edl import fulfil_edl_with_sources @@ -13,6 +14,7 @@ from werkzeug.debug.tbtools import get_current_traceback from jinja2 import evalcontextfilter, Markup from functools import wraps from .utils import nbsp_at_start +from itsdangerous import URLSafeTimedSerializer import re @@ -60,6 +62,45 @@ def home(): docs = Item.query.order_by(Item.created) return render_template('home.html', docs=docs) +@bp.route('/password_reset', methods=['GET', 'POST']) +def password_reset(): + form = ForgotPasswordForm() + if not form.validate_on_submit(): + return render_template('auth/password_reset.html', form=form) + ts = URLSafeTimedSerializer(current_app.config["SECRET_KEY"]) + user = User.lookup_user_or_email(form.user_or_email.data) + if user: + token = ts.dumps(user.id, salt='password-reset') + print(token) + return redirect(url_for('.password_reset_sent')) + +@bp.route('/password_reset/sent', methods=['GET', 'POST']) +def password_reset_sent(): + return render_template('auth/password_reset_sent.html') + +@bp.route('/reset/', methods=['GET', 'POST']) +def reset_with_token(token): + ts = URLSafeTimedSerializer(current_app.config["SECRET_KEY"]) + try: + user_id = ts.loads(token, salt='password-reset', max_age=86400) + except: + abort(404) + + form = PasswordForm() + if not form.validate_on_submit(): + return render_template('auth/password_reset_confirm.html', form=form) + + user = User.query.get(user_id) + user.set_password(form.password.data) + session.add(user) + session.commit() + + return redirect(url_for('.password_reset_complete')) + +@bp.route('/reset/done') +def password_reset_complete(): + return render_template('auth/password_reset_complete.html') + @bp.route('/source_doc_upload', methods=["POST"]) @show_errors def source_doc_upload():