import { describe, it, expect } from "vitest"; import { hostingConfig, requestOwner, createPreviewLimiter, } from "../server/hosting"; import { keepDraft, getDraft, keepReport, getReport } from "../server/sessions"; import { extractLocal, assess } from "../server/context"; import { CASES } from "../server/cases"; const reviewer = "12345678-1234-1234-1234-123456789abc"; const env = { CONTEXT_HOSTED: "true", PORT: "8080", WEBSITE_SITE_NAME: "context-preview", CONTEXT_AUTH_PROVIDER: "azure-easyauth", CONTEXT_REVIEWER_IDS: reviewer, CONTEXT_ALLOWED_ORIGINS: "https://context.marmar.life,https://preview.azurewebsites.net", }; describe("restricted hosted preview", () => { it("fails closed with incomplete hosting configuration", () => { expect(() => hostingConfig({ CONTEXT_HOSTED: "true" })).toThrow(); expect(() => hostingConfig({ ...env, CONTEXT_ALLOWED_ORIGINS: "http://context.marmar.life", }) ).toThrow(); expect(() => hostingConfig({ ...env, PORT: "oops" })).toThrow(); }); it("keeps local mode loopback only", () => { const config = hostingConfig({}); expect(config.bind).toBe("127.0.0.1"); expect( requestOwner(config, { host: "localhost:4191", origin: "http://localhost:4191", }) ).toBe("local"); expect(() => requestOwner(config, { host: "evil.example" })).toThrow(); expect(() => requestOwner(config, { host: "localhost:4191", origin: "https://evil.example", }) ).toThrow(); }); it("requires the exact allowed host, origin and approved principal", () => { const config = hostingConfig(env); const headers = { host: "context.marmar.life", origin: "https://context.marmar.life", "x-ms-client-principal-id": reviewer, }; expect(config.bind).toBe("0.0.0.0"); expect(requestOwner(config, headers)).toBe(reviewer); expect(() => requestOwner(config, { ...headers, host: "other.azurewebsites.net" }) ).toThrow(); expect(() => requestOwner(config, { ...headers, origin: "https://evil.example" }) ).toThrow(); expect(() => requestOwner(config, { ...headers, "x-ms-client-principal-id": "unknown", }) ).toThrow(); expect(() => requestOwner(config, { host: headers.host })).toThrow(); }); it("bounds repeated extractions separately for each reviewer", () => { const limit = createPreviewLimiter(); for (let i = 0; i < 6; i++) limit(reviewer, 1000); expect(() => limit(reviewer, 1000)).toThrow(/limit/); expect(() => limit("other", 1000)).not.toThrow(); expect(() => limit(reviewer, 61_000)).not.toThrow(); }); it("bounds daily calls across minute windows", () => { const limit = createPreviewLimiter(); for (let i = 0; i < 100; i++) limit(reviewer, i * 60_000); expect(() => limit(reviewer, 100 * 60_000)).toThrow(/limit/); expect(() => limit(reviewer, 86_400_000)).not.toThrow(); }); it("isolates drafts and reports between authenticated reviewers", () => { const sources = CASES[0].sources; const draft = keepDraft(extractLocal(sources), sources, reviewer); expect(() => getDraft(draft.id!, sources, "other")).toThrow(); expect(getDraft(draft.id!, sources, reviewer).id).toBe(draft.id); const review = Object.fromEntries( draft.facts.map((f) => [f.id, "include" as const]) ); const assessment = assess(sources, review, draft.fingerprint, true, draft); expect(() => keepReport(draft.id!, assessment, review, "other")).toThrow(); const saved = keepReport(draft.id!, assessment, review, reviewer); expect(() => getReport(saved.reportId!, "other")).toThrow(); expect(getReport(saved.reportId!, reviewer).synthetic).toBe(true); }); });