"""DTOs for the ``/auth/*`` endpoints (CONTRACT.md §3, BACKEND_BUILD.md §6). The wire shapes here are intentionally minimal — auth is a 3-account system backed by ``credentials.toml`` (see ``src/lib/auth/repo.py``). The frontend never sees passwords beyond submitting them on login; the session token lives only in an HttpOnly cookie. ``MeResponse`` is what every authed page queries on mount to learn who's signed in. All fields inherit camelCase aliasing via :class:`ApiModel`. """ from __future__ import annotations from .common import ApiModel class LoginRequest(ApiModel): """``POST /auth/login`` request body. Plain-text password on the wire is fine because: (a) v1 runs entirely on localhost behind the Express proxy (same-origin, HTTP), (b) the credentials store itself is plain-text by design (see the comment at the top of ``src/lib/auth/repo.py``), and (c) we use ``hmac.compare_digest`` on the backend so the comparison is timing-safe. When we split-host later, TLS terminates at the FE proxy and the password is encrypted in transit. """ username: str password: str class MeResponse(ApiModel): """``GET /auth/me`` (and the ``POST /auth/login`` success body). The frontend hits ``/auth/me`` on mount to populate ``AuthContext``; a 401 redirects to ``/login``. ``isAdmin`` drives admin-only UI gating (Operations pages, Add Book, Re-ingest actions). ``role`` is the full role string (``admin`` | ``viewer`` | ``reader``) so the FE can distinguish a low-privilege ``reader`` (Reading-Room-only) from a read-only ``viewer`` (full library, no paid queries) — ``isAdmin`` alone can't. ``displayName`` is purely cosmetic — used in the header badge when present; falls back to the username otherwise. """ username: str is_admin: bool role: str display_name: str | None = None