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 import Flask
|
||||||
|
from flask_mail import Mail
|
||||||
|
from flask import render_template
|
||||||
import os
|
import os
|
||||||
|
|
||||||
def create_app():
|
def create_app():
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.config.from_mapping(
|
app.config.from_mapping(
|
||||||
|
BASE_URL=os.environ.get("BASE_URL", default="http://localhost:5000"),
|
||||||
SECRET_KEY=os.environ.get("SECRET_KEY", default="dev"),
|
SECRET_KEY=os.environ.get("SECRET_KEY", default="dev"),
|
||||||
DATABASE_URL=os.environ.get("DATABASE_URL", default="sql://postgres:dev@localhost:5432/postgres"),
|
DATABASE_URL=os.environ.get("DATABASE_URL", default="sql://postgres:dev@localhost:5432/postgres"),
|
||||||
DATABASE_SCHEMA=os.environ.get("DATABASE_SCHEMA", default="public"),
|
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
|
from capsulflask import db
|
||||||
|
|
||||||
db.init_app(app)
|
db.init_app(app)
|
||||||
|
|
||||||
# from capsulflask import auth, blog
|
@app.route("/")
|
||||||
|
def index():
|
||||||
|
return render_template("index.html")
|
||||||
|
|
||||||
# app.register_blueprint(auth.bp)
|
from capsulflask import auth
|
||||||
# app.register_blueprint(blog.bp)
|
|
||||||
|
app.register_blueprint(auth.bp)
|
||||||
|
|
||||||
app.add_url_rule("/", endpoint="index")
|
app.add_url_rule("/", endpoint="index")
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
@ -1,10 +1,15 @@
|
|||||||
import functools
|
import functools
|
||||||
|
|
||||||
from flask import Blueprint
|
from flask import Blueprint
|
||||||
|
from flask import flash
|
||||||
|
from flask import current_app
|
||||||
from flask import g
|
from flask import g
|
||||||
from flask import redirect
|
from flask import redirect
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
|
from flask import request
|
||||||
from flask import session
|
from flask import session
|
||||||
|
from flask import render_template
|
||||||
|
from flask_mail import Message
|
||||||
|
|
||||||
from capsulflask.db import get_model
|
from capsulflask.db import get_model
|
||||||
|
|
||||||
@ -22,32 +27,41 @@ def account_required(view):
|
|||||||
|
|
||||||
return wrapped_view
|
return wrapped_view
|
||||||
|
|
||||||
|
@bp.route("/login", methods=("GET", "POST"))
|
||||||
@bp.route("/register", methods=("GET", "POST"))
|
def login():
|
||||||
def register():
|
|
||||||
|
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
email = request.form["email"]
|
email = request.form["email"]
|
||||||
model = get_model()
|
|
||||||
error = None
|
error = None
|
||||||
|
|
||||||
if not email:
|
if not email:
|
||||||
error = "Email is required."
|
error = "Email is required."
|
||||||
elif (
|
|
||||||
model.
|
|
||||||
):
|
|
||||||
error = f"User {username} is already registered."
|
|
||||||
|
|
||||||
if error is None:
|
if error is None:
|
||||||
# the name is available, store it in the database and go to
|
token = get_model().login(email)
|
||||||
# the login page
|
|
||||||
db.execute(
|
link = f"{current_app.config['BASE_URL']}/auth/magic/{token}"
|
||||||
"INSERT INTO user (username, password) VALUES (?, ?)",
|
|
||||||
(username, generate_password_hash(password)),
|
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)
|
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:
|
class Model:
|
||||||
def __init__(self, connection, cursor):
|
def __init__(self, connection, cursor):
|
||||||
self.connection = connection
|
self.connection = connection
|
||||||
self.cursor = cursor
|
self.cursor = cursor
|
||||||
|
|
||||||
def emailExists(self, email):
|
def login(self, email):
|
||||||
self.cursor.execute("SELECT * FROM accounts WHERE email = %(email)s", {"email": email})
|
self.cursor.execute("SELECT * FROM accounts WHERE email = %s", (email, ))
|
||||||
return len(self.cursor.fetchall()) > 0
|
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 (
|
CREATE TABLE accounts (
|
||||||
email TEXT PRIMARY KEY NOT NULL,
|
email TEXT PRIMARY KEY NOT NULL,
|
||||||
|
created TIMESTAMP NOT NULL DEFAULT NOW()
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE vms (
|
CREATE TABLE vms (
|
||||||
id TEXT PRIMARY KEY NOT NULL,
|
id TEXT PRIMARY KEY NOT NULL,
|
||||||
email TEXT REFERENCES accounts(email) ON DELETE RESTRICT,
|
email TEXT REFERENCES accounts(email) ON DELETE RESTRICT,
|
||||||
created TIMESTAMP NOT NULL,
|
created TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||||
deleted TIMESTAMP NOT NULL,
|
deleted TIMESTAMP NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE payments (
|
CREATE TABLE payments (
|
||||||
email TEXT REFERENCES accounts(email) ON DELETE RESTRICT,
|
email TEXT REFERENCES accounts(email) ON DELETE RESTRICT,
|
||||||
created TIMESTAMP NOT NULL,
|
created TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||||
dollars INTEGER NOT NULL,
|
dollars INTEGER NOT NULL,
|
||||||
PRIMARY KEY (email, created)
|
PRIMARY KEY (email, created)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE logintokens (
|
CREATE TABLE logintokens (
|
||||||
email TEXT REFERENCES accounts(email) ON DELETE RESTRICT,
|
email TEXT REFERENCES accounts(email) ON DELETE RESTRICT,
|
||||||
created TIMESTAMP NOT NULL,
|
created TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||||
token TEXT NOT NULL,
|
token TEXT NOT NULL,
|
||||||
PRIMARY KEY (email, created)
|
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
|
astroid==2.4.1
|
||||||
|
blinker==1.4
|
||||||
click==7.1.2
|
click==7.1.2
|
||||||
Flask==1.1.2
|
Flask==1.1.2
|
||||||
|
Flask-Mail==0.9.1
|
||||||
isort==4.3.21
|
isort==4.3.21
|
||||||
itsdangerous==1.1.0
|
itsdangerous==1.1.0
|
||||||
Jinja2==2.11.2
|
Jinja2==2.11.2
|
||||||
lazy-object-proxy==1.4.3
|
lazy-object-proxy==1.4.3
|
||||||
MarkupSafe==1.1.1
|
MarkupSafe==1.1.1
|
||||||
mccabe==0.6.1
|
mccabe==0.6.1
|
||||||
|
nanoid==2.0.0
|
||||||
pkg-resources==0.0.0
|
pkg-resources==0.0.0
|
||||||
psycopg2==2.8.5
|
psycopg2==2.8.5
|
||||||
pylint==2.5.2
|
pylint==2.5.2
|
||||||
|
Loading…
Reference in New Issue
Block a user