fix(cortex-ui): strip whitespace from token; drop credentials:'include'
Live e2e test caught a paste-inserted whitespace in URL token param —
copy-paste from terminal had inserted %20%20%20 into middle of the
64-char hex token, which passed URL parsing but failed byte-level
auth::tokens_match on the daemon → 403.
Two fixes:
1. `sanitize_token()` strips ALL whitespace (spaces, tabs, newlines,
zero-width) from token before use, applied on both URL-param and
localStorage read paths. Defensive even against future Setup-form
paste mishaps — Setup input itself could also be whitespace-dirty.
2. `credentials: 'include'` → `credentials: 'omit'`. Bearer auth rides
on an explicit header; we don't need cookies. `include` triggers
browser quirks (Safari especially) around credentialed cross-origin
fetches that can strip or mismangle Authorization on redirects.
3. Error message now includes response body preview — `"403 Forbidden
— {\"error\":{\"code\":\"forbidden\",\"message\":\"bearer token
rejected\"}}"` — so the next failing setup surfaces root-cause.
Tests unchanged (10 passing). Rebuild hash: index-7ZqAoBoM.js.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
6672ae48e7
commit
39f95f7e04
2 changed files with 22 additions and 5 deletions
|
|
@ -13,9 +13,16 @@ async function api<T>(
|
|||
Authorization: `Bearer ${c.token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
credentials: 'include',
|
||||
// Bearer auth via explicit header; no cookies needed. credentials:'omit'
|
||||
// is the safe cross-origin default and avoids Safari quirks around
|
||||
// credentials:'include' stripping Authorization on some redirects.
|
||||
credentials: 'omit',
|
||||
});
|
||||
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
|
||||
if (!res.ok) {
|
||||
let body = '';
|
||||
try { body = (await res.text()).slice(0, 200); } catch { /* ignore */ }
|
||||
throw new Error(`${res.status} ${res.statusText}${body ? ` — ${body}` : ''}`);
|
||||
}
|
||||
return res.json() as Promise<T>;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,12 +7,22 @@ const DEFAULT_DAEMON = 'http://localhost:9797';
|
|||
const KEY_DAEMON = 'kei-cortex-daemon';
|
||||
const KEY_TOKEN = 'kei-cortex-token';
|
||||
|
||||
/** Strip ALL whitespace (spaces, tabs, newlines, zero-width) from a token. */
|
||||
function sanitize_token(raw: string): string {
|
||||
return raw.replace(/\s+/g, '');
|
||||
}
|
||||
|
||||
export function load_config(): CortexConfig | null {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const override = params.get('daemon');
|
||||
const url_daemon = params.get('daemon')?.trim();
|
||||
const url_token_raw = params.get('token');
|
||||
const url_token = url_token_raw ? sanitize_token(url_token_raw) : null;
|
||||
if (url_daemon) localStorage.setItem(KEY_DAEMON, url_daemon);
|
||||
if (url_token) localStorage.setItem(KEY_TOKEN, url_token);
|
||||
const daemon_url =
|
||||
override ?? localStorage.getItem(KEY_DAEMON) ?? DEFAULT_DAEMON;
|
||||
const token = params.get('token') ?? localStorage.getItem(KEY_TOKEN) ?? '';
|
||||
url_daemon ?? localStorage.getItem(KEY_DAEMON) ?? DEFAULT_DAEMON;
|
||||
const stored_token = localStorage.getItem(KEY_TOKEN);
|
||||
const token = url_token ?? (stored_token ? sanitize_token(stored_token) : '');
|
||||
if (!token) return null;
|
||||
return { daemon_url, token };
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue