""" Client portal — read-only, scoped client view (see docs/CLIENT_PORTAL.md). M1: a client opens a magic URL (/portal/enter/{token}) which mints a signed session cookie, then sees their locations (overview) and per-location read-only live data sourced from SLMM's cache. Every data route re-checks ownership. """ import logging from fastapi import APIRouter, Request, Depends from fastapi.responses import RedirectResponse from sqlalchemy.orm import Session from backend.database import get_db from backend.models import Client from backend.templates_config import templates from backend.portal_auth import ( get_current_client, make_session_cookie, resolve_token, COOKIE_NAME, COOKIE_MAX_AGE, ) logger = logging.getLogger(__name__) router = APIRouter(prefix="/portal", tags=["portal"]) @router.get("/enter/{token}") def portal_enter(token: str, request: Request, db: Session = Depends(get_db)): """Magic-URL entry: validate the token, mint a session cookie, land on /portal.""" tok, client = resolve_token(token, db) if not client: return templates.TemplateResponse( "portal/access_required.html", {"request": request, "reason": "invalid"}, status_code=403, ) resp = RedirectResponse(url="/portal", status_code=303) resp.set_cookie( COOKIE_NAME, make_session_cookie(tok.id), max_age=COOKIE_MAX_AGE, httponly=True, samesite="lax", ) logger.info(f"[PORTAL] {client.slug}: session opened via token {tok.id[:8]}") return resp @router.get("/logout") def portal_logout(): resp = RedirectResponse(url="/portal/access", status_code=303) resp.delete_cookie(COOKIE_NAME) return resp @router.get("/access") def portal_access(request: Request): """Landing for an unauthenticated visitor (no valid link).""" return templates.TemplateResponse( "portal/access_required.html", {"request": request, "reason": "required"} ) @router.get("") def portal_home(request: Request, client: Client = Depends(get_current_client)): """Client overview. (M1 task 4 fills in the scoped location list + map.)""" return templates.TemplateResponse( "portal/overview.html", {"request": request, "client": client, "locations": []}, )