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) }}
+
+
+
+
{% 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():