feat(web-tanstack): register security middleware and wire nonce to __root

- Add @tanstack/start + vinxi to deps so defineConfig is available
- Uncomment defineConfig registration in app.config.ts — middleware
  is now actually wired into the Nitro server hook, not just defined
- Update __root.tsx loader to call getNonce(getEvent().node.req)
  from @repo/core-shared/security/tanstack so the per-request nonce
  is read server-side and injected via <meta name="csp-nonce">
- Update __root.test.tsx: mock provides useLoaderData and asserts
  the nonce meta tag is rendered with the correct content

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-20 11:06:26 +00:00
parent 8d35fabaa5
commit 5fd483af39
5 changed files with 5636 additions and 162 deletions

View File

@@ -2,22 +2,28 @@ import { describe, it, expect, vi } from "vitest";
import { render, screen } from "@testing-library/react";
import "@testing-library/jest-dom";
// Mock @tanstack/react-router so we don't need a full router context
// Mock @tanstack/react-router so we don't need a full router context.
// useLoaderData is supplied so the component can read the nonce from loader data.
vi.mock("@tanstack/react-router", () => ({
createRootRoute: vi.fn((opts: { component: React.ComponentType }) => ({
options: { component: opts.component },
useLoaderData: () => ({ nonce: "test-nonce-abc" }),
})),
Outlet: () => <div data-testid="outlet" />,
}));
describe("Root route", () => {
it("wraps Outlet with TanstackTrpcProvider (children render)", async () => {
it("renders csp-nonce meta tag and Outlet", async () => {
const { Route } = await import("./__root");
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const RootComponent = (Route as any).options.component as React.ComponentType;
const RootComponent = (Route as any).options
.component as React.ComponentType;
render(<RootComponent />);
expect(screen.getByTestId("outlet")).toBeInTheDocument();
const metaTag = document.querySelector('meta[name="csp-nonce"]');
expect(metaTag).toBeInTheDocument();
expect(metaTag?.getAttribute("content")).toBe("test-nonce-abc");
});
});

View File

@@ -1,5 +1,25 @@
import { Outlet, createRootRoute } from "@tanstack/react-router";
import { getNonce } from "@repo/core-shared/security/tanstack";
export const Route = createRootRoute({
component: () => <Outlet />,
loader: async () => {
try {
// Server-side during SSR: read nonce set by applySecurityHeaders middleware.
// Fails gracefully on client-side navigation (nonce already in DOM from SSR).
const { getEvent } = await import("vinxi/http");
return { nonce: getNonce(getEvent().node.req) };
} catch {
return { nonce: "" };
}
},
component: () => {
const { nonce } = Route.useLoaderData();
return (
<>
{/* nonce exposed to client so instrumentation-client.ts can read it */}
<meta name="csp-nonce" content={nonce} />
<Outlet />
</>
);
},
});