#39
Protect the management console and API with a shared auth token
Summary
The Cairn server accepted every request unauthenticated: the console at /, the whole API behind it, and docker compose publishes port 8000 on all interfaces. Anyone who could reach the port could read, create, stop, or delete every project. This PR puts a shared secret in front of everything so Cairn can be run in any workflow, including on a reachable host.
Server
- New default-deny
AuthMiddleware(cairn/server/auth.py). Every path returns401unless it is on the public list:/,/static/*,/health,/favicon.icoand/auth/*./docsand/openapi.jsonare protected too, so a new route can never fail open. - Two credential forms:
Authorization: Bearer <token>(dispatcher, scripts) or an HttpOnly, SameSite=Strict session cookie issued byPOST /auth/login. The cookie value is an HMAC derived from the token, so a leaked cookie never reveals the token and rotating the token invalidates every session.Secureis set automatically behind an HTTPS proxy (X-Forwarded-Proto). - Token resolution at startup:
--auth-token/CAIRN_AUTH_TOKEN, else--auth-token-file/CAIRN_AUTH_TOKEN_FILE(defaultauth_tokennext to the database), else a random token is generated, written there and printed once in the log. Only an explicit--no-auth/CAIRN_NO_AUTH=1disables auth, with a loud warning when bound to a non-loopback host. GET /health: unauthenticated liveness probe (used by the compose health check instead of/projects).
Console
- Sign-in overlay on first load and whenever the server answers
401mid-session; inline error for a wrong token; polling and toasts pause while it is shown; sign-out button in both headers.
Dispatcher
server_tokenindispatch.yaml, overridden byCAIRN_SERVER_TOKEN; sent as a bearer token on every request.- A rejected token (HTTP 401) stops the dispatcher with an explanation instead of retrying forever.
Deployment and docs
docker-compose.yamlrequiresCAIRN_AUTH_TOKENfrom.env(.env.exampleadded,.envgit-ignored) and passes it to both containers.- README "Authentication" section, server protocol spec (认证 section), dispatcher design quick-ref, and the example configs are updated.
Testing
- 15 new tests: anonymous rejection, bearer access, login/logout cookie lifecycle, forged cookies,
Securebehind an HTTPS proxy,--no-auth, token generation and reuse, env precedence, CLI flags, client header, config loading, dispatcher 401 stop. - Existing API and end-to-end fixtures now run against the protected server with a bearer token.
- Verified manually in a browser: wrong token rejected, correct token opens the console, project created through the UI with the cookie, sign-out returns to the sign-in screen.
Note: the token travels in clear text over plain HTTP, so a remotely reachable server should sit behind a TLS-terminating proxy (documented in the README).