aboutsummaryrefslogtreecommitdiff
path: root/app/auth
diff options
context:
space:
mode:
Diffstat (limited to 'app/auth')
-rw-r--r--app/auth/__init__.py7
-rw-r--r--app/auth/email.py22
-rw-r--r--app/auth/forms.py52
-rw-r--r--app/auth/routes.py93
4 files changed, 174 insertions, 0 deletions
diff --git a/app/auth/__init__.py b/app/auth/__init__.py
new file mode 100644
index 0000000..c938693
--- /dev/null
+++ b/app/auth/__init__.py
@@ -0,0 +1,7 @@
+# -*- coding: utf-8 -*-
+
+from flask import Blueprint
+
+bp = Blueprint('auth', __name__)
+
+from app.auth import routes
diff --git a/app/auth/email.py b/app/auth/email.py
new file mode 100644
index 0000000..9f6f9d0
--- /dev/null
+++ b/app/auth/email.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+
+from threading import Thread
+
+from flask import current_app, render_template
+
+from app import mail
+
+
+def send_password_reset_email(user):
+ token = user.get_reset_password_token()
+ send_email(
+ "[AnnotateChange] Reset your password",
+ sender=current_app.config["ADMINS"][0],
+ recipients=[user.email],
+ text_body=render_template(
+ "email/reset_password.txt", user=user, token=token
+ ),
+ html_body=render_template(
+ "email/reset_password.html", user=user, token=token
+ ),
+ )
diff --git a/app/auth/forms.py b/app/auth/forms.py
new file mode 100644
index 0000000..8f7662a
--- /dev/null
+++ b/app/auth/forms.py
@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+
+from flask_wtf import FlaskForm
+
+from wtforms import StringField, PasswordField, BooleanField, SubmitField
+from wtforms.validators import DataRequired, ValidationError, Email, EqualTo
+
+from app.models import User
+
+
+class LoginForm(FlaskForm):
+ username = StringField("Username", validators=[DataRequired()])
+ password = PasswordField("Password", validators=[DataRequired()])
+ remember_me = BooleanField("Remember Me")
+ submit = SubmitField("Sign In")
+
+
+class RegistrationForm(FlaskForm):
+ username = StringField("Username", validators=[DataRequired()])
+ email = StringField("Email", validators=[DataRequired(), Email()])
+ password = PasswordField("Password", validators=[DataRequired()])
+ password2 = PasswordField(
+ "Repeat Password", validators=[DataRequired(), EqualTo("password")]
+ )
+ submit = SubmitField("Register")
+
+ def validate_username(self, username):
+ user = User.query.filter_by(username=username.data).first()
+ if user is not None:
+ raise ValidationError(
+ "Username already in use, please use a different one."
+ )
+
+ def validate_email(self, email):
+ user = User.query.filter_by(email=email.data).first()
+ if user is not None:
+ raise ValidationError(
+ "Email address already in use, please use a different one."
+ )
+
+
+class ResetPasswordRequestForm(FlaskForm):
+ email = StringField("Email", validators=[DataRequired(), Email()])
+ submit = SubmitField("Request password reset")
+
+
+class ResetPasswordForm(FlaskForm):
+ password = PasswordField("Password", validators=[DataRequired()])
+ password2 = PasswordField(
+ "Repeat Password", validators=[DataRequired(), EqualTo("password")]
+ )
+ submit = SubmitField("Request Password Reset")
diff --git a/app/auth/routes.py b/app/auth/routes.py
new file mode 100644
index 0000000..88ff7a0
--- /dev/null
+++ b/app/auth/routes.py
@@ -0,0 +1,93 @@
+# -*- coding: utf-8 -*-
+
+import datetime
+
+from flask import render_template, flash, redirect, url_for, request
+from flask_login import current_user, login_user, logout_user, login_required
+
+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.models import User
+from app.auth.email import send_password_reset_email
+
+
+@bp.route("/login", methods=("GET", "POST"))
+def login():
+ if current_user.is_authenticated:
+ current_user.last_active = datetime.datetime.utcnow()
+ db.session.commit()
+ return redirect(url_for("main.index"))
+ form = LoginForm()
+ if form.validate_on_submit():
+ 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", category="error")
+ return redirect(url_for("auth.login"))
+ login_user(user, remember=form.remember_me.data)
+ next_page = request.args.get("next")
+ if not next_page or url_parse(next_page).netloc != "":
+ next_page = 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 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)
+ user.set_password(form.password.data)
+ db.session.add(user)
+ db.session.commit()
+ flash("Thank you, you are now a registered user!")
+ return redirect(url_for("auth.login"))
+ return render_template("auth/register.html", title="Register", form=form)
+
+
+@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("Check your email for the instructions to reset your password.")
+ 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.")
+ return redirect(url_for("auth.login"))
+ return render_template("auth/reset_password.html", form=form)