From 19d855fb89e3cdcab7bb77a57ae2e7d51a5ed53f Mon Sep 17 00:00:00 2001 From: Peter Stockings Date: Wed, 23 Jul 2025 21:46:13 +1000 Subject: [PATCH] Move auth logic to blueprint --- app.py | 91 +++------------------------------------- extensions.py | 2 + routes/auth.py | 83 ++++++++++++++++++++++++++++++++++++ templates/dashboard.html | 2 +- templates/login.html | 2 +- templates/signup.html | 2 +- 6 files changed, 94 insertions(+), 88 deletions(-) create mode 100644 routes/auth.py diff --git a/app.py b/app.py index 6547538..cec2c31 100644 --- a/app.py +++ b/app.py @@ -4,9 +4,9 @@ from flask import Flask, Response, jsonify, redirect, render_template, render_te import jinja_partials from jinja2_fragments import render_block import requests -from extensions import db, htmx, init_app +from extensions import db, htmx, init_app, login_manager from services import create_http_function_view_model, create_http_functions_view_model -from flask_login import LoginManager, UserMixin, current_user, login_required, login_user, logout_user +from flask_login import current_user, login_required from werkzeug.security import check_password_hash, generate_password_hash import os from dotenv import load_dotenv @@ -15,6 +15,7 @@ from routes.test import test from routes.home import home from routes.http import http from routes.llm import llm +from routes.auth import auth from flask_apscheduler import APScheduler import asyncio import aiohttp @@ -28,9 +29,8 @@ app = Flask(__name__) app.config.from_pyfile('config.py') app.secret_key = os.environ.get('SECRET_KEY', '2a661781919643cb8a5a8bc57642d99f') -login_manager = LoginManager() login_manager.init_app(app) -login_manager.login_view = "login" +login_manager.login_view = "auth.login" jinja_partials.register_extensions(app) # Remove scheduler configuration and initialization @@ -41,21 +41,7 @@ app.register_blueprint(test, url_prefix='/test') app.register_blueprint(home, url_prefix='/home') app.register_blueprint(http, url_prefix='/http') app.register_blueprint(llm, url_prefix='/llm') - -class User(UserMixin): - def __init__(self, id, username, password_hash, created_at): - self.id = id - self.username = username - self.password_hash = password_hash - self.created_at = created_at - - @staticmethod - def get(user_id): - user_data = db.get_user(int(user_id)) - - if user_data: - return User(id=str(user_data['id']), username=user_data['username'], password_hash=user_data['password_hash'], created_at=user_data['created_at']) - return None +app.register_blueprint(auth, url_prefix='/auth') # Swith to inter app routing, which results in speed up from ~400ms to ~270ms # https://stackoverflow.com/questions/76886643/linking-two-not-exposed-dokku-apps @@ -186,7 +172,7 @@ def execute_http_function(user_id, function): # Check if the function is public, if not check if the user is authenticated and owns the function if not is_public: if not current_user.is_authenticated: - return login_manager.unauthorized() + return redirect(url_for('auth.login', next=request.url)) if int(current_user.id) != user_id: return jsonify({'error': 'Function belongs to another user'}), 404 @@ -244,71 +230,6 @@ def execute_http_function(user_id, function): except Exception as e: return jsonify({'error': str(e)}), 500 -@app.route('/login', methods=['GET', 'POST']) -def login(): - if request.method == 'GET': - return render_template("login.html") - - username = request.form.get('username') - password = request.form.get('password') - if not username or not password: - return render_template("login.html", error="Both username and password must be entered") - - user_data = db.get_user_by_username(username) - if not user_data: - return render_template("login.html", error="User does not exist") - - if not check_password_hash(user_data['password_hash'], password): - return render_template("login.html", error="Invalid username or password") - - user = User(id=str(user_data['id']), username=user_data['username'], password_hash=user_data['password_hash'], created_at=user_data['created_at']) - - # user should be an instance of your `User` class - login_user(user) - #flask.flash('Logged in successfully.') - - next = request.args.get('next') - return redirect(next or url_for('home.index')) - -@app.route('/signup', methods=['GET', 'POST']) -def signup(): - if request.method == 'GET': - return render_template("signup.html") - - username = request.form.get('username') - password = request.form.get('password') - if not username or not password: - return render_template("signup.html", error="Both username and password must be entered") - - if len(username) < 10 or len(password) < 10: - return render_template("signup.html", error="Both username and password must be at least 10 characters long") - - user = db.get_user_by_username(username) - if user: - return render_template("signup.html", error="User already exists") - - hashed_password = generate_password_hash(password) - user_data = db.create_new_user(username, hashed_password) - - user = User(id=str(user_data['id']), username=user_data['username'], password_hash=user_data['password_hash'], created_at=user_data['created_at']) - login_user(user) - - return redirect(url_for('home.index')) - -@app.route("/logout") -@login_required -def logout(): - logout_user() - return redirect(url_for('landing_page')) - - - -@login_manager.user_loader -def load_user(user_id): - user_data = db.get_user(int(user_id)) - if user_data: - return User(id=str(user_data['id']), username=user_data['username'], password_hash=user_data['password_hash'], created_at=user_data['created_at']) - return None if __name__ == '__main__': # Bind to PORT if defined, otherwise default to 5000. diff --git a/extensions.py b/extensions.py index 71cb516..a910c31 100644 --- a/extensions.py +++ b/extensions.py @@ -2,9 +2,11 @@ from jinja2 import Environment, FileSystemLoader, select_autoescape from db import DataBase from flask_htmx import HTMX from flask import url_for +from flask_login import LoginManager db = DataBase() htmx = HTMX() +login_manager = LoginManager() environment = Environment( loader=FileSystemLoader("templates"), diff --git a/routes/auth.py b/routes/auth.py new file mode 100644 index 0000000..4065ada --- /dev/null +++ b/routes/auth.py @@ -0,0 +1,83 @@ +from flask import Blueprint, render_template, request, redirect, url_for +from flask_login import login_user, logout_user, login_required, UserMixin +from werkzeug.security import generate_password_hash, check_password_hash +from extensions import db, login_manager + +auth = Blueprint('auth', __name__) + +class User(UserMixin): + def __init__(self, id, username, password_hash, created_at): + self.id = id + self.username = username + self.password_hash = password_hash + self.created_at = created_at + + @staticmethod + def get(user_id): + user_data = db.get_user(int(user_id)) + + if user_data: + return User(id=str(user_data['id']), username=user_data['username'], password_hash=user_data['password_hash'], created_at=user_data['created_at']) + return None + +@login_manager.user_loader +def load_user(user_id): + user_data = db.get_user(int(user_id)) + if user_data: + return User(id=str(user_data['id']), username=user_data['username'], password_hash=user_data['password_hash'], created_at=user_data['created_at']) + return None + +@auth.route('/login', methods=['GET', 'POST']) +def login(): + if request.method == 'GET': + return render_template("login.html") + + username = request.form.get('username') + password = request.form.get('password') + if not username or not password: + return render_template("login.html", error="Both username and password must be entered") + + user_data = db.get_user_by_username(username) + if not user_data: + return render_template("login.html", error="User does not exist") + + if not check_password_hash(user_data['password_hash'], password): + return render_template("login.html", error="Invalid username or password") + + user = User(id=str(user_data['id']), username=user_data['username'], password_hash=user_data['password_hash'], created_at=user_data['created_at']) + + login_user(user) + + next = request.args.get('next') + return redirect(next or url_for('home.index')) + +@auth.route('/signup', methods=['GET', 'POST']) +def signup(): + if request.method == 'GET': + return render_template("signup.html") + + username = request.form.get('username') + password = request.form.get('password') + if not username or not password: + return render_template("signup.html", error="Both username and password must be entered") + + if len(username) < 10 or len(password) < 10: + return render_template("signup.html", error="Both username and password must be at least 10 characters long") + + user = db.get_user_by_username(username) + if user: + return render_template("signup.html", error="User already exists") + + hashed_password = generate_password_hash(password) + user_data = db.create_new_user(username, hashed_password) + + user = User(id=str(user_data['id']), username=user_data['username'], password_hash=user_data['password_hash'], created_at=user_data['created_at']) + login_user(user) + + return redirect(url_for('home.index')) + +@auth.route("/logout") +@login_required +def logout(): + logout_user() + return redirect(url_for('landing_page')) \ No newline at end of file diff --git a/templates/dashboard.html b/templates/dashboard.html index b8a8c59..7862a72 100644 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -69,7 +69,7 @@

Sign up + data-id="18" href="{{ url_for('auth.signup') }}" rel="ugc">Sign up

diff --git a/templates/signup.html b/templates/signup.html index 1de5b6a..80fb480 100644 --- a/templates/signup.html +++ b/templates/signup.html @@ -44,7 +44,7 @@

Already have an accont? Login + data-id="18" href="{{ url_for('auth.login') }}" rel="ugc">Login