import { test } from "node:test"; import assert from "node:assert/strict"; import { requestOrigin, verifyOrigin } from "../lib/request-origin"; const internalUrl = "http://localhost:3000/api/v1/tokens"; const makeRequest = ( host: string, origin?: string, extra: Record = {}, ) => new Request(internalUrl, { method: "POST", headers: { host, "content-type": "application/json", ...(origin === undefined ? {} : { origin }), ...extra, }, }); test("the testing switch accepts any origin only when explicitly enabled", () => { const request = makeRequest("localhost:3000", "https://test-domain.example"); assert.doesNotThrow(() => verifyOrigin(request, { allowAnyOrigin: true })); for (const configuration of [{}, { allowAnyOrigin: false }]) { assert.throws( () => verifyOrigin(request, configuration), /Invalid request origin/, ); } }); test("the environment switch requires the exact value true", () => { const previous = process.env.MINEGEO_ALLOW_ANY_ORIGIN; const request = makeRequest("localhost:3000", "https://test-domain.example"); try { for (const value of [undefined, "false", "0", "TRUE", "true"]) { if (value === undefined) delete process.env.MINEGEO_ALLOW_ANY_ORIGIN; else process.env.MINEGEO_ALLOW_ANY_ORIGIN = value; if (value === "true") assert.doesNotThrow(() => verifyOrigin(request)); else assert.throws(() => verifyOrigin(request), /Invalid request origin/); } } finally { if (previous === undefined) delete process.env.MINEGEO_ALLOW_ANY_ORIGIN; else process.env.MINEGEO_ALLOW_ANY_ORIGIN = previous; } }); test("the testing switch retains JSON checks for requests without an origin", () => { assert.throws( () => verifyOrigin( new Request(internalUrl, { method: "POST", headers: { "content-type": "application/x-www-form-urlencoded" }, }), { allowAnyOrigin: true }, ), /JSON request required/, ); }); test("accepts a Mac LAN origin when Next uses localhost internally", () => { const request = makeRequest("192.168.1.50:3000", "http://192.168.1.50:3000"); assert.equal(requestOrigin(request), "http://192.168.1.50:3000"); assert.equal( requestOrigin(request, "http://localhost:3000/"), "http://192.168.1.50:3000", ); assert.doesNotThrow(() => verifyOrigin(request, { websiteUrl: "http://localhost:3000/" }), ); }); test("matches ports exactly and rejects cross-site and internal-address origins", () => { for (const origin of [ "http://192.168.1.50:3001", "https://192.168.1.50:3000", "https://unrelated.example", "http://localhost:3000", ]) { assert.throws( () => verifyOrigin(makeRequest("192.168.1.50:3000", origin), {}), /Invalid request origin/, ); } }); test("normalizes explicit public URLs and supports configured proxy origins", () => { assert.doesNotThrow(() => verifyOrigin(makeRequest("localhost:3000", "https://minegeo.example"), { websiteUrl: "https://minegeo.example/", }), ); assert.doesNotThrow(() => verifyOrigin(makeRequest("localhost:3000", "https://preview.example"), { additionalOrigins: " https://preview.example/ , https://other.example ", }), ); assert.throws( () => verifyOrigin(makeRequest("localhost:3000", "https://unrelated.example"), { websiteUrl: "https://minegeo.example/", }), /Invalid request origin/, ); }); test("forwarded hosts cannot authorize an unrelated origin", () => { assert.throws( () => verifyOrigin( makeRequest("192.168.1.50:3000", "https://unrelated.example", { "x-forwarded-host": "unrelated.example", "x-forwarded-proto": "https", }), {}, ), /Invalid request origin/, ); }); test("login redirects preserve a configured public host's HTTPS scheme", () => { assert.equal( requestOrigin(makeRequest("minegeo.example"), "https://minegeo.example"), "https://minegeo.example", ); }); test("handles IPv6 and default ports without authorizing malformed authorities", () => { assert.doesNotThrow(() => verifyOrigin(makeRequest("[::1]:3000", "http://[::1]:3000"), {}), ); assert.doesNotThrow(() => verifyOrigin( makeRequest("minegeo.example:80", "http://minegeo.example"), {}, ), ); for (const host of [ "trusted.example@unrelated.example", "trusted.example/path", "trusted.example,unrelated.example", "trusted.example:invalid-port", ]) assert.throws( () => requestOrigin(makeRequest(host)), /Invalid request origin/, ); }); test("keeps Java JSON and bearer requests usable without a browser Origin", () => { assert.doesNotThrow(() => verifyOrigin(makeRequest("localhost:3000"), {})); assert.doesNotThrow(() => verifyOrigin( new Request(internalUrl, { method: "POST", headers: { authorization: "Bearer test" }, }), {}, ), ); assert.throws( () => verifyOrigin( new Request(internalUrl, { method: "POST", headers: { "content-type": "application/x-www-form-urlencoded" }, }), {}, ), /JSON request required/, ); }); test("opaque, credential-bearing, and path-bearing origins cannot bypass validation", () => { for (const origin of [ "null", "http://name:password@localhost:3000", "http://localhost:3000/extra", "http://localhost:3000?extra=1", ]) { assert.throws( () => verifyOrigin(makeRequest("localhost:3000", origin), {}), /Invalid request origin/, ); } });