feat(standalone): send session credentials on backend API calls

Backends with real auth (session cookies) need credentials:'include' on the
editor's cross-origin fetches — ts-rest clients, project file bytes/upload,
drift keepalive, lib item GET/PUT, sync-stack resolve. Cookie-less setups are
unaffected (the thin identity headers still ride along and same-site Lax
cookies simply don't exist). CDN/static-gallery fetches stay credential-less
(wildcard CORS rejects credentialed requests). The example backend's CORS now
sends allow-credentials — origin reflection was already exact, so this only
lets browsers accept those responses.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016TxciQ5VkNYUZesgMSEMUr
This commit is contained in:
Istvan Matejcsok 2026-07-06 17:37:44 +02:00
commit 840173c978
6 changed files with 29 additions and 3 deletions

View file

@ -138,5 +138,11 @@ export function reportDriftBeacon(slug: string, body: DriftReportBody): void {
} catch {
/* fall through to keepalive fetch */
}
void fetch(url, { method: "POST", body: blob, keepalive: true }).catch(() => {});
// credentials: parity with the beacon path (beacons carry same-site cookies).
void fetch(url, {
method: "POST",
body: blob,
keepalive: true,
credentials: "include",
}).catch(() => {});
}

View file

@ -12,4 +12,8 @@ import { API_BASE_URL, userSlug } from "./config";
export const client = initClient(contract, {
baseUrl: API_BASE_URL,
baseHeaders: { [USER_HEADER]: userSlug() },
// Send the backend's session cookie (same-site, different origin): backends
// with real auth resolve the user from it and ignore the thin header. On a
// cookie-less setup this changes nothing.
credentials: "include",
});

View file

@ -80,7 +80,12 @@ function remoteProjectSource(): ProjectSource {
return res.body;
},
async fetchFileBytes(slug, relPath) {
const res = await fetch(fileUrl(slug, relPath));
// credentials: session-cookie auth (see contract-client.ts). The static
// gallery fetches below stay credential-less — a CDN's wildcard CORS
// rejects credentialed requests.
const res = await fetch(fileUrl(slug, relPath), {
credentials: "include",
});
if (!res.ok) throw new Error(`download failed (${res.status}): ${relPath}`);
return new Uint8Array(await res.arrayBuffer());
},
@ -93,6 +98,7 @@ function remoteProjectSource(): ProjectSource {
const res = await fetch(`${projectsBase()}/${encodeURIComponent(slug)}/files`, {
method: "POST",
body: form,
credentials: "include",
});
if (!res.ok) throw new Error(`upload failed (${res.status}): ${relPath}`);
},