diff --git a/capsulflask/__init__.py b/capsulflask/__init__.py index 27f2de9..cac2cfa 100644 --- a/capsulflask/__init__.py +++ b/capsulflask/__init__.py @@ -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 \ No newline at end of file + return app + diff --git a/capsulflask/auth.py b/capsulflask/auth.py index 30cfd8f..306f8dd 100644 --- a/capsulflask/auth.py +++ b/capsulflask/auth.py @@ -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 {link} 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")) \ No newline at end of file diff --git a/capsulflask/model.py b/capsulflask/model.py index af66f5f..41526cd 100644 --- a/capsulflask/model.py +++ b/capsulflask/model.py @@ -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 \ No newline at end of file + 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 + diff --git a/capsulflask/schema_migrations/02_up_accounts_vms_etc.sql b/capsulflask/schema_migrations/02_up_accounts_vms_etc.sql index d3da2bf..6a6c5db 100644 --- a/capsulflask/schema_migrations/02_up_accounts_vms_etc.sql +++ b/capsulflask/schema_migrations/02_up_accounts_vms_etc.sql @@ -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) ); diff --git a/capsulflask/static/style.css b/capsulflask/static/style.css new file mode 100644 index 0000000..536c73f --- /dev/null +++ b/capsulflask/static/style.css @@ -0,0 +1,4 @@ +.float-right { + display: inline-block; + float: right; +} \ No newline at end of file diff --git a/capsulflask/templates/base.html b/capsulflask/templates/base.html new file mode 100644 index 0000000..6ffa507 --- /dev/null +++ b/capsulflask/templates/base.html @@ -0,0 +1,38 @@ + + +
+ + +