Files
workout/routes/auth.py
2026-01-31 14:53:01 +11:00

143 lines
4.7 KiB
Python

from flask import Blueprint, render_template, redirect, url_for, flash, request
from werkzeug.security import generate_password_hash, check_password_hash
from flask_login import login_user, login_required, logout_user
from forms.login import LoginForm
from forms.signup import SignupForm
from extensions import db
from utils import get_client_ip
auth = Blueprint('auth', __name__)
class Person:
"""
Simple Person class compatible with Flask-Login.
"""
def __init__(self, person_id, name, email, password_hash, is_admin=False):
self.id = person_id
self.name = name
self.email = email
self.password_hash = password_hash
self.is_admin = is_admin
def get_id(self):
"""Required by Flask-Login to get a unique user identifier."""
return str(self.id)
@property
def is_authenticated(self):
return True
@property
def is_active(self):
return True
@property
def is_anonymous(self):
return False
# -------------------------
# Database helper functions
# -------------------------
def get_person_by_id(person_id):
"""
Fetch a person record by person_id and return a Person object.
"""
sql = """
SELECT person_id, name, email, password_hash, is_admin
FROM person
WHERE person_id = %s
LIMIT 1
"""
row = db.execute(sql, [person_id], one=True)
if row:
return Person(row['person_id'], row['name'], row['email'], row['password_hash'], row['is_admin'])
return None
def get_person_by_email(email):
"""
Fetch a person record by email and return a Person object.
"""
sql = """
SELECT person_id, name, email, password_hash, is_admin
FROM person
WHERE email = %s
LIMIT 1
"""
row = db.execute(sql, [email], one=True)
if row:
return Person(row['person_id'], row['name'], row['email'], row['password_hash'], row['is_admin'])
return None
def create_person(name, email, password_hash):
"""
Insert a new person into the database; return the new person's ID.
"""
sql = """
INSERT INTO person (name, email, password_hash)
VALUES (%s, %s, %s)
RETURNING person_id AS person_id
"""
row = db.execute(sql, [name, email, password_hash], commit=True, one=True)
return row['person_id']
def record_login_attempt(email, success, person_id=None):
"""
Record a login attempt in the database.
"""
sql = """
INSERT INTO login_attempts (email, ip_address, success, user_agent, person_id)
VALUES (%s, %s, %s, %s, %s)
"""
db.execute(sql, [email, get_client_ip(), success, request.user_agent.string, person_id], commit=True)
# ---------------------
# Blueprint endpoints
# ---------------------
@auth.route('/signup', methods=['GET', 'POST'])
def signup():
form = SignupForm()
if form.validate_on_submit():
hashed_password = generate_password_hash(form.password.data)
new_person_id = create_person(
name=form.name.data,
email=form.email.data,
password_hash=hashed_password
)
db.activityRequest.log(new_person_id, 'SIGNUP', 'person', new_person_id, f"User signed up: {form.email.data}")
flash("Account created successfully. Please log in.", "success")
return redirect(url_for('auth.login'))
return render_template('auth/signup.html', form=form)
@auth.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
person = get_person_by_email(form.email.data)
if person and check_password_hash(person.password_hash, form.password.data):
login_user(person)
db.activityRequest.log(person.id, 'LOGIN_SUCCESS', 'person', person.id, f"User logged in: {form.email.data}")
flash("Logged in successfully.", "success")
return redirect(url_for('calendar.get_calendar', person_id=person.id))
else:
db.activityRequest.log(person.id if person else None, 'LOGIN_FAILURE', 'person', person.id if person else None, f"Failed login attempt for: {form.email.data}")
flash("Invalid email or password.", "danger")
return render_template('auth/login.html', form=form)
@auth.route('/logout')
@login_required
def logout():
person_id = current_user.id if current_user.is_authenticated else None
logout_user()
db.activityRequest.log(person_id, 'LOGOUT', 'person', person_id, "User logged out")
flash('You have been logged out.', 'success')
return redirect(url_for('auth.login'))