sync: cookie-authenticated live layers (credentialed fetchImpl)

The closed API now membership-gates /parties/sync-room/* per request via
the session cookie and no longer puts a bearer token in sync-stack layer
descriptors. Pass a credentials:include fetch into SyncStack so live-layer
HTTP ops (manifest/bodies/put) carry the cookie; the realtime WebSocket
already gets it automatically (same-site handshake).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014dasZuqo6FStgT3rkC85im
This commit is contained in:
Istvan Matejcsok 2026-07-07 15:58:58 +02:00
commit 8e99f5c5ac

View file

@ -95,8 +95,8 @@ async function resolveAndOpen(
};
const res = await fetch(
`${opts.apiBase}/api/scopes/${encodeURIComponent(opts.scope)}/libs/${encodeURIComponent(libId)}/sync-stack`,
// credentials: session-cookie auth; the layer descriptors this returns keep
// their own bearer-token channel (sync-client transport is cookie-free).
// credentials: session-cookie auth, here and on every layer fetch below —
// live layers are membership-gated by the API worker per request.
{ method: "POST", headers, credentials: "include" },
);
if (!res.ok) throw new Error(`sync-stack resolve failed: HTTP ${res.status}`);
@ -106,7 +106,12 @@ async function resolveAndOpen(
};
log(`[synced] resolved ${body.layers.length} layer(s) for lib ${libId}`);
const stack = new SyncStack({ layers: body.layers });
// The descriptors carry no bearer token — live-layer HTTP ops authenticate
// with the session cookie, so the stack's fetch must send credentials. The
// realtime WebSocket gets cookies automatically (same-site handshake).
const credentialedFetch: typeof fetch = (input, init) =>
fetch(input, { ...init, credentials: "include" });
const stack = new SyncStack({ layers: body.layers, fetchImpl: credentialedFetch });
await stack.open();
return {
stack,