""" UI router — serves the web interface and handles login/logout. Routes: GET / → redirect to /{user}/{persona} if logged in, else /login GET /login → login page POST /login → validate credentials, set cookie, redirect POST /logout → clear cookie, redirect to /login GET /{user}/{persona} → serve index.html with CORTEX_CONFIG injected GET /{user}/{persona}/ → same (trailing slash) """ import logging from pathlib import Path import jwt from fastapi import APIRouter, Form, Request from fastapi.responses import HTMLResponse, RedirectResponse, Response from auth_utils import COOKIE_NAME, check_credentials, create_token, decode_token from persona import list_user_personas, validate as validate_persona logger = logging.getLogger(__name__) router = APIRouter() _STATIC = Path(__file__).parent.parent / "static" # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- def _get_session_user(request: Request) -> str | None: """Return the authenticated username from the session cookie, or None.""" token = request.cookies.get(COOKIE_NAME) if not token: return None try: return decode_token(token) except jwt.InvalidTokenError: return None def _set_cookie(response: Response, username: str) -> None: from auth_utils import create_token from config import settings token = create_token(username) response.set_cookie( COOKIE_NAME, token, max_age=settings.jwt_expire_days * 86400, httponly=True, samesite="lax", secure=False, # set True in production behind HTTPS ) def _first_persona(username: str) -> str | None: """Return the first available persona for a user, or None.""" names = list_user_personas(username) return names[0] if names else None # --------------------------------------------------------------------------- # Root redirect # --------------------------------------------------------------------------- @router.get("/", include_in_schema=False) async def root(request: Request): user = _get_session_user(request) if not user: return RedirectResponse("/login", status_code=302) persona = _first_persona(user) if not persona: return HTMLResponse("
Invalid username or password.
', ) return HTMLResponse(html, status_code=401) persona = _first_persona(username) if not persona: return HTMLResponse("