fine-tuning login
This commit is contained in:
parent
e6fcb847f0
commit
7fe0d9a9c5
23
README.md
23
README.md
@ -2,16 +2,6 @@
|
|||||||
|
|
||||||
Python Flask web application for capsul.org
|
Python Flask web application for capsul.org
|
||||||
|
|
||||||
## postgres database schema management
|
|
||||||
|
|
||||||
capsulflask has a concept of a schema version. When the application starts, it will query the database for a table named
|
|
||||||
`schemaversion` that has one row and one column (`version`). If the `version` it finds is not equal to the `desiredSchemaVersion` variable set in `db.py`, it will run migration scripts from the `schema_migrations` folder one by one until the `schemaversion` table shows the correct version.
|
|
||||||
|
|
||||||
For example, the script named `02_up_xyz.sql` should contain code that migrates the database from schema version 1 to schema version 2. Likewise, the script `02_down_xyz.sql` should contain code that migrates from schema version 2 back to schema version 1.
|
|
||||||
|
|
||||||
**IMPORTANT: if you need to make changes to the schema, make a NEW schema version. DO NOT EDIT the existing schema versions.**
|
|
||||||
|
|
||||||
In general, for safety, schema version upgrades should not delete data. Schema version downgrades will simply throw an error and exit for now.
|
|
||||||
|
|
||||||
## how to run locally
|
## how to run locally
|
||||||
|
|
||||||
@ -33,7 +23,7 @@ pip install -r requirements.txt
|
|||||||
Run an instance of Postgres (I used docker for this, you can use whatever you want, point is its listening on localhost:5432)
|
Run an instance of Postgres (I used docker for this, you can use whatever you want, point is its listening on localhost:5432)
|
||||||
|
|
||||||
```
|
```
|
||||||
docker run -it -e POSTGRES_PASSWORD=dev -p 5432:5432 postgres
|
docker run --rm -it -e POSTGRES_PASSWORD=dev -p 5432:5432 postgres
|
||||||
```
|
```
|
||||||
|
|
||||||
Run the app
|
Run the app
|
||||||
@ -41,3 +31,14 @@ Run the app
|
|||||||
```
|
```
|
||||||
FLASK_APP=capsulflask flask run
|
FLASK_APP=capsulflask flask run
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## postgres database schema management
|
||||||
|
|
||||||
|
capsulflask has a concept of a schema version. When the application starts, it will query the database for a table named
|
||||||
|
`schemaversion` that has one row and one column (`version`). If the `version` it finds is not equal to the `desiredSchemaVersion` variable set in `db.py`, it will run migration scripts from the `schema_migrations` folder one by one until the `schemaversion` table shows the correct version.
|
||||||
|
|
||||||
|
For example, the script named `02_up_xyz.sql` should contain code that migrates the database from schema version 1 to schema version 2. Likewise, the script `02_down_xyz.sql` should contain code that migrates from schema version 2 back to schema version 1.
|
||||||
|
|
||||||
|
**IMPORTANT: if you need to make changes to the schema, make a NEW schema version. DO NOT EDIT the existing schema versions.**
|
||||||
|
|
||||||
|
In general, for safety, schema version upgrades should not delete data. Schema version downgrades will simply throw an error and exit for now.
|
@ -18,7 +18,7 @@ from capsulflask.db import get_model
|
|||||||
bp = Blueprint("auth", __name__, url_prefix="/auth")
|
bp = Blueprint("auth", __name__, url_prefix="/auth")
|
||||||
|
|
||||||
def account_required(view):
|
def account_required(view):
|
||||||
"""View decorator that redirects anonymous users to the login page."""
|
"""View decorator that redirects non-logged-in users to the login page."""
|
||||||
|
|
||||||
@functools.wraps(view)
|
@functools.wraps(view)
|
||||||
def wrapped_view(**kwargs):
|
def wrapped_view(**kwargs):
|
||||||
@ -36,31 +36,34 @@ def login():
|
|||||||
error = None
|
error = None
|
||||||
|
|
||||||
if not email:
|
if not email:
|
||||||
error = "Email is required."
|
error = "email is required"
|
||||||
elif not re.match(r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)", email):
|
elif not re.match(r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)", email):
|
||||||
error = "Enter a valid email address."
|
error = "enter a valid email address"
|
||||||
|
|
||||||
|
|
||||||
if error is None:
|
if error is None:
|
||||||
token = get_model().login(email)
|
token = get_model().login(email)
|
||||||
|
if token is None:
|
||||||
|
error = "too many logins. please use one of the existing login links that have been emailed to you"
|
||||||
|
else:
|
||||||
|
link = f"{current_app.config['BASE_URL']}/auth/magic/{token}"
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
current_app.config["FLASK_MAIL_INSTANCE"].send(
|
If you didn't request this, ignore this message.
|
||||||
Message(
|
""",
|
||||||
"Click This Link to Login to Capsul",
|
html=f"""
|
||||||
body=f"""
|
<p>Navigate to <a href="{link}">{link}</a> to log into capsul.</p>
|
||||||
Navigate to {link} to log into capsul.
|
<p>If you didn't request this, ignore this message.</p>
|
||||||
""",
|
""",
|
||||||
html=f"""
|
recipients=[email]
|
||||||
Navigate to <a href="{link}">{link}</a> to log into capsul.
|
)
|
||||||
""",
|
|
||||||
sender=current_app.config['MAIL_DEFAULT_SENDER'],
|
|
||||||
recipients=[email]
|
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
|
||||||
return render_template("check-your-email.html")
|
return render_template("login-landing.html", email=email)
|
||||||
|
|
||||||
flash(error)
|
flash(error)
|
||||||
|
|
||||||
|
@ -12,6 +12,10 @@ class Model:
|
|||||||
if len(self.cursor.fetchall()) == 0:
|
if len(self.cursor.fetchall()) == 0:
|
||||||
self.cursor.execute("INSERT INTO accounts (email) VALUES (%s)", (email, ))
|
self.cursor.execute("INSERT INTO accounts (email) VALUES (%s)", (email, ))
|
||||||
|
|
||||||
|
self.cursor.execute("SELECT token FROM logintokens WHERE email = %s", (email, ))
|
||||||
|
if len(self.cursor.fetchall()) > 2:
|
||||||
|
return None
|
||||||
|
|
||||||
token = generate()
|
token = generate()
|
||||||
self.cursor.execute("INSERT INTO logintokens (email, token) VALUES (%s, %s)", (email, token))
|
self.cursor.execute("INSERT INTO logintokens (email, token) VALUES (%s, %s)", (email, token))
|
||||||
self.connection.commit()
|
self.connection.commit()
|
||||||
@ -22,7 +26,8 @@ class Model:
|
|||||||
self.cursor.execute("SELECT email FROM logintokens WHERE token = %s", (token, ))
|
self.cursor.execute("SELECT email FROM logintokens WHERE token = %s", (token, ))
|
||||||
rows = self.cursor.fetchall()
|
rows = self.cursor.fetchall()
|
||||||
if len(rows) > 0:
|
if len(rows) > 0:
|
||||||
self.cursor.execute("DELETE FROM logintokens WHERE token = %s", (token, ))
|
email = rows[0][0]
|
||||||
|
self.cursor.execute("DELETE FROM logintokens WHERE email = %s", (email, ))
|
||||||
self.connection.commit()
|
self.connection.commit()
|
||||||
return rows[0][0]
|
return email
|
||||||
return None
|
return None
|
@ -15,7 +15,7 @@ CREATE TABLE vms (
|
|||||||
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 DEFAULT NOW(),
|
created TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||||
dollars INTEGER NOT NULL,
|
dollars NUMERIC(8, 2) NOT NULL,
|
||||||
PRIMARY KEY (email, created)
|
PRIMARY KEY (email, created)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<title>{% block title %}{% endblock %}</title>
|
<title>{% block title %}{% endblock %}{% if self.title() %} - {% endif %}capsul</title>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<link rel="shortcut icon" href="/favicon.ico" />
|
<link rel="shortcut icon" href="/favicon.ico" />
|
||||||
<link rel="apple-touch-icon" href="/icon.png" />
|
<link rel="apple-touch-icon" href="/icon.png" />
|
||||||
@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
<div class="float-right">
|
<div class="float-right">
|
||||||
{% if session["account"] %}
|
{% if session["account"] %}
|
||||||
<span>{{ session["account"] }}</span>
|
<span>Logged in as {{ session["account"] }}</span>
|
||||||
<a href="{{ url_for('auth.logout') }}">Log Out</a>
|
<a href="{{ url_for('auth.logout') }}">Log Out</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="{{ url_for('auth.login') }}">Log In</a>
|
<a href="{{ url_for('auth.login') }}">Log In</a>
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
Check Your Email My Dude
|
|
||||||
{% endblock %}
|
|
7
capsulflask/templates/login-landing.html
Normal file
7
capsulflask/templates/login-landing.html
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}check your email{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
check your email. a login link has been sent to {{ email }}
|
||||||
|
{% endblock %}
|
@ -1,5 +1,6 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}login{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<form method="post">
|
<form method="post">
|
||||||
|
Loading…
Reference in New Issue
Block a user