it sends a magic link when you log in
This commit is contained in:
parent
edb476ec41
commit
64bca1fd97
@ -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
|
||||
|
||||
|
@ -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"))
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
);
|
||||
|
4
capsulflask/static/style.css
Normal file
4
capsulflask/static/style.css
Normal file
@ -0,0 +1,4 @@
|
||||
.float-right {
|
||||
display: inline-block;
|
||||
float: right;
|
||||
}
|
38
capsulflask/templates/base.html
Normal file
38
capsulflask/templates/base.html
Normal 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>
|
6
capsulflask/templates/check-your-email.html
Normal file
6
capsulflask/templates/check-your-email.html
Normal file
@ -0,0 +1,6 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
Check Your Email My Dude
|
||||
{% endblock %}
|
6
capsulflask/templates/index.html
Normal file
6
capsulflask/templates/index.html
Normal file
@ -0,0 +1,6 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
Index Page!
|
||||
{% endblock %}
|
10
capsulflask/templates/login.html
Normal file
10
capsulflask/templates/login.html
Normal 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 %}
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user