""" Family-shared session login. POST /api/auth/login — body ``{"password": "..."}`` — must match ``settings.SESSION_PASSWORD``. On success: signs the first FamilyProfile.id and writes it as the ``mp_session`` cookie, returns 204. POST /api/auth/logout — clears the cookie, returns 204. The session is intentionally simple: a single shared family password gates mutations behind nginx on the trusted network. No per-user auth. """ from fastapi import APIRouter, Depends, HTTPException, Response, status from pydantic import BaseModel from sqlalchemy.orm import Session from app.config import settings from app.database import get_db from app.models import FamilyProfile from app.security import SESSION_COOKIE, SESSION_MAX_AGE, issue_session router = APIRouter() class LoginRequest(BaseModel): password: str @router.post("/login") def login(payload: LoginRequest, db: Session = Depends(get_db)): expected = settings.SESSION_PASSWORD if not expected: raise HTTPException( status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Session auth not configured", ) if payload.password != expected: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid password" ) profile = db.query(FamilyProfile).first() # If no profile exists yet, sign a placeholder so the cookie still # validates; the family-id will be re-issued the first time a profile # is created. This avoids login being blocked on first-run. family_id = str(profile.id) if profile else "bootstrap" cookie_value = issue_session(family_id) response = Response(status_code=status.HTTP_204_NO_CONTENT) response.set_cookie( key=SESSION_COOKIE, value=cookie_value, max_age=SESSION_MAX_AGE, httponly=True, secure=True, samesite="lax", path="/", ) return response @router.post("/logout") def logout(): response = Response(status_code=status.HTTP_204_NO_CONTENT) response.delete_cookie(key=SESSION_COOKIE, path="/") return response