fix(rpc): render canonical csrf token for clients
This commit is contained in:
@@ -4138,8 +4138,13 @@ export const REACTIVE_RUNTIME = String.raw`
|
|||||||
}
|
}
|
||||||
|
|
||||||
function callServerFunction(component, functionName, args) {
|
function callServerFunction(component, functionName, args) {
|
||||||
|
var csrfMeta = document.querySelector('meta[name="wrnexus-csrf"]');
|
||||||
var csrfMatch = /(?:^|;\s*)wire-csrf=([^;]+)/.exec(document.cookie || "");
|
var csrfMatch = /(?:^|;\s*)wire-csrf=([^;]+)/.exec(document.cookie || "");
|
||||||
var csrf = csrfMatch ? decodeURIComponent(csrfMatch[1]) : "";
|
var csrf = csrfMeta
|
||||||
|
? csrfMeta.getAttribute("content") || ""
|
||||||
|
: csrfMatch
|
||||||
|
? decodeURIComponent(csrfMatch[1])
|
||||||
|
: "";
|
||||||
return fetch("/__wrnexus/rpc", {
|
return fetch("/__wrnexus/rpc", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: "same-origin",
|
credentials: "same-origin",
|
||||||
|
|||||||
@@ -828,6 +828,34 @@ test("component server calls send the CSRF cookie in the RPC header", async () =
|
|||||||
expect(new Headers(request?.headers).get("x-csrf-token")).toBe("rpc token");
|
expect(new Headers(request?.headers).get("x-csrf-token")).toBe("rpc token");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("component server calls prefer the rendered CSRF token over stale cookies", async () => {
|
||||||
|
const win = mount(
|
||||||
|
`<meta name="wrnexus-csrf" content="current-token">` +
|
||||||
|
`<div data-scope="" data-wrn-component="Home">` +
|
||||||
|
`<button data-on-click="server.handleClick()">go</button>` +
|
||||||
|
`</div>`,
|
||||||
|
);
|
||||||
|
Object.defineProperty(win.document, "cookie", {
|
||||||
|
configurable: true,
|
||||||
|
value: "wire-csrf=stale-token",
|
||||||
|
});
|
||||||
|
let request: RequestInit | undefined;
|
||||||
|
const globals = globalThis as Record<string, unknown>;
|
||||||
|
const originalFetch = globals.fetch;
|
||||||
|
globals.fetch = async (_input: RequestInfo | URL, init?: RequestInit) => {
|
||||||
|
request = init;
|
||||||
|
return Response.json({ ok: true });
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
(win.document.querySelector("button") as unknown as HTMLElement).click();
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||||
|
} finally {
|
||||||
|
globals.fetch = originalFetch;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(new Headers(request?.headers).get("x-csrf-token")).toBe("current-token");
|
||||||
|
});
|
||||||
|
|
||||||
test("development runtime warns when a component binding names a missing function", () => {
|
test("development runtime warns when a component binding names a missing function", () => {
|
||||||
const win = new Window() as unknown as Window & Record<string, unknown>;
|
const win = new Window() as unknown as Window & Record<string, unknown>;
|
||||||
win.document.body.innerHTML =
|
win.document.body.innerHTML =
|
||||||
|
|||||||
@@ -1873,6 +1873,7 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
|
|||||||
body: renderedBody,
|
body: renderedBody,
|
||||||
scripts,
|
scripts,
|
||||||
extraHead: [
|
extraHead: [
|
||||||
|
`<meta name="wrnexus-csrf" content="${escapeHtml(pageCsrf)}" />`,
|
||||||
preserve ? `<meta name="wrnexus-preserve" content="${preserve}" />` : "",
|
preserve ? `<meta name="wrnexus-preserve" content="${preserve}" />` : "",
|
||||||
pwaEnabled ? `<link rel="manifest" href="/site.webmanifest" />` : "",
|
pwaEnabled ? `<link rel="manifest" href="/site.webmanifest" />` : "",
|
||||||
pwaEnabled ? `<meta name="mobile-web-app-capable" content="yes" />` : "",
|
pwaEnabled ? `<meta name="mobile-web-app-capable" content="yes" />` : "",
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ test("server actions validate, enforce CSRF, invalidate, and progressively enhan
|
|||||||
const page = await handlers.fetch(new Request("https://example.test/users"), server);
|
const page = await handlers.fetch(new Request("https://example.test/users"), server);
|
||||||
const html = await page!.text();
|
const html = await page!.text();
|
||||||
expect(html).toContain('name="_csrf"');
|
expect(html).toContain('name="_csrf"');
|
||||||
|
expect(html).toContain('name="wrnexus-csrf"');
|
||||||
expect(html).toContain("/__wrnexus/actions.js");
|
expect(html).toContain("/__wrnexus/actions.js");
|
||||||
const cookie = page!.headers.get("set-cookie")!;
|
const cookie = page!.headers.get("set-cookie")!;
|
||||||
const token = /wire-csrf=([^;]+)/.exec(cookie)?.[1];
|
const token = /wire-csrf=([^;]+)/.exec(cookie)?.[1];
|
||||||
|
|||||||
Reference in New Issue
Block a user