From 8d542b2efd563d9c616d563d74b37a7d756730b8 Mon Sep 17 00:00:00 2001 From: Florian Egger Date: Mon, 14 Sep 2026 14:20:58 +0200 Subject: [PATCH] Redirect unauthenticated browser requests to login --- app/main.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/app/main.py b/app/main.py index 6086462..2953fc9 100644 --- a/app/main.py +++ b/app/main.py @@ -10,7 +10,7 @@ from uuid import uuid4 import pdfplumber from fastapi import Depends, FastAPI, File, Form, HTTPException, Request, UploadFile -from fastapi.responses import HTMLResponse, RedirectResponse +from fastapi.responses import HTMLResponse, RedirectResponse, Response from fastapi.templating import Jinja2Templates from pypdf import PdfReader from sqlalchemy import select @@ -58,9 +58,19 @@ def require_api(request: Request) -> None: raise HTTPException(status_code=401, detail="Valid bearer token required") +class LoginRequired(Exception): + """A browser endpoint was accessed without an authenticated session.""" + + +@app.exception_handler(LoginRequired) +def login_required_handler(request: Request, exc: LoginRequired): + # Browsers expect to be sent to the login page, not shown a bare 401. + return RedirectResponse("/login", status_code=303) + + def require_web(request: Request) -> None: if not request.session.get("authenticated"): - raise HTTPException(status_code=401, detail="Login required") + raise LoginRequired() def insert_raw_event(db: Session, source: str, device_id: str, external_id: str, project_slug: str | None, occurred_at: datetime | None, payload: dict) -> bool: @@ -79,6 +89,11 @@ def healthz() -> dict: return {"status": "ok", "timezone": APP_TIMEZONE} +@app.get("/favicon.ico", include_in_schema=False) +def favicon(): + return Response(status_code=204) + + @app.get("/login", response_class=HTMLResponse) def login_page(request: Request): return templates.TemplateResponse(request, "login.html", {"error": None})