1import type { IncomingMessage } from "node:http";
2import {
3 proxyRequestHeaders,
4 resolveForwardedHost,
5} from "../forwarded-host.ts";
6import { describe, expect, test } from "vitest";
7
8describe("resolveForwardedHost", () => {
9 test("prefers the forwarded host over the immediate proxy host", () => {
10 expect(
11 resolveForwardedHost(
12 { host: "localhost:5173", "x-forwarded-host": "example.ngrok.app" },
13 "127.0.0.1:9999",
14 ),
15 ).toBe("example.ngrok.app");
16 });
17
18 test("uses the first forwarded host when a proxy chain appends to the header", () => {
19 expect(
20 resolveForwardedHost(
21 {
22 host: "localhost:5173",
23 "x-forwarded-host": "example.ngrok.app, localhost:5173",
24 },
25 "127.0.0.1:9999",
26 ),
27 ).toBe("example.ngrok.app");
28 });
29
30 test("uses the first forwarded host when Node exposes repeated headers as an array", () => {
31 expect(
32 resolveForwardedHost(
33 {
34 host: "localhost:5173",
35 "x-forwarded-host": ["example.ngrok.app", "localhost:5173"],
36 },
37 "127.0.0.1:9999",
38 ),
39 ).toBe("example.ngrok.app");
40 });
41
42 test("falls back to the request host or the provided fallback", () => {
43 expect(
44 resolveForwardedHost({ host: "localhost:5173" }, "127.0.0.1:9999"),
45 ).toBe("localhost:5173");
46 expect(resolveForwardedHost({}, "127.0.0.1:9999")).toBe("127.0.0.1:9999");
47 });
48});
49
50describe("proxyRequestHeaders", () => {
51 const proxySharedSecret = "server-instance-secret";
52 test.each([
53 [{ host: "localhost:5173" }, false, "http://localhost:5173"],
54 [{ host: "localhost:5173" }, true, "https://localhost:5173"],
55 [{ host: "localhost:80" }, true, "https://localhost:80"],
56 [
57 {
58 host: "localhost:5173",
59 "x-forwarded-host": "public.example, proxy.internal",
60 "x-forwarded-proto": "https, http",
61 },
62 false,
63 "https://public.example",
64 ],
65 ])(
66 "signs the original URL including protocol and port",
67 (headers, encrypted, origin) => {
68 const result = proxyRequestHeaders(
69 {
70 headers: {
71 ...headers,
72 "alchemy-runtime-original-url": "https://forged.example/",
73 "alchemy-runtime-proxy-shared-secret": "forged",
74 },
75 socket: { encrypted },
76 } as unknown as IncomingMessage,
77 new URL("http://127.0.0.1:9999//callback/%2F?x=1&x=2"),
78 proxySharedSecret,
79 );
80 expect(result["alchemy-runtime-original-url"]).toBe(
81 `${origin}//callback/%2F?x=1&x=2`,
82 );
83 expect(result.host).toBe(new URL(origin).host);
84 expect(result["alchemy-runtime-proxy-shared-secret"]).toBe(
85 proxySharedSecret,
86 );
87 },
88 );
89});