mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-05 15:09:42 +02:00
fix(webview): recover a proxied dashboard that reloads on its landing page
The runtime shim masks `/webview/<cap>/` off a proxied page's URL so its router boots on the path it expects, and the landing page masks to exactly `/`. A `location.reload()` there (a Vite dev server on a config change or a failed HMR update, the likeliest case in the feature's own motivating scenario) therefore asks for Codeman's root as an iframe navigation. `serveLostWebviewFrame()` returned early for `/`, so on a passwordless install the frame received Codeman's own app shell and rendered it inside the web tab, and with a password it got a 401 in the frame. Either way no `codeman:webview-lost` message was posted, and because the document loaded fine the load handler cleared the failed-frame panel, so the Reload / Open in new tab affordances never appeared. Before masking the frame's URL was the prefixed one, so a reload worked; this was a regression. `/` is the one lost-frame path a registered route also serves, so the route table cannot tell that reload from a real navigation. Credentials can: nothing in Codeman frames its own root, and a sandboxed frame is opaque-origin with no cookie and no Authorization header. `carriesAuthCredentials()` (pure, in webview-proxy.ts) makes that test, and `/` is now admitted by the auth hook only when it fails; a framed `/` that does carry credentials still gets the shell. Without a password no auth hook runs at all, so the index route applies the same test itself (`isLostWebviewRootFrame`) before rendering the shell, and the three places that emitted the recovery page share `sendLostWebviewFramePage()`. Tests: the password form in webview-auth-exemption (recovery page for a credential-free framed `/`, shell with valid Basic auth, 401 with a stale cookie or a top-level navigation), the passwordless form against a real WebServer in webview-lost-root-frame (port 3198), and the credential predicate in webview-proxy. All three fail without the fix. Verified against a live isolated instance as well: a framed `GET /` with no credentials answers the 470-byte recovery page, a top-level `GET /` and a framed one carrying a cookie answer the shell. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -28,6 +28,7 @@ import {
|
||||
stripFrameAncestors,
|
||||
upstreamWebSocketUrl,
|
||||
isLostWebviewFrameNavigation,
|
||||
carriesAuthCredentials,
|
||||
lostWebviewFramePage,
|
||||
LOST_FRAME_PAGE_CSP,
|
||||
} from '../src/web/webview-proxy.js';
|
||||
@@ -816,4 +817,16 @@ describe('lost-frame recovery', () => {
|
||||
// The page must never carry a Referer that would leak anything about the tab.
|
||||
expect(page).toContain('name="referrer" content="no-referrer"');
|
||||
});
|
||||
|
||||
it('tells a credential-free request (a sandboxed frame reloading on /) from one that could authenticate', () => {
|
||||
const has = (headers: Record<string, string | string[] | undefined>) =>
|
||||
carriesAuthCredentials(headers, 'codeman_session');
|
||||
expect(has({})).toBe(false);
|
||||
expect(has({ cookie: 'theme=dark; codeman_sessions=lookalike' })).toBe(false);
|
||||
expect(has({ authorization: '' })).toBe(false);
|
||||
expect(has({ cookie: 'codeman_session=abc' })).toBe(true);
|
||||
expect(has({ cookie: 'theme=dark; codeman_session=abc' })).toBe(true);
|
||||
expect(has({ cookie: ['theme=dark', 'codeman_session=abc'] })).toBe(true);
|
||||
expect(has({ authorization: 'Basic YWRtaW46eA==' })).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user