You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(auth): make logout revoke, and keep the JWT out of the URL (#72)
Logging out only deleted the cookie. A refresh token copied out of the
browser stayed valid for its full seven days, so the endpoint did nothing
at all against the one person it was meant to stop.
A JWT cannot be withdrawn once issued, so each refresh token now carries the
`ver` it was minted under and `GitHubUser.token_version` is bumped on
logout. Cheaper than a denylist: nothing stored per token, nothing to
expire. It invalidates all of the user's sessions rather than just this
browser's, which for a tool holding GitHub and Claude credentials is the
behaviour someone clicking logout after losing a laptop expects. Logout is
authenticated now, because revocation has to know whose tokens to kill.
Tokens issued before the column existed carry no `ver` claim and are
rejected the same way, logging current users out once. That is the intent,
not a side effect.
Separately, the OAuth callback put the access token in the redirect URL,
where it landed in browser history, in the Referer of whatever the page
loaded next, and in every proxy log along the way -- for the credential that
authenticates the entire API. The redirect carries no token now: it sets the
httpOnly refresh cookie, and the frontend trades that for an access token
through the `refreshToken()` helper the API client already had for silent
renewal.
426 backend tests, 66 frontend.
-**Coolify `--project-directory`**: Coolify sets `--project-directory` to the repo root, not the compose file location. Relative paths in the compose (`context`, `volumes`) must be relative to the repo root (`./apps/api`, not `../../apps/api`).
133
133
-**Coolify domain persistence**: Domains set in the Coolify UI may be cleared on redeploy/reload. Verify after each deploy. If persistent issues, add Traefik labels directly in the compose.
134
134
-**`.dockerignore` vs `pyproject.toml`**: `apps/api/.dockerignore` excludes `*.md` but `pyproject.toml` references `readme = "README.md"` — `!README.md` exception is required in `.dockerignore` or `uv sync` fails.
135
-
-**OAuth callback dual flow**: `GET /api/v1/auth/github/callback` accepts both OAuth login (with `state` CSRF param) and GitHub App installation redirect (with `installation_id`, no `state`). The `state` parameter is optional.
135
+
-**OAuth callback dual flow**: `GET /api/v1/auth/github/callback` accepts both OAuth login (with `state` CSRF param) and GitHub App installation redirect (with `installation_id`, no `state`). The `state` parameter is optional. It redirects with **no token in the URL** — it sets the httpOnly refresh cookie, and the frontend trades that for an access token via `POST /auth/refresh` (`refreshToken()` in `shared/api/client.ts`).
136
+
-**Refresh tokens are revocable**: each carries a `ver` claim matching `GitHubUser.token_version`; logout bumps the column, which invalidates every outstanding token for that user. `POST /auth/logout` is authenticated because revocation needs to know whose tokens to kill.
136
137
-**GitHub App PEM key**: must be stored as raw PEM (multi-line) in Coolify env vars, NOT base64-encoded. The code passes it directly to `jwt.encode()`.
137
138
-**OAuth tokens must be single-line**: Claude OAuth tokens pasted with line breaks cause `Invalid bearer token` errors. Frontend should strip whitespace/newlines from token input.
0 commit comments