// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0 // The CSRF wall (spec ยง6.4): a state-changing call made with the session cookie carries the // page's token, and a bearer or API-key caller is not asked for one, because no browser attaches // those by itself. import type { Principal } from "@conusai-kit/auth" import { AppError } from "@conusai-kit/core" import { beforeAll, describe, expect, it } from "vitest" const session = (sessionId: string): Principal => ({ actor: { userId: "11111111-1111-1111-1111-111111111111" }, method: "session", sessionId, }) let csrfToken: (sessionId: string) => string let requireCsrf: (request: Request, principal: Principal | null) => void beforeAll(async () => { // loadAuthConfig reads the environment at call time, so the secret is set before the import // that uses it. process.env.AUTH_SESSION_SECRET = "0123456789abcdef0123456789abcdef" process.env.CONUSAI_PUBLIC_URL = "https://app.example.test" process.env.OIDC_ISSUER = "https://id.example.test" process.env.OIDC_WEB_CLIENT_ID = "web" process.env.OIDC_AUDIENCE = "api" const module = await import("../apps/web/src/server/csrf") csrfToken = module.csrfToken requireCsrf = module.requireCsrf }) const post = (headers: Record): Request => new Request("https://app.example.test/api/v1/tenancy.org.create", { method: "POST", headers }) describe("requireCsrf", () => { it("accepts a POST carrying this session's token", () => { const principal = session("session-1") expect(() => requireCsrf(post({ "x-csrf-token": csrfToken("session-1"), "sec-fetch-site": "same-origin" }), principal), ).not.toThrow() }) it("refuses a POST with no token", () => { expect(() => requireCsrf(post({}), session("session-1"))).toThrow(AppError) }) it("refuses another session's token", () => { expect(() => requireCsrf(post({ "x-csrf-token": csrfToken("session-2") }), session("session-1"))).toThrow( /x-csrf-token/, ) }) it("refuses a cross-site POST even with a token", () => { const headers = { "x-csrf-token": csrfToken("session-1"), "sec-fetch-site": "cross-site" } expect(() => requireCsrf(post(headers), session("session-1"))).toThrow(/cross-site/) }) it("asks nothing of a bearer or API-key caller", () => { const bearer: Principal = { actor: { userId: "u" }, method: "bearer" } const key: Principal = { actor: { userId: "u" }, method: "api-key", orgId: "o" } expect(() => requireCsrf(post({}), bearer)).not.toThrow() expect(() => requireCsrf(post({}), key)).not.toThrow() }) it("asks every browser session method for its token, including dev-login and an external provider session", () => { const dev: Principal = { actor: { userId: "u" }, method: "dev-login", sessionId: "session-1" } const external: Principal = { actor: { userId: "u" }, method: "external-session", sessionRef: { provider: "convex-better-auth", opaqueId: "session-1" }, } expect(() => requireCsrf(post({}), dev)).toThrow(/x-csrf-token/) expect(() => requireCsrf(post({}), external)).toThrow(/x-csrf-token/) // An external session's token is bound to its provider reference, never to a kit row of the same ID. expect(() => requireCsrf(post({ "x-csrf-token": csrfToken("session-1") }), external)).toThrow(/x-csrf-token/) }) it("fails closed on an external session disguised with a kit session row", () => { const disguised: Principal = { actor: { userId: "u" }, method: "external-session", sessionId: "session-1", sessionRef: { provider: "convex-better-auth", opaqueId: "session-1" }, } expect(() => requireCsrf(post({ "x-csrf-token": csrfToken("session-1") }), disguised)).toThrow(AppError) }) it("asks nothing of a GET", () => { const request = new Request("https://app.example.test/api/health", { headers: {} }) expect(() => requireCsrf(request, session("session-1"))).not.toThrow() }) })