import { expect, test } from "bun:test"; import { createContext } from "@wrnexus/core"; import { getOriginalRequestMethod, getOriginalRequestOrigin, getOriginalRequestPath, getOriginalRequestUrl, redirectToLogin, stableStringify, } from "../src/index.ts"; test("derives the shared workspace root domain from configured public app origins", async () => { const previous = process.env.WRNEXUS_WORKSPACE_ORIGINS; process.env.WRNEXUS_WORKSPACE_ORIGINS = JSON.stringify({ admin: "https://admin.staging.example.com", citizen: "https://citizen.staging.example.com", sso: "https://sso.staging.example.com", }); try { const { workspaceRootDomain } = await import("../src/workspace.ts"); expect(workspaceRootDomain()).toBe("staging.example.com"); } finally { if (previous === undefined) delete process.env.WRNEXUS_WORKSPACE_ORIGINS; else process.env.WRNEXUS_WORKSPACE_ORIGINS = previous; } }); function context(url: string, headers: HeadersInit = {}) { const parsed = new URL(url); return createContext(new Request(parsed, { headers }), parsed); } test("uses the direct context URL when no gateway headers exist", () => { const ctx = context("https://app.example.test/account?tab=security"); expect(getOriginalRequestUrl(ctx).href).toBe("https://app.example.test/account?tab=security"); expect(getOriginalRequestOrigin(ctx)).toBe("https://app.example.test"); expect(getOriginalRequestPath(ctx)).toBe("/account?tab=security"); expect(getOriginalRequestMethod(ctx)).toBe("GET"); }); test("reconstructs an allowed original gateway URL", () => { const ctx = context("http://sso.localhost:3000/api/verify", { "x-forwarded-host": "admin.localhost:3000", "x-forwarded-proto": "http", "x-original-method": "GET", "x-original-uri": "/users?page=2", }); const url = getOriginalRequestUrl(ctx, { allowedHosts: ["admin.localhost:3000"] }); expect(url.href).toBe("http://admin.localhost:3000/users?page=2"); expect(getOriginalRequestMethod(ctx)).toBe("GET"); }); test("keeps the protected app URL when the SSO verifier crosses the gateway again", () => { const ctx = context("http://sso.localhost:3000/api/verify", { "x-forwarded-host": "sso.localhost:3000", "x-forwarded-proto": "http", "x-original-host": "admin.localhost:3000", "x-original-proto": "http", "x-original-method": "GET", "x-original-uri": "/settings?tab=security", }); const response = redirectToLogin(ctx, "/login", { allowedHosts: ["admin.localhost:3000"], }); const location = new URL(response.headers.get("location")!); expect(location.origin).toBe("http://sso.localhost:3000"); expect(location.pathname).toBe("/login"); expect(location.searchParams.get("returnTo")).toBe( "http://admin.localhost:3000/settings?tab=security", ); }); test("rejects untrusted hosts and unsafe request paths", () => { const untrusted = context("http://sso.localhost/api/verify", { "x-forwarded-host": "evil.example", "x-original-uri": "/", }); const unsafePath = context("http://sso.localhost/api/verify", { "x-forwarded-host": "admin.localhost", "x-original-uri": "//evil.example/steal", }); expect(() => getOriginalRequestUrl(untrusted)).toThrow("Untrusted forwarded host"); expect(() => getOriginalRequestUrl(unsafePath, { allowedHosts: ["admin.localhost"] })).toThrow( "absolute request path", ); }); test("creates a safe login redirect with an encoded returnTo URL", () => { const ctx = context("http://sso.localhost:3000/api/verify", { "x-forwarded-host": "admin.localhost:3000", "x-forwarded-proto": "http", "x-original-uri": "/reports?range=week", }); const response = redirectToLogin(ctx, "/login", { allowedHosts: new Set(["admin.localhost:3000"]), }); const location = new URL(response.headers.get("location")!); expect(response.status).toBe(302); expect(location.origin).toBe("http://sso.localhost:3000"); expect(location.pathname).toBe("/login"); expect(location.searchParams.get("returnTo")).toBe( "http://admin.localhost:3000/reports?range=week", ); }); test("relative login redirects use the configured public app origin", () => { const previous = process.env.WRNEXUS_APP_ORIGIN; process.env.WRNEXUS_APP_ORIGIN = "https://sso.example.test"; try { const ctx = context("http://127.0.0.1:3002/api/verify", { "x-original-host": "admin.example.test", "x-original-proto": "https", "x-original-uri": "/settings", }); const response = redirectToLogin(ctx, "/sign-in", { allowedHosts: ["admin.example.test"], }); const location = new URL(response.headers.get("location")!); expect(location.origin).toBe("https://sso.example.test"); expect(location.pathname).toBe("/sign-in"); expect(location.searchParams.get("returnTo")).toBe("https://admin.example.test/settings"); } finally { if (previous === undefined) delete process.env.WRNEXUS_APP_ORIGIN; else process.env.WRNEXUS_APP_ORIGIN = previous; } }); test("supports an allowed-host callback and custom response options", () => { const ctx = context("https://login.example.test/api/verify", { "x-forwarded-host": "reports.example.test", "x-forwarded-proto": "https", "x-original-uri": "/", }); const response = redirectToLogin(ctx, "https://login.example.test/sign-in?tenant=acme", { allowedHosts: (host) => host.endsWith(".example.test"), returnToParam: "next", status: 303, }); const location = new URL(response.headers.get("location")!); expect(response.status).toBe(303); expect(location.searchParams.get("tenant")).toBe("acme"); expect(location.searchParams.get("next")).toBe("https://reports.example.test/"); }); test("stableStringify permits repeated references but rejects cycles", () => { const shared = { value: 1 }; expect(stableStringify({ second: shared, first: shared })).toBe( '{"first":{"value":1},"second":{"value":1}}', ); const circular: { self?: unknown } = {}; circular.self = circular; expect(() => stableStringify(circular)).toThrow("circular"); });