EqualifyEverything / equalify-iris

feat(auth): one admin-set GitHub token, and no sign-in (#459)
One admin-set GitHub token, held server-side, and no sign-in anywhere. Iris had three GitHub credential concepts — per-user device-flow tokens, `github.issue_token`, and `github.anonymous_token` — and the complexity lived in the bridge between them: `req.anonymous`, a session-list 403, address-keyed limits, `usingServiceToken`. This deletes the bridge and both ends of it. `github.token` is one PAT the operator sets, required at startup, never sent to a client. No caller presents a GitHub credential. Gone with the login: the device flow and `src/routes/auth.ts`, the redirect flow, the per-user token cache, OAuth scope validation, `client_id` / `client_secret` / `oauth_scope` / `oauth_base_url`, `github.anonymous_token`, `github.issue_token`, and `auth_per_minute`. Net −2,003 lines across 39 files. Two questions stay separate, because conflating them is the failure this design would otherwise be one refactor away from. "May this caller use the API at all?" is `server.api_token` — a plain shared secret, optional, following the `server.quality_token` pattern, and deliberately NOT the GitHub token. It is checked before GitHub is consulted, so a refused stranger cannot spend the deployment's rate limit. "Who is this deployment?" is `github.token`, resolved once per process against `GET /user`. A caller who presents the gate token is not thereby anybody. Publishing the GitHub token to browsers so they could authenticate with it would have made the deployment's identity readable out of a page. Blank gate means open, which is what lets the demo page work with no authorizing step at all. What that costs is documented rather than guarded. With one identity there is nothing to isolate: `GET /v1/sessions` lists the deployment's sessions, and a session id reaches its document. A guard hiding one consequence of a shared identity while the others stand is theatre, so what replaces it is a boot warning and the same sentence in README.md, docs/API.md and docs/github-auth.md. Contributors get no attribution — issues say what a session found, not who found it — and that was the sustainability argument for requiring a login, so the README now states the trade instead of describing a mechanism it no longer has. The deployment manifests were a correctness problem, not a docs problem: `docker-compose.yml` never passed `IRIS_GITHUB_TOKEN`, which this makes required, so `docker compose up` would have refused to boot. Seven review rounds, each finding a defect the previous fix created. The two in new code: the identity memo cached the resolved id rather than the lookup, so a cold-start burst of 8 parallel requests spent 8 GitHub lookups; and `upsertUser` sat inside the GitHub try, reporting a failed write as `401 could not authenticate to GitHub: SQLITE_READONLY … (/private/tmp/…)` — the exact symptom `rejectLegacyUsersTable` names as misleading, with a filesystem path published to an unauthenticated caller. Both pinned, both pins verified by reverting the fix. The rest were claims left describing deleted mechanisms, in five files, including two paragraphs of docs/API.md that answered the attribution question in opposite ways twenty lines apart. An account repoint turned out to strand every per-session route, not just the listing, which is now documented and pinned at the route. test/e2e.sh grew a second deployment with the gate deleted: the open path the demo page depends on had been covered by unit tests and never driven through the real stack. Closes #456 Co-Authored-By: bbertucc <46652+bbertucc@users.noreply.github.com> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Blake Bertuccelli-Booth Blake Bertuccelli-Booth committed on Sep 11, 2026, 06:07 PM
Showing 41 changed files +1835 additions -3481 deletions
Browse files at this commit →