fix(console): library source settings 404'd — the drawer still called the old plugin origin
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).
This commit is contained in:
@@ -26,9 +26,15 @@ import { m } from "@/paraglide/messages";
|
||||
* A library source's settings, rendered as a **generic form** from the plugin's own JSON Schema.
|
||||
*
|
||||
* The point (design D7, closing G8): a scanner plugin ships no SPA at all. It serves
|
||||
* `GET/PUT /__config` from the kit, and the console renders whatever schema comes back. Everything
|
||||
* goes through the existing session-gated `/plugin-ui/<id>/…` proxy, so there is **zero new host
|
||||
* surface** — the browser never learns the plugin's port or secret.
|
||||
* `GET/PUT /__config` from the kit, and the console renders whatever schema comes back. The browser
|
||||
* never learns the plugin's port or secret — the console reads it server-side over loopback.
|
||||
*
|
||||
* That read goes through `/api/plugin-config/<id>` on the CONSOLE origin, not the `/plugin-ui/…`
|
||||
* proxy this used to call. Plugin UIs live on their own origin (2026-08-05 review H-3) and the
|
||||
* console origin now answers 404 for `/plugin-ui/**` by design, which broke this drawer for every
|
||||
* library plugin — it is the one consumer of that path that is not an iframe. What it needs is
|
||||
* DATA, not an embedded UI, so it gets JSON same-origin and no plugin markup ever reaches the
|
||||
* console origin.
|
||||
*
|
||||
* Fields the derivation can't express fall back to a raw JSON editor. That fallback is what bounds
|
||||
* the risk of the whole approach: worst case the drawer is a validated textarea, and the PUT still
|
||||
@@ -51,7 +57,7 @@ export const SourceSettingsDialog: FC<{
|
||||
let cancelled = false;
|
||||
(async () => {
|
||||
try {
|
||||
const res = await fetch(`/plugin-ui/${pluginId}/__config`, {
|
||||
const res = await fetch(`/api/plugin-config/${pluginId}`, {
|
||||
credentials: "same-origin",
|
||||
});
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
@@ -77,7 +83,7 @@ export const SourceSettingsDialog: FC<{
|
||||
const save = async (value: JsonObject) => {
|
||||
setSaving(true);
|
||||
try {
|
||||
const res = await fetch(`/plugin-ui/${pluginId}/__config`, {
|
||||
const res = await fetch(`/api/plugin-config/${pluginId}`, {
|
||||
method: "PUT",
|
||||
credentials: "same-origin",
|
||||
headers: { "content-type": "application/json" },
|
||||
|
||||
Reference in New Issue
Block a user