2020-05-10 01:36:14 +00:00
|
|
|
import functools
|
2020-05-10 04:45:20 +00:00
|
|
|
import re
|
2020-05-10 01:36:14 +00:00
|
|
|
|
|
|
|
from flask import Blueprint
|
2020-05-10 03:59:22 +00:00
|
|
|
from flask import flash
|
|
|
|
from flask import current_app
|
2020-05-10 01:36:14 +00:00
|
|
|
from flask import g
|
|
|
|
from flask import redirect
|
|
|
|
from flask import url_for
|
2020-05-10 03:59:22 +00:00
|
|
|
from flask import request
|
2020-05-10 01:36:14 +00:00
|
|
|
from flask import session
|
2020-05-10 03:59:22 +00:00
|
|
|
from flask import render_template
|
|
|
|
from flask_mail import Message
|
2020-05-10 04:32:13 +00:00
|
|
|
from werkzeug.exceptions import abort
|
2020-05-10 01:36:14 +00:00
|
|
|
|
|
|
|
from capsulflask.db import get_model
|
|
|
|
|
|
|
|
bp = Blueprint("auth", __name__, url_prefix="/auth")
|
|
|
|
|
|
|
|
def account_required(view):
|
2020-05-10 18:51:54 +00:00
|
|
|
"""View decorator that redirects non-logged-in users to the login page."""
|
2020-05-10 01:36:14 +00:00
|
|
|
|
|
|
|
@functools.wraps(view)
|
|
|
|
def wrapped_view(**kwargs):
|
|
|
|
if session.get("account") is None:
|
|
|
|
return redirect(url_for("auth.login"))
|
|
|
|
|
|
|
|
return view(**kwargs)
|
|
|
|
|
|
|
|
return wrapped_view
|
|
|
|
|
2020-05-10 03:59:22 +00:00
|
|
|
@bp.route("/login", methods=("GET", "POST"))
|
|
|
|
def login():
|
2020-05-10 01:36:14 +00:00
|
|
|
if request.method == "POST":
|
|
|
|
email = request.form["email"]
|
2020-05-11 20:13:20 +00:00
|
|
|
errors = list()
|
2020-05-10 01:36:14 +00:00
|
|
|
|
|
|
|
if not email:
|
2020-05-11 20:13:20 +00:00
|
|
|
errors.append("email is required")
|
2020-05-11 03:55:16 +00:00
|
|
|
elif len(email.strip()) < 6 or email.count('@') != 1 or email.count('.') == 0:
|
2020-05-11 20:13:20 +00:00
|
|
|
errors.append("enter a valid email address")
|
2020-05-10 01:36:14 +00:00
|
|
|
|
2020-05-11 20:13:20 +00:00
|
|
|
if len(errors) == 0:
|
2020-05-10 03:59:22 +00:00
|
|
|
token = get_model().login(email)
|
2020-05-10 18:51:54 +00:00
|
|
|
if token is None:
|
2020-05-11 20:13:20 +00:00
|
|
|
errors.append("too many logins. please use one of the existing login links that have been emailed to you")
|
2020-05-10 18:51:54 +00:00
|
|
|
else:
|
|
|
|
link = f"{current_app.config['BASE_URL']}/auth/magic/{token}"
|
|
|
|
|
|
|
|
current_app.config["FLASK_MAIL_INSTANCE"].send(
|
|
|
|
Message(
|
2020-05-10 23:59:30 +00:00
|
|
|
"Click This Link to Login to Capsul",
|
2020-05-12 05:02:55 +00:00
|
|
|
body=(f"Navigate to {link} to log into Capsul.\n"
|
|
|
|
"\nIf you didn't request this, ignore this message."),
|
2020-05-10 23:59:30 +00:00
|
|
|
recipients=[email]
|
2020-05-10 18:51:54 +00:00
|
|
|
)
|
2020-05-10 03:59:22 +00:00
|
|
|
)
|
|
|
|
|
2020-05-10 18:51:54 +00:00
|
|
|
return render_template("login-landing.html", email=email)
|
2020-05-10 01:36:14 +00:00
|
|
|
|
2020-05-11 20:13:20 +00:00
|
|
|
for error in errors:
|
|
|
|
flash(error)
|
2020-05-10 01:36:14 +00:00
|
|
|
|
2020-05-10 03:59:22 +00:00
|
|
|
return render_template("login.html")
|
|
|
|
|
2020-05-10 04:32:13 +00:00
|
|
|
@bp.route("/magic/<string:token>", methods=("GET", ))
|
|
|
|
def magiclink(token):
|
2020-05-11 06:47:14 +00:00
|
|
|
email = get_model().consume_token(token)
|
2020-05-10 04:32:13 +00:00
|
|
|
if email is not None:
|
|
|
|
session.clear()
|
|
|
|
session["account"] = email
|
2020-05-11 06:47:14 +00:00
|
|
|
return redirect(url_for("console.index"))
|
2020-05-10 04:32:13 +00:00
|
|
|
else:
|
2020-05-17 04:05:45 +00:00
|
|
|
# this is here to prevent xss
|
|
|
|
if not re.match(r"^[a-zA-Z0-9_-]+$", token):
|
|
|
|
token = '___________'
|
|
|
|
|
2020-05-10 04:32:13 +00:00
|
|
|
abort(404, f"Token {token} doesn't exist or has already been used.")
|
|
|
|
|
2020-05-10 03:59:22 +00:00
|
|
|
@bp.route("/logout")
|
|
|
|
def logout():
|
|
|
|
session.clear()
|
2020-05-12 05:02:55 +00:00
|
|
|
return redirect(url_for("index"))
|