dace2c4e66
fix(GRO-2586): enforce trusted-origins allowlist on Better Auth CORS responses Co-Authored-By: Paperclip <noreply@paperclip.ing>
61 lines
2.4 KiB
TypeScript
61 lines
2.4 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import { enforceAuthCors } from "../lib/auth-cors.js";
|
|
|
|
const TRUSTED = ["https://uat.groombook.dev", "https://dev.groombook.dev"];
|
|
|
|
/** Simulates Better Auth reflecting the request Origin (the pre-fix bug). */
|
|
function makeReflectedResponse(origin: string | null): Response {
|
|
return new Response('{"ok":true}', {
|
|
status: 200,
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
...(origin
|
|
? {
|
|
"Access-Control-Allow-Origin": origin,
|
|
"Access-Control-Allow-Credentials": "true",
|
|
}
|
|
: {}),
|
|
},
|
|
});
|
|
}
|
|
|
|
describe("enforceAuthCors (GRO-2586)", () => {
|
|
it("passes trusted origin through with credentials", () => {
|
|
const origin = "https://uat.groombook.dev";
|
|
const res = enforceAuthCors(origin, TRUSTED, makeReflectedResponse(origin));
|
|
expect(res.headers.get("Access-Control-Allow-Origin")).toBe(origin);
|
|
expect(res.headers.get("Access-Control-Allow-Credentials")).toBe("true");
|
|
});
|
|
|
|
it("strips ACAO for attacker origin (credentialed cross-origin read blocked)", () => {
|
|
const origin = "https://evil.example.com";
|
|
const res = enforceAuthCors(origin, TRUSTED, makeReflectedResponse(origin));
|
|
expect(res.headers.get("Access-Control-Allow-Origin")).toBeNull();
|
|
expect(res.headers.get("Access-Control-Allow-Credentials")).toBeNull();
|
|
});
|
|
|
|
it("strips ACAO when no Origin header (undefined)", () => {
|
|
const res = enforceAuthCors(undefined, TRUSTED, makeReflectedResponse(null));
|
|
expect(res.headers.get("Access-Control-Allow-Origin")).toBeNull();
|
|
expect(res.headers.get("Access-Control-Allow-Credentials")).toBeNull();
|
|
});
|
|
|
|
it("preserves non-CORS response headers and status from Better Auth", () => {
|
|
const origin = "https://evil.example.com";
|
|
const res = enforceAuthCors(origin, TRUSTED, makeReflectedResponse(origin));
|
|
expect(res.headers.get("Content-Type")).toBe("application/json");
|
|
expect(res.status).toBe(200);
|
|
});
|
|
|
|
it("second trusted origin is also allowed", () => {
|
|
const origin = "https://dev.groombook.dev";
|
|
const res = enforceAuthCors(origin, TRUSTED, makeReflectedResponse(origin));
|
|
expect(res.headers.get("Access-Control-Allow-Origin")).toBe(origin);
|
|
});
|
|
|
|
it("empty string origin is treated as untrusted", () => {
|
|
const res = enforceAuthCors("", TRUSTED, makeReflectedResponse(""));
|
|
expect(res.headers.get("Access-Control-Allow-Origin")).toBeNull();
|
|
});
|
|
});
|