it sends a magic link when you log in

This commit is contained in:
forest 2020-05-09 22:59:22 -05:00
parent edb476ec41
commit 64bca1fd97
10 changed files with 137 additions and 28 deletions

View File

@ -1,24 +1,40 @@
from flask import Flask
from flask_mail import Mail
from flask import render_template
import os
def create_app():
app = Flask(__name__)
app.config.from_mapping(
BASE_URL=os.environ.get("BASE_URL", default="http://localhost:5000"),
SECRET_KEY=os.environ.get("SECRET_KEY", default="dev"),
DATABASE_URL=os.environ.get("DATABASE_URL", default="sql://postgres:dev@localhost:5432/postgres"),
DATABASE_SCHEMA=os.environ.get("DATABASE_SCHEMA", default="public"),
MAIL_SERVER=os.environ.get("MAIL_SERVER", default="m1.nullhex.com"),
MAIL_PORT=os.environ.get("MAIL_PORT", default="587"),
MAIL_USE_TLS=os.environ.get("MAIL_USE_TLS", default="True").lower() in ['true', '1', 't', 'y', 'yes'],
MAIL_USERNAME=os.environ.get("MAIL_USERNAME", default="forest@nullhex.com"),
MAIL_PASSWORD=os.environ.get("MAIL_PASSWORD", default=""),
MAIL_DEFAULT_SENDER=os.environ.get("MAIL_DEFAULT_SENDER", default="forest@nullhex.com"),
)
app.config['FLASK_MAIL_INSTANCE'] = Mail(app)
from capsulflask import db
db.init_app(app)
# from capsulflask import auth, blog
@app.route("/")
def index():
return render_template("index.html")
# app.register_blueprint(auth.bp)
# app.register_blueprint(blog.bp)
from capsulflask import auth
app.register_blueprint(auth.bp)
app.add_url_rule("/", endpoint="index")
return app
return app

View File

@ -1,10 +1,15 @@
import functools
from flask import Blueprint
from flask import flash
from flask import current_app
from flask import g
from flask import redirect
from flask import url_for
from flask import request
from flask import session
from flask import render_template
from flask_mail import Message
from capsulflask.db import get_model
@ -22,32 +27,41 @@ def account_required(view):
return wrapped_view
@bp.route("/register", methods=("GET", "POST"))
def register():
@bp.route("/login", methods=("GET", "POST"))
def login():
if request.method == "POST":
email = request.form["email"]
model = get_model()
error = None
if not email:
error = "Email is required."
elif (
model.
):
error = f"User {username} is already registered."
if error is None:
# the name is available, store it in the database and go to
# the login page
db.execute(
"INSERT INTO user (username, password) VALUES (?, ?)",
(username, generate_password_hash(password)),
token = get_model().login(email)
link = f"{current_app.config['BASE_URL']}/auth/magic/{token}"
current_app.config["FLASK_MAIL_INSTANCE"].send(
Message(
"Click This Link to Login to Capsul",
body=f"""
Navigate to {link} to log into capsul.
""",
html=f"""
Navigate to <a href="{link}">{link}</a> to log into capsul.
""",
sender=current_app.config['MAIL_DEFAULT_SENDER'],
recipients=[email]
)
)
db.commit()
return redirect(url_for("auth.login"))
return render_template("check-your-email.html")
flash(error)
return render_template("auth/register.html")
return render_template("login.html")
@bp.route("/logout")
def logout():
session.clear()
return redirect(url_for("index"))

View File

@ -1,10 +1,21 @@
from nanoid import generate
class Model:
def __init__(self, connection, cursor):
self.connection = connection
self.cursor = cursor
def emailExists(self, email):
self.cursor.execute("SELECT * FROM accounts WHERE email = %(email)s", {"email": email})
return len(self.cursor.fetchall()) > 0
def login(self, email):
self.cursor.execute("SELECT * FROM accounts WHERE email = %s", (email, ))
if len(self.cursor.fetchall()) == 0:
self.cursor.execute("INSERT INTO accounts (email) VALUES (%s)", (email, ))
token = generate()
self.cursor.execute("INSERT INTO logintokens (email, token) VALUES (%s, %s)", (email, token))
self.connection.commit()
return token

View File

@ -2,25 +2,26 @@
CREATE TABLE accounts (
email TEXT PRIMARY KEY NOT NULL,
created TIMESTAMP NOT NULL DEFAULT NOW()
);
CREATE TABLE vms (
id TEXT PRIMARY KEY NOT NULL,
email TEXT REFERENCES accounts(email) ON DELETE RESTRICT,
created TIMESTAMP NOT NULL,
deleted TIMESTAMP NOT NULL,
created TIMESTAMP NOT NULL DEFAULT NOW(),
deleted TIMESTAMP NOT NULL
);
CREATE TABLE payments (
email TEXT REFERENCES accounts(email) ON DELETE RESTRICT,
created TIMESTAMP NOT NULL,
created TIMESTAMP NOT NULL DEFAULT NOW(),
dollars INTEGER NOT NULL,
PRIMARY KEY (email, created)
);
CREATE TABLE logintokens (
email TEXT REFERENCES accounts(email) ON DELETE RESTRICT,
created TIMESTAMP NOT NULL,
created TIMESTAMP NOT NULL DEFAULT NOW(),
token TEXT NOT NULL,
PRIMARY KEY (email, created)
);

View File

@ -0,0 +1,4 @@
.float-right {
display: inline-block;
float: right;
}

View File

@ -0,0 +1,38 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>{% block title %}{% endblock %}</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="/favicon.ico" />
<link rel="apple-touch-icon" href="/icon.png" />
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
<header>
<div class="float-right">
{% if g.user %}
<span>{{ g.user['username'] }}</span>
<a href="{{ url_for('auth.logout') }}">Log Out</a>
{% else %}
<a href="{{ url_for('auth.login') }}">Log In</a>
{% endif %}
</div>
<h1>
capsul
</h1>
</header>
<main>
{% for message in get_flashed_messages() %}
<div class="flash">{{ message }}</div>
{% endfor %}
{% block content %}{% endblock %}
</main>
<footer>
</footer>
</body>
</html>

View File

@ -0,0 +1,6 @@
{% extends 'base.html' %}
{% block content %}
Check Your Email My Dude
{% endblock %}

View File

@ -0,0 +1,6 @@
{% extends 'base.html' %}
{% block content %}
Index Page!
{% endblock %}

View File

@ -0,0 +1,10 @@
{% extends 'base.html' %}
{% block content %}
<form method="post">
<label for="email">Email Address</label>
<input name="email" id="email" required>
<input type="submit" value="Log In">
</form>
{% endblock %}

View File

@ -1,12 +1,15 @@
astroid==2.4.1
blinker==1.4
click==7.1.2
Flask==1.1.2
Flask-Mail==0.9.1
isort==4.3.21
itsdangerous==1.1.0
Jinja2==2.11.2
lazy-object-proxy==1.4.3
MarkupSafe==1.1.1
mccabe==0.6.1
nanoid==2.0.0
pkg-resources==0.0.0
psycopg2==2.8.5
pylint==2.5.2