IEEE.org     |     IEEE Xplore Digital Library     |     IEEE Standards     |     IEEE Spectrum     |     More Sites

Verified Commit 1ea6da7a authored by Emi Simpson's avatar Emi Simpson
Browse files

Added very basic direct auth implementation

parent b4f53f5d
from pymysql.err import IntegrityError
from werkzeug.exceptions import abort
from werkzeug.utils import redirect
from mystic.config import get_database
from flask.blueprints import Blueprint
from flask.helpers import flash
from flask.templating import render_template
from mystic.database import User
from typing import Optional, Tuple, cast
from pymysql.cursors import Cursor
from mystic.auth import AuthModule, update_user
from flask import session, request
from flask.app import Flask
from random import randint
bp = Blueprint("direct_login", __name__, url_prefix="/")
@bp.get("/login")
def login() -> str:
return render_template("login.html",
login=("Login", "#")
)
@bp.post("/login")
def post_login() -> str:
if 'email' in request.form:
# This is a signup
signup = True
required_fields = ['username', 'password', 'name', 'last_name', 'email']
else:
# This is a login
signup = False
required_fields = ['username', 'password']
missing_fields = [field for field in required_fields if field not in request.form]
if len(missing_fields) > 0:
flash(f"Missing Fields: {missing_fields}", "error-generic-generic")
return render_template("login.html",
login=("Login", "#")
)
if signup:
run_signup()
else:
run_login()
return render_template("login.html",
login=("Login", "#")
)
def run_signup() -> None:
username = request.form["username"].strip().lower()
name = request.form["name"].strip()
last_name = request.form["last_name"].strip()
password = request.form["password"].strip()
email = request.form["email"].strip().lower()
if check_fields_not_empty(
"signup",
("username", "What cool username do you want?", username),
("name", "What do you want to be called?", name),
("email", "What email do you want to use?", email),
("password", "Choose a safe password", password),
):
return
if '@' in username:
flash("Username's can't contain @s", "error-signup-username")
return
db = get_database()
c = db.cursor()
try:
uid = randint(0, 2**(8 * 4) -1)
User.create_user(
c,
uid,
username,
name,
last_name,
email,
)
db.commit()
except IntegrityError as e:
n_conflicts = c.execute(
'SELECT username, email FROM users WHERE username = %s OR email = %s;',
(request.form['username'].strip(), request.form['email'].strip())
)
results = cast(Optional[Tuple[Tuple[str, str], ...]], c.fetchall())
assert n_conflicts != 0, "Receieved integrity error, but can't find source: %s"%(e)
assert results is not None
if any(result[0] == request.form['username'] for result in results):
flash(f'That username has already been taken', 'error-signup-username')
if any(result[1] == request.form['email'] for result in results):
flash(f'That email is already in use', 'error-signup-email')
return
finally:
c.close()
session["id"] = uid
abort(redirect("/"))
def run_login() -> None:
username = request.form["username"].strip().lower()
password = request.form["password"].strip()
if check_fields_not_empty(
"signup",
("username", "Either a username or a password can be used to log in", username),
("password", "Enter your password", password),
):
return
db = get_database()
c = db.cursor()
if '@' in username:
lookup = User.lookup_user_by_email
else:
lookup = User.lookup_user
user = lookup(c, username)
if user is None:
flash(f'User not found', 'error-login-username')
c.close()
return
session["id"] = user.user_id
abort(redirect("/"))
def check_fields_not_empty(section: str, *fields: Tuple[str, str, str]) -> bool:
"""
Check that none of the provided fields are empty strings
Expects a section and a series of tuples, each of which contains three strings. The
first is the name of the field as it should be flashed to Jinja, the second is the
message that should be displayed to the user when the field is empty, and the third is
the message itself. The section parameter is similarly used to determine the category
of the jinja category, as described below
Flashes a message to Jinja for each empty field. The error message will be the exact
text as is associated with the field in the arguments, and the category of the error
will be "error-{section}-{field_name}".
Ultimately returns true if any messages were flashed, and false otherwise.
"""
error_found = False
for field_name, message, field in fields:
if len(field) == 0:
flash(message, f"error-{section}-{field_name}")
error_found = True
return error_found
class DirectAuth(AuthModule):
"""
Allow users to authenticate by username and password managed by the application
"""
def __init__(
self,
):
"""
Takes no parameters at the minute
"""
def setup(self, app: Flask) -> None:
app.register_blueprint(bp, url_prefix='/auth/')
def get_login_url(self) -> str:
#return self.service_provider.get_login_url()
return ""
def check_user(self, c: Cursor) -> Optional[User]:
if 'id' in session:
return User(cast(int, session['id']))
return None
......@@ -900,6 +900,25 @@ class User:
user._username = record[1]
return user
@staticmethod
def lookup_user_by_email(c: Cursor, email: str) -> Optional['User']:
"""
Attempt to fetch a user by their email
Returns a partially populated user if successful, or none if unsuccessful.
Raises:
sqlite3.OperationalError: The provided cursor points to a database
that was not properly set up
"""
c.execute('SELECT * FROM users WHERE email = %s;', (email,))
record = c.fetchone()
if record is None:
return None
user = User(record[0])
user._email = email
return user
@property
def alphaid(self) -> str:
"""
......
{% extends "base.html" %}
{% import "utils.html" as utils %}
{% block title %} Login {% endblock %}
{% block head %}
{{super()}}
<style>
#login {
display: grid;
grid-template-columns: repeat(2, 420px);
justify-content: center;
align-content: center;
gap: 120px;
height: 100%;
position: relative;
}
input {
margin-top: 4px;
margin-bottom: -4px;
}
.input-field {
margin-bottom: 30px;
}
#login form {
display: flex;
flex-direction: column;
border-radius: 6.9px;
width: 100%;
border: 1px solid #ddd;
box-shadow: 3px 3px 8px lightgrey;
padding: 37px;
}
</style>
{% endblock %}
{% block main %}
<div id=login>
<section>
<form method=POST>
<h2> Create Account </h2>
<div class="input-field">
<label>Username</label>
<input name="username" {{ utils.highlight_if_error("signup", "username") }}>
{{ utils.show_field_error("signup", "username") }}
</div>
<div class="input-field">
<label>Password</label>
<input name="password" type=password {{ utils.highlight_if_error("signup", "password") }}>
{{ utils.show_field_error("signup", "password") }}
</div>
<div class="input-field">
<label>First/Display Name</label>
<input name="name" {{ utils.highlight_if_error("signup", "name") }}>
{{ utils.show_field_error("signup", "name") }}
</div>
<div class="input-field">
<label>Last Name (Optional)</label>
<input name="last_name">
</div>
<div class="input-field">
<label>Email</label>
<input name="email" type=email {{ utils.highlight_if_error("signup", "email") }}>
{{ utils.show_field_error("signup", "email") }}
</div>
<div class="button-wrapper">
<button class=big>Sign Up</button>
</div>
</form>
</section>
<section>
<form method=POST>
<h2> Login </h2>
<div class="input-field">
<label>Username / Email</label>
<input name="username" {{ utils.highlight_if_error("login", "username") }}>
{{ utils.show_field_error("login", "username") }}
</div>
<div class="input-field">
<label>Password</label>
<input name="password" type=password {{ utils.highlight_if_error("login", "password") }}>
{{ utils.show_field_error("login", "password") }}
</div>
<div class="button-wrapper">
<button class=big>Login</button>
</div>
</form>
</section>
</div>
{% endblock %}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment