data_annotation_platform/app/auth/routes.py (173 lines of code) (raw):
# -*- coding: utf-8 -*-
# Author: G.J.J. van den Burg <gvandenburg@turing.ac.uk>
# License: See LICENSE file
# Copyright: 2020 (c) The Alan Turing Institute
import datetime
import markdown
import textwrap
from flask import (
render_template,
flash,
redirect,
url_for,
current_app,
session,
request,
)
from flask_login import current_user, login_user, logout_user
from werkzeug.urls import url_parse
from app import db
from app.auth import bp
from app.auth.forms import (
LoginForm,
RegistrationForm,
ResetPasswordRequestForm,
ResetPasswordForm,
)
from app.auth.email import (
send_password_reset_email,
send_email_confirmation_email,
)
from app.models import User, Task, Annotation
LEGAL = markdown.markdown(
textwrap.dedent(
"""
## Terms and Conditions
The AnnotateChange application was created to construct a data set for
the analysis of change point detection algorithms. As a user, you will
be asked to annotate time series data. It is important that we can use
these annotations freely and publish them under a permissive license.
Therefore, we ask that you agree to the following terms and conditions.
1. Identifiable user data such as email address, password, and IP
address will not be shared by us with any third party, unless we are
required to do so by law. This information will only be used to provide
authentication to the application and verify that you have access to
the email address you provide us.
2. Annotations and any other information you provide us through use of
the application are provided to us under a worldwide, royalty-free,
non-exclusive, and perpetual license and can be made publically
available by us under a permissive license and used for whatever
purpose we see fit. In any publication of this annotation data users
will only be identified by a numeric ID, not by personal identifiable
information (such as email or IP address).
3. You agree that you will not revoke or seek invalidation of any
license that you have granted under these Terms and conditions for any
content you have provided us.
"""
)
)
def auto_logout():
# Automatically logout after a period of inactivity
# https://stackoverflow.com/a/40914886/1154005
session.permanent = True
current_app.permanent_session_lifetime = datetime.timedelta(days=1)
session.modified = True
@bp.route("/login", methods=("GET", "POST"))
def login():
form = LoginForm()
if form.validate_on_submit():
# log the user in if exists
user = User.query.filter_by(username=form.username.data).first()
if user is None or not user.check_password(form.password.data):
flash("Invalid username or password", "error")
return redirect(url_for("auth.login"))
login_user(user)
# record last_active time
current_user.last_active = datetime.datetime.utcnow()
db.session.commit()
# remove any assigned unfinished datasets, as these may no longer be
# needed
user_tasks = Task.query.filter_by(
annotator_id=current_user.id, done=False
).all()
for task in user_tasks:
if task.admin_assigned:
continue
anns = Annotation.query.filter_by(task_id=task.id).all()
if len(anns) > 0:
flash(
"Internal error, unfinished tasks has annotations!",
"error",
)
db.session.delete(task)
db.session.commit()
# redirect if not confirmed yet
if not user.is_confirmed:
return redirect(url_for("auth.not_confirmed"))
# Get the next page from the request (default to index)
next_page = request.args.get("next")
if not next_page or url_parse(next_page).netloc != "":
next_page = url_for("main.index")
# redirect if not introduced yet
if not user.is_introduced:
return redirect(url_for("main.index"))
return redirect(next_page)
return render_template("auth/login.html", title="Sign In", form=form)
@bp.route("/logout")
def logout():
logout_user()
return redirect(url_for("main.index"))
@bp.route("/register", methods=("GET", "POST"))
def register():
if not current_app.config['ACCEPTING_REGISTRATION']:
return render_template("auth/no_register.html")
if current_user.is_authenticated:
return redirect(url_for("main.index"))
form = RegistrationForm()
if form.validate_on_submit():
user = User(
username=form.username.data,
email=form.email.data,
fullname=form.fullname.data,
read_toc=form.toc.data,
wants_credit=form.credit.data,
wants_updates=form.updated.data,
max_tasks=current_app.config['TASKS_MAX_PER_USER'],
)
user.set_password(form.password.data)
db.session.add(user)
db.session.commit()
send_email_confirmation_email(user)
flash(
"An email has been sent to confirm your account, please check "
"your email (including the spam folder).",
"info",
)
return redirect(url_for("auth.login"))
return render_template(
"auth/register.html", title="Register", form=form, legal=LEGAL
)
@bp.route("/reset_password_request", methods=("GET", "POST"))
def reset_password_request():
if current_user.is_authenticated:
return redirect(url_for("main.index"))
form = ResetPasswordRequestForm()
if form.validate_on_submit():
user = User.query.filter_by(email=form.email.data).first()
if user:
send_password_reset_email(user)
flash(
"Please check your email for the instructions to reset your password.",
"info",
)
return redirect(url_for("auth.login"))
return render_template(
"auth/reset_password_request.html", title="Reset Password", form=form
)
@bp.route("/reset_password/<token>", methods=("GET", "POST"))
def reset_password(token):
if current_user.is_authenticated:
return redirect(url_for("main.index"))
user = User.verify_reset_password_token(token)
if not user:
return redirect(url_for("main.index"))
form = ResetPasswordForm()
if form.validate_on_submit():
user.set_password(form.password.data)
db.session.commit()
flash("Your password has been reset.", "info")
return redirect(url_for("auth.login"))
return render_template("auth/reset_password.html", form=form)
@bp.route("/confirm/<token>")
def confirm_email(token):
if current_user.is_authenticated and current_user.is_confirmed:
flash("Account is already confirmed.")
return redirect(url_for("main.index"))
user = User.verify_email_confirmation_token(token)
if not user:
flash("The confirmation link is invalid or has expired.", "error")
return redirect(url_for("main.index"))
if user.is_confirmed:
flash("Account is already confirmed, please login.", "success")
else:
user.is_confirmed = True
db.session.commit()
flash("Account confirmed successfully. Thank you!", "success")
return redirect(url_for("auth.login"))
return redirect(url_for("main.index"))
@bp.route("/not_confirmed")
def not_confirmed():
if current_user.is_anonymous:
flash("Please login before accessing this page.")
return redirect(url_for("auth.login"))
if current_user.is_confirmed:
flash("Account is already confirmed.")
return redirect(url_for("main.index"))
flash("Please confirm your account before moving on.", "info")
return render_template("auth/not_confirmed.html")
@bp.route("/resend")
def resend_confirmation():
if current_user.is_anonymous:
flash("Please login before accessing this page.")
return redirect(url_for("auth.login"))
if current_user.is_confirmed:
flash("Account is already confirmed.")
return redirect(url_for("main.index"))
send_email_confirmation_email(current_user)
email = current_user.email
flash("A new confirmation has been sent to %s." % email, "success")
return redirect(url_for("auth.not_confirmed"))