feat: simple shared-token api auth

Adds a shared-token auth dependency for the viewing API (Task 2.9). Trust
model: when API_TOKEN is empty (the default) auth is disabled — we trust the
network boundary (local/intranet tool). When set, every request must present
the token via `Authorization: Bearer <token>` or `?token=<token>`, else 401.
`/api/health` is exempt so liveness probes work without the token.

- app/config.py: add `api_token: str = ""` to Settings.
- app/api/auth.py: `require_auth` dependency (secrets.compare_digest, reads
  get_settings() per-request so token rotation via cache_clear() works).
- app/api/main.py: apply the dependency globally; document the trust model.
- .env.example: document API_TOKEN.
- tests/test_api_auth.py: 8 tests covering disabled/correct/missing/wrong/
  malformed-header/query-param/health-exempt paths.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
浅念
2026-08-01 14:30:28 +08:00
parent 9cbd796975
commit 83b1aeb97c
5 changed files with 258 additions and 3 deletions
+4
View File
@@ -7,3 +7,7 @@ MEDIA_DIR=./media
# Fernet key (e.g. from cryptography.fernet.Fernet.generate_key()) used to encrypt
# stored credentials. Required for real runs; left blank for tests.
ENCRYPTION_KEY=
# Shared bearer token for the viewing API. Empty (default) -> auth DISABLED
# (trust the network boundary). When set, API requests must present it via
# `Authorization: Bearer <token>` or `?token=<token>`.
API_TOKEN=