From 2d22158dd95f76399f5716d556be60c77da04a46 Mon Sep 17 00:00:00 2001 From: Tom van der Lee Date: Thu, 30 Jul 2026 16:03:32 +0200 Subject: Admin ui --- management/endpoints.py | 22 +++++++++++++++++++--- management/templates/base.html | 17 ++++++++++++++--- management/templates/dashboard.html | 16 ++++++++++------ management/templates/users.html | 32 ++++++++++++++++++++++++-------- 4 files changed, 67 insertions(+), 20 deletions(-) (limited to 'management') diff --git a/management/endpoints.py b/management/endpoints.py index 47602d6..ef44114 100644 --- a/management/endpoints.py +++ b/management/endpoints.py @@ -1,7 +1,9 @@ +from functools import wraps +from urllib.parse import urlencode + from fastapi import FastAPI, Request from sqlalchemy import func from sqlmodel import select -from starlette.authentication import requires from starlette.middleware.authentication import AuthenticationMiddleware from starlette.middleware.sessions import SessionMiddleware from starlette.responses import RedirectResponse @@ -21,6 +23,20 @@ templates = Jinja2Templates(directory=[ 'management/templates' ]) + +def requires_session(func): + """Redirects to an absolute login URL instead of Starlette's `requires(redirect=...)`, + which resolves the redirect target via `request.url_for()` against whichever router is + bound into `request.scope` -- a lookup that only reaches `authentication`'s routes when + `management` happens to be mounted under a shared root app.""" + @wraps(func) + async def wrapper(request: Request, *args, **kwargs): + if not {'authenticated', 'session'}.issubset(request.auth.scopes): + next_param = urlencode({'next': str(request.url)}) + return RedirectResponse(f'/auth/login/?{next_param}', status_code=303) + return await func(request, *args, **kwargs) + return wrapper + @management.get('/') async def index(request: Request): with get_session_context() as session: @@ -34,12 +50,12 @@ async def index(request: Request): @management.get('/dashboard/') -@requires(['authenticated', 'session'], redirect='get_login') +@requires_session async def dashboard(request: Request): return templates.TemplateResponse(request, 'dashboard.html') @management.get('/users/') -@requires(['authenticated', 'session'], redirect='get_login') +@requires_session async def users(request: Request): with get_session_context() as session: query = select(User) diff --git a/management/templates/base.html b/management/templates/base.html index 0b9da11..7ba0adb 100644 --- a/management/templates/base.html +++ b/management/templates/base.html @@ -1,7 +1,18 @@ {% extends 'global.html' %} +{% import 'macros.html' as ui %} {% block template %} -