patristic-be / src /api /dto /auth.py
Mario33333's picture
deploy: reader-access security hardening (feature/audiobook@06a5ed8)
7c2d250 verified
Raw
History Blame Contribute Delete
1.95 kB
"""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