from pathlib import Path from typing import Optional from fastapi import APIRouter, Depends, Form from fastapi.responses import HTMLResponse, RedirectResponse from app.internal import auth router = APIRouter(tags=["ui"]) _TEMPLATE = Path(__file__).parent.parent / "templates" / "index.html" _SCANNER_TEMPLATE = Path(__file__).parent.parent / "templates" / "scanner.html" _LOGIN_TEMPLATE = Path(__file__).parent.parent / "templates" / "login.html" @router.get("/login", response_class=HTMLResponse) async def login_page(error: Optional[str] = None): html = _LOGIN_TEMPLATE.read_text() banner = ( '
Invalid username or password.
' if error else "" ) return html.replace("", banner) @router.post("/login") async def login_submit(username: str = Form(...), password: str = Form(...)): if not auth.verify_credentials(username, password): return RedirectResponse("/login?error=1", status_code=303) token = auth.create_session_token(username) resp = RedirectResponse("/", status_code=303) resp.set_cookie( auth.SESSION_COOKIE_NAME, token, max_age=auth.SESSION_TTL_SECONDS, httponly=True, samesite="lax", # No TLS termination on this port (LAN dashboard on :80) — `secure` # would make the cookie never get sent at all. secure=False, ) return resp @router.post("/logout") @router.get("/logout") async def logout(): resp = RedirectResponse("/login", status_code=303) resp.delete_cookie(auth.SESSION_COOKIE_NAME) return resp @router.get("/", response_class=HTMLResponse) async def index(authed: bool = Depends(auth.require_session)): if not authed: return RedirectResponse("/login") return _TEMPLATE.read_text() @router.get("/scanner", response_class=HTMLResponse) async def scanner(authed: bool = Depends(auth.require_session)): if not authed: return RedirectResponse("/login") return _SCANNER_TEMPLATE.read_text()