Library source settings never opened — the drawer still called the old plugin origin #118

Merged
enricobuehler merged 1 commits from worktree-library-settings-origin-split into main 2026-08-08 12:40:47 +00:00
Owner

Opening a library source's settings did nothing, for every library plugin. Confirmed against the running console on .21:

console origin :47992   /plugin-ui/lutris/__config -> 404
plugin  origin :47993   /plugin-ui/lutris/__config -> 401

The cause

The drawer fetches a relative /plugin-ui/<id>/__config, so it resolves against the console's own origin — where middleware/auth.ts answers 404 for /plugin-ui/**, unconditionally and by design.

That refusal is the 2026-08-05 review's origin split (H-3): plugin UIs moved to their own listener, and neither origin may serve the other's paths. The settings drawer is the only consumer of /plugin-ui that is not an iframe — every other caller builds an absolute URL from pluginOriginFrom(uiConfig) — so it is the one thing the split broke, and nothing failed loudly enough for anyone to notice.

This is independent of the category/SDK work: it would have failed even with everything else correct.

Why not just point it at the plugin origin

That needs CORS plus cross-site cookies, and it would put a plugin-controlled response inside a credentialed cross-origin fetch — reopening exactly the hole the split closed.

What this drawer needs is data, not an embedded UI. /api/plugin-config/<id> reads the plugin's __config server-side over loopback and returns the JSON same-origin, so no plugin markup or script is ever served from the console origin, and the per-boot secret stays on the server exactly as in the /plugin-ui proxy.

Details worth reviewing

  • /api/** is always session-gated (isPublicPath), so the route inherits the gate and answers 401 as JSON rather than redirecting to /login — which is what a fetch needs and what the old path could never give it.
  • Forwards GET and PUT only.
  • Reads the body before the stale-credential retry: readRawBody drains the stream, so a retried PUT would have saved {} over the operator's config.
  • Keeps the one-shot 401 retry with bustCredential, because a plugin's secret rotates on restart well inside the credential cache TTL.
  • Passes the plugin's own body through untouched, so a 400's decode issue still reaches the operator.

Verification

Against the real built server, not a mock:

request result meaning
/api/plugin-config/lutris 401 route resolves and is gated; the api/[...] BFF catch-all does not swallow it
/plugin-ui/lutris/__config 404 the origin split is still intact
/api/v1/status 401 the BFF still works

tsc clean, production build clean (route bundled as routes/api/plugin-config/_id_.mjs), i18n 633 messages across en+de, biome clean on both touched files — the single noAccumulatingSpread warning in SourceSettings.tsx pre-dates this change.

Needs a console (punktfunk-web) build to reach an operator.

Opening a library source's settings did nothing, for **every** library plugin. Confirmed against the running console on `.21`: ``` console origin :47992 /plugin-ui/lutris/__config -> 404 plugin origin :47993 /plugin-ui/lutris/__config -> 401 ``` ## The cause The drawer fetches a **relative** `/plugin-ui/<id>/__config`, so it resolves against the console's own origin — where `middleware/auth.ts` answers 404 for `/plugin-ui/**`, unconditionally and by design. That refusal is the 2026-08-05 review's origin split (**H-3**): plugin UIs moved to their own listener, and neither origin may serve the other's paths. The settings drawer is the only consumer of `/plugin-ui` that is **not an iframe** — every other caller builds an absolute URL from `pluginOriginFrom(uiConfig)` — so it is the one thing the split broke, and nothing failed loudly enough for anyone to notice. This is independent of the `category`/SDK work: it would have failed even with everything else correct. ## Why not just point it at the plugin origin That needs CORS plus cross-site cookies, and it would put a plugin-controlled response inside a credentialed cross-origin fetch — reopening exactly the hole the split closed. What this drawer needs is **data, not an embedded UI**. `/api/plugin-config/<id>` reads the plugin's `__config` server-side over loopback and returns the JSON same-origin, so no plugin markup or script is ever served from the console origin, and the per-boot secret stays on the server exactly as in the `/plugin-ui` proxy. ## Details worth reviewing - `/api/**` is always session-gated (`isPublicPath`), so the route inherits the gate and answers **401 as JSON** rather than redirecting to `/login` — which is what a `fetch` needs and what the old path could never give it. - Forwards **GET and PUT only**. - Reads the body **before** the stale-credential retry: `readRawBody` drains the stream, so a retried PUT would have saved `{}` over the operator's config. - Keeps the one-shot 401 retry with `bustCredential`, because a plugin's secret rotates on restart well inside the credential cache TTL. - Passes the plugin's own body through untouched, so a 400's decode issue still reaches the operator. ## Verification Against the **real built server**, not a mock: | request | result | meaning | |---|---|---| | `/api/plugin-config/lutris` | **401** | route resolves and is gated; the `api/[...]` BFF catch-all does not swallow it | | `/plugin-ui/lutris/__config` | **404** | the origin split is still intact | | `/api/v1/status` | 401 | the BFF still works | `tsc` clean, production build clean (route bundled as `routes/api/plugin-config/_id_.mjs`), i18n 633 messages across en+de, biome clean on both touched files — the single `noAccumulatingSpread` warning in `SourceSettings.tsx` pre-dates this change. Needs a console (`punktfunk-web`) build to reach an operator.
enricobuehler added 1 commit 2026-08-08 12:18:30 +00:00
fix(console): library source settings 404'd — the drawer still called the old plugin origin
ci / bun-nix (pull_request) Successful in 35s
ci / web (pull_request) Successful in 1m19s
ci / docs-site (pull_request) Successful in 1m18s
ci / rust-arm64 (pull_request) Successful in 3m22s
ci / rust (pull_request) Successful in 4m30s
deef5e4382
Opening a library source's settings did nothing, for every library plugin. Confirmed on
`.21` against the running console:

    console origin :47992  /plugin-ui/lutris/__config -> 404
    plugin  origin :47993  /plugin-ui/lutris/__config -> 401

The drawer fetches a RELATIVE `/plugin-ui/<id>/__config`, so it resolves against the
console's own origin — where `middleware/auth.ts` answers 404 for `/plugin-ui/**`
unconditionally and by design. That refusal is the 2026-08-05 review's origin split
(H-3): plugin UIs moved to their own listener, and neither origin may serve the other's
paths. The drawer is the only consumer of `/plugin-ui` that is NOT an iframe — every
other caller builds an absolute URL from `pluginOriginFrom(uiConfig)` — so it was the
one thing the split broke, and nothing failed loudly enough to notice.

The fix is deliberately not to point the drawer at the plugin origin. That needs CORS
plus cross-site cookies, and it would put a plugin-controlled response inside a
credentialed cross-origin fetch — reopening exactly the hole the split closed. What
this drawer needs is DATA, not an embedded UI: `/api/plugin-config/<id>` reads the
plugin's `__config` server-side over loopback and returns the JSON same-origin, so no
plugin markup or script is ever served from the console origin and the per-boot secret
stays on the server, as with the `/plugin-ui` proxy.

`/api/**` is always session-gated (`isPublicPath`), so the new route inherits the gate
and answers 401 as JSON rather than redirecting to /login — which is what a `fetch`
needs and what the old path could never give it. It forwards only GET and PUT, reads
the body BEFORE the stale-credential retry (`readRawBody` drains the stream, so a
retried PUT would have saved `{}` over the operator's config), and passes the plugin's
own body through untouched so a 400's decode issue still reaches the operator.

Verified against the real built server: `/api/plugin-config/lutris` answers 401 — the
route resolves and is gated, and the BFF catch-all at `api/[...]` does not swallow it —
while `/plugin-ui/lutris/__config` still answers 404 on the console origin, i.e. the
split is intact. `/api/v1/status` still reaches the BFF. tsc clean, production build
clean, i18n 633 messages across en+de, biome clean on both touched files (the one
warning in SourceSettings.tsx pre-dates this change).
enricobuehler merged commit 8f32976349 into main 2026-08-08 12:40:47 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: unom/punktfunk#118