Repository navigation
feat!: admin pages served by the API, with sign-in - #33
Merged
Merged
Conversation
Server-rendered admin pages at /admin/, registered when sign-in is on. Admins sign in through our own provider as a built-in confidential client, with the token request made in-process. ADMIN_USERS lists the admins; others get a 403 page showing their sub. Sessions last 8 hours, POSTs must carry our Origin, and responses carry a strict CSP.
johnf
force-pushed
the
spec/26-admin-pages
branch
from
October 27, 2026 23:49
3ea7fce to
c8f1b99
Compare
This was referenced Oct 28, 2026
Closed
@tailwindcss/cli builds src/admin/public/admin.css (gitignored) from the admin templates: pnpm build builds it and pnpm dev watches it. @fastify/static serves it under /admin/. Tests build it in global setup when it is missing.
…dmin API Admins can index, reindex and delete one crate's indexes, and index or delete all of them, from the crate tree. Each action posts a form and redirects back with a one-time flash message. Delete actions ask for confirmation. BREAKING CHANGE: the admin API under /api/admin is gone, along with the TOKEN_ADMIN setting. Use the admin pages at /admin/ instead.
Crate files are served inline on the API's origin, so script in a crate could act as an admin. The admin pages now answer only on ADMIN_ORIGIN (default http://admin.localhost:8080 outside production; unset in production turns admin off), and nothing else is served there.
- An action on a crate covers it and the crates whose id follows it with a slash, for index and delete alike; an unknown crate is refused. - Every covered crate is queued before indexing starts, so the page keeps refreshing until the work is done. - Deleting is refused while a covered crate is queued or indexing, and indexing while one is being deleted. - Add Reindex all, log every action with the admin's sub, and return sign-in after a POST to the home page. - Sign-out ends on a signed-out page rather than signing straight back in. - The filter survives the page's refresh.
getState returned the live state cache entry. The crates page reads every crate's state before rendering any, so a crate whose structural index finished in between showed the cleared entry: "Structural: not indexed" beside "Search: indexing", without the count that would have shown it indexed. Return a copy instead.
johnf
force-pushed
the
spec/26-admin-pages
branch
from
October 28, 2026 00:18
851be32 to
c54b03a
Compare
This was referenced Oct 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #26
Closes #29
Closes #30
Closes #31
Closes #32
Summary
The API serves its own admin pages, rendered on the server, on their own origin (
ADMIN_ORIGIN) in the same process. An admin signs in through our OIDC provider and gets in only if theirsubis inADMIN_USERS. The bearer-token admin API is gone.sequenceDiagram participant B as Browser participant A as Admin origin /admin participant P as Issuer /oauth (our provider) participant U as Upstream provider B->>A: GET /admin/ (no session) A-->>B: 303 to P /oauth/authorize (PKCE, state, nonce) B->>P: authorize P-->>B: upstream sign-in (skipped if already signed in for Oni) B->>U: sign in U-->>P: callback P-->>B: 303 to A /admin/callback?code B->>A: callback A->>P: token request in-process (inject), no call to our public URL A-->>B: session cookie (8 h, SameSite=Lax, /admin) and 303 to the page asked forChanges from the spec as written, from review and a design change agreed during the work (the issues are updated to match):
ADMIN_ORIGIN, and only/adminanswers there, because arocapi serves crate files inline on the API origin (Sandbox file responses so crate files can't run scripts on the API origin crate-works/arocapi#133). This adds the settingADMIN_ORIGIN. In development and test it defaults tohttp://admin.localhost:8080; in production, admin is off unless it is set. The reverse proxy must pass the originalHostthrough./admin/signed-out, not/admin/, so it doesn't sign straight back in./, not a bare string prefix.sub.@fastify/view, not a hand-rolledhtmlhelper. Templates aren't type-checked; the data passed to them is typed.createIndexmarks every affected crate queued before it starts, so the page's self-refresh starts at once. It also fixes indexing by a string id, which never loaded the object, andgetStatenow returns a copy, so a page read mid-index isn't changed under it.Evidence
TOKEN_ADMINcompiled intoldaca-admin, and index state was kept in the browser's local storage.After:
pnpm test: 19 files, 161 tests pass;tsc,biomeandknipare clean. The admin cases all go through HTTP with real Postgres and OpenSearch and the fake upstream:Not covered: the filter box and
confirm()run only in a browser, and nothing in the project runs browser JS. The tests check the script is served, is allowed by the CSP and is wired up.Merge Danger
Door: two-way
Removing
/api/adminandTOKEN_ADMINis breaking (feat!), but nothing in production calls them. The CLI scripts call the indexer directly.Blast Radius: deploy
ADMIN_ORIGIN, a proxy hostname that passesHostthrough,ADMIN_USERS, andpnpm buildfor the stylesheet. Without them, admin stays off and startup warns.OIDC_COOKIE_KEYSparsing: it now drops blank entries and needs at least one key.Follow-up: #35, shared entities lost when one crate's index is deleted (predates this branch; Delete makes it easy to hit).
Unfixed review findings:
ldaca-admindid, not bymemberOf. Check this against real LDaCA ids.