the dev proxy was mounted behind the auth middleware
"Unauthorized" in the pane, and that word is the whole diagnosis: this router's own refusal says "Not authorised for the dev proxy. Open it from the Preview panel." The text on screen came from `userMiddleware`, which means the request was rejected before the router ever saw it. I had mounted `/dev-proxy` in the PROTECTED table. Everything there gets `userMiddleware`, which requires an Authorization header — and an iframe navigation is a navigation, not a fetch, so it has none. The document 401'd every time. The cookie work in the last commit was correct and could never have helped: nothing was reaching the code that reads the cookie. Moved to the unprotected mounts, beside `/auth` and `/sidecar`, with the reason written where the list is. It is not open — the router verifies the OWNER's token on `/session` and requires the cookie it issues on everything else. ── Why I did not catch this myself ── My tests mounted `devProxyRouter` on a bare Hono app. That proves the router and nothing else, and the bug was entirely in what sits in front of it. Retested through `buildHonoApp()` — the real assembly, every middleware — where `/session` returns 200 with a cookie, the frame returns 200 carrying the dev server's own HTML and injected script, and an uncredentialled request now gets THIS router's message rather than the middleware's, which is the observable difference that proves the path. A test that constructs its own subset of the system will keep passing while the system is broken. Worth remembering the next time a router is proven in isolation. tsgo clean, frontend builds, 889 pass / 7 fail unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+9
-2
@@ -139,8 +139,6 @@ const PROTECTED_MOUNTS: [prefix: string, router: ReturnType<typeof createRouter>
|
||||
// ['/gitea', giteaRouter], // plugin — switched off 2026-08-13
|
||||
['/app-store', appStoreRouter],
|
||||
['/plugins', pluginsRouter],
|
||||
// Loopback dev servers, framed by the Preview panel. See its router for why it is not a proxy host.
|
||||
['/dev-proxy', devProxyRouter],
|
||||
// ['/caldav', caldavRouter], // the JSON door for Officer's own calendar/contacts UI — plugin, switched off 2026-08-13
|
||||
// ['/dav', davRouter], // app-password management (the sync door is /dav, top-level) — plugin, switched off
|
||||
// ['/notify', notifyRouter], // plugin — switched off 2026-08-13
|
||||
@@ -178,6 +176,7 @@ export const UNPROTECTED_API_PREFIXES: string[] = [
|
||||
// '/vault', // switched off with the Vaultwarden mounts — see above
|
||||
'/sidecar',
|
||||
'/agent-handoff',
|
||||
'/dev-proxy',
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -234,6 +233,14 @@ export function buildHonoApp(plugins: MountedPlugin[] = []): Hono<{ Variables: H
|
||||
// peer on that panel's own dashboard. See servers/api/agent-handoff/router.ts.
|
||||
app.route('/api/agent-handoff', agentHandoffRouter);
|
||||
|
||||
// Loopback dev servers, framed by the Preview panel.
|
||||
//
|
||||
// UNPROTECTED because an iframe navigation carries no Authorization header — `userMiddleware` would 401
|
||||
// the document before the router saw it, which is exactly what happened: the pane showed the
|
||||
// middleware's "Unauthorized", not this router's own message. It is not open: the router verifies the
|
||||
// OWNER's token on `/session` and requires the cookie that issues on everything else.
|
||||
app.route('/api/dev-proxy', devProxyRouter);
|
||||
|
||||
// CalDAV/CardDAV for phones and desktop clients — mounted TOP-LEVEL for the same reason the vault is:
|
||||
// DAVx5, iOS and Thunderbird authenticate with HTTP Basic on every request and have nowhere to put a
|
||||
// platform JWT, so userMiddleware would 401 them. The credential is a scoped DAV app password; see
|
||||
|
||||
Reference in New Issue
Block a user