diff --git a/.claude/rules/auth-surfaces.md b/.claude/rules/auth-surfaces.md new file mode 100644 index 0000000..2a303d9 --- /dev/null +++ b/.claude/rules/auth-surfaces.md @@ -0,0 +1,69 @@ +--- +description: Casdoor SSO for the browser + owner-minted PATs for MCP/CLI; owner-only on every surface; one resolver; ?token= fallback; dev-owner on loopback +paths: + - "auth.py" + - "api/auth.py" + - "api/tokens.py" + - "api/deps.py" + - "api/websocket.py" + - "mcp_server/server.py" + - "main.py" +--- + +# Authentication across the four surfaces + +Auth is **Casdoor SSO for the browser + owner-minted Personal Access Tokens for +MCP/CLI**, and the gateway is **owner-only**: exactly one operator (the Casdoor +user whose name matches `OWNER_NAME`) may use any surface; every other identity +gets 403. There is one resolver behind all of it — don't add a second auth +mechanism, a per-surface token, or a bypass. + +- **`resolve_bearer(session, raw_token)` in [auth.py](../../auth.py) is the single + resolver.** It turns a bearer string into a `User` (or `None`), classifying it + as a PAT (`hs_pat_` prefix → hash lookup) or a Casdoor JWT (RS256, validated + against the endpoint's JWKS). `resolve_from_header_or_query` wraps it to accept + the token from the `Authorization` header *or* a `?token=` query param. Every + surface funnels through these — REST, WebSocket, MCP. + +- **Owner gating is `is_owner(user)` + `get_current_owner`.** `is_owner` matches + `user.name == OWNER_NAME` (SSO) or the dev-owner sub (dev mode). REST routers + carry `dependencies=[Depends(get_current_owner)]` (aliased `_auth` in + [main.py](../../main.py)) → 401 if unauthenticated, 403 if not owner. New + protected routers get the same dependency. `/auth/me` is the one exception: it + resolves the user *without* the owner gate so a signed-in non-owner sees + `is_owner:false` (the dashboard's "not authorized" screen) instead of a bare + 401. + +- **The `?token=` query-param fallback is intentional and narrow.** Browsers + can't set headers on a WebSocket connect or on an `