Consolidate all X-API-Key auth into gitea_saas/auth.py (#24) #27

Merged
pbwraith merged 3 commits from issue-24 into main 2026-08-30 00:31:15 +02:00

3 Commits

Author SHA1 Message Date
Claude
3819b49498 fix(#27): guard /dashboard through the shared auth path after !26 rebase
Resolves PR #27's bounce finding. Rebased onto !26's head (the #25
/fdashboard guard, verified green) and resolved the dashboard.py +
test_dashboard.py conflict deliberately:

- /dashboard stays GUARDED (never unguarded) but now flows directly
  through gitea_saas.auth.key_is_valid + unauthorized_response — the
  local _guard closure is gone, so there is exactly one auth path.
- Unset configured key now refuses BOTH dashboard endpoints with the
  canonical 401 (was: 503 on the HTML page, per #25's local guard; the
  consolidation normalizes every refusal to one status/body).
- test_dashboard_page_guard_blocks_anonymous_access (!26's self-checking
  test) is kept: removing the guard from /dashboard fails it.
- /dashboard is NOT in auth.PUBLIC_PATHS.
- Bare-mount test updated: /dashboard 401 without key, 200 with key.
2026-08-26 06:43:17 -05:00
Claude
64ab8478dc Consolidate all X-API-Key auth into gitea_saas/auth.py (#24)
Four front-ends (server, delivery, intake_factory, dashboard) hand-rolled
the same X-API-Key check with one hardcoded literal each and four distinct
401 bodies. Add the single auth path:

- gitea_saas/auth.py owns API_KEY_HEADER (the only "X-API-Key" literal),
  PUBLIC_PATHS (liveness exemptions: /health, /healthz,
  /api/factory/health), key_is_valid() (the one compare_digest call on the
  API-key path, fail-closed on unset/non-str keys), and UNAUTHORIZED_BODY +
  unauthorized_response() (canonical 401, byte-identical on every wire).
- config.py re-exports API_KEY_HEADER from auth.py for existing importers;
  intake_factory.py drops its dead try/except ImportError fallback (config
  is long merged; a failed import must be loud); dashboard.py replaces its
  stale "lands with #2/#6" comment/literal with the real import.
- server before_request, delivery.authorized, intake _require_auth and
  dashboard _guard all delegate to the shared helper; /healthz is now
  open on the composed app (was 401 for a liveness route).
- Unset-key refusal is now the same canonical 401 as a wrong key
  (dashboard's 503 API_KEY_NOT_CONFIGURED removed — one status for every
  refusal). stripe_client.py's webhook-signature check is untouched
  (explicitly out of scope).

Tests: new tests/test_auth.py pins the acceptance criteria (one
compare_digest on the API-key path + one exempt stripe site, one literal,
same-length wrong key rejected, unset key refuses every front-end,
byte-identical 401 body across all front-ends, composed-app route
exposure). Existing suites updated to the canonical body.
Suite: 158 passed -> 171 passed (1 skipped).

Closes #24
2026-08-26 06:40:18 -05:00
Claude
08febd56ca fix(dashboard): require X-API-Key on /dashboard (fail-closed, #25)
The HTML operator page served the same clients/ledger/queue/review data as
the authenticated JSON summary, but with no auth at all. The loopback
requirement it relied on existed only as prose (page note + docstring); it
was never enforced by mount_dashboard, which does not own the app and
cannot constrain the bind address.

Put /dashboard behind the same _guard as /api/dashboard/summary:
- no X-API-Key -> 401, no billing data
- no configured key -> 503 (fail-closed)
- valid key -> 200 HTML (unchanged rendering)

Also drop the now-false 'loopback-only' security claims from the page note
and the mount_dashboard docstring, and update the tests:
- test_dashboard_page_open_and_html now sends a valid key
- new test_dashboard_page_guard_blocks_anonymous_access: no key -> 401,
  body contains no client data (mutation check: fails if guard removed)
- new test_dashboard_wrong_key_same_length_rejected: wrong same-length
  key -> 401 on HTML page too
- test_summary_fail_closed_without_configured_key now asserts the HTML
  page also fails closed (503), not open
- test_dashboard_mounts_onto_existing_app sends a valid key

/api/dashboard/summary behaviour unchanged. Suite: 160 passed, 1 skipped
(before: 158 passed, 1 skipped).
2026-08-26 02:54:37 -05:00