UNPKG

@beignet/core

Version:

Core framework primitives for Beignet

120 lines 4.05 kB
import { parseHttpRequestUrl, requireTrustedProxyHeaderName, } from "./trusted-proxy-internal.js"; const DEFAULT_PROTOCOL_HEADER = "x-forwarded-proto"; const DEFAULT_HOST_HEADER = "x-forwarded-host"; function splitForwardedHeader(value) { if (!value) return []; return value .split(",") .map((entry) => entry.trim()) .filter(Boolean); } function firstHeaderValue(req, header) { return splitForwardedHeader(req.headers.get(header))[0]; } function normalizeProtocol(value) { if (!value) return undefined; const protocol = value.toLowerCase().replace(/:$/, ""); if (protocol === "http" || protocol === "https") return protocol; return undefined; } function hasInvalidHostCharacter(value) { for (const char of value) { const code = char.charCodeAt(0); if (code <= 32 || code === 127 || char === "/" || char === "\\" || char === "@" || char === "?" || char === "#") { return true; } } return false; } function normalizeHost(value, protocol) { if (!value || hasInvalidHostCharacter(value)) { return undefined; } try { return new URL(`${protocol}://${value}`).host; } catch { return undefined; } } function baseRequestUrl(req) { return parseHttpRequestUrl(req.url); } function normalizedBaseProtocol(url) { const protocol = normalizeProtocol(url.protocol); if (protocol) return protocol; throw new Error("Resolved request URL must use HTTP or HTTPS."); } /** * Resolve a client IP from a configured trusted proxy source. */ export function resolveTrustedClientIp(req, source) { if (!source) return undefined; if (typeof source === "function") { return source(req)?.trim() || undefined; } if (typeof source === "object") { return firstHeaderValue(req, requireTrustedProxyHeaderName(source.header, "trustedProxy.clientIp.header")); } if (source === "x-forwarded-for-first" || source === "x-forwarded-for-last") { const entries = splitForwardedHeader(req.headers.get("x-forwarded-for")); if (entries.length === 0) return undefined; return source === "x-forwarded-for-first" ? entries[0] : entries[entries.length - 1]; } return firstHeaderValue(req, source); } /** * Resolve request metadata using only app-visible URL data unless a trusted * proxy policy is explicitly configured. */ export function resolveTrustedRequest(req, config = false) { const baseUrl = baseRequestUrl(req); const baseProtocol = normalizedBaseProtocol(baseUrl); const baseHost = baseUrl.host; if (!config) { return { url: baseUrl, origin: baseUrl.origin, protocol: baseProtocol, host: baseHost, clientIpTrusted: false, trustedProxy: false, }; } const protocolHeader = config.protocolHeader === false ? undefined : requireTrustedProxyHeaderName(config.protocolHeader ?? DEFAULT_PROTOCOL_HEADER, "trustedProxy.protocolHeader"); const hostHeader = config.hostHeader === false ? undefined : requireTrustedProxyHeaderName(config.hostHeader ?? DEFAULT_HOST_HEADER, "trustedProxy.hostHeader"); const protocol = normalizeProtocol(protocolHeader ? firstHeaderValue(req, protocolHeader) : undefined) ?? baseProtocol; const host = normalizeHost(hostHeader ? firstHeaderValue(req, hostHeader) : undefined, protocol) ?? baseHost; const url = new URL(baseUrl); url.protocol = `${protocol}:`; url.host = host; const clientIp = resolveTrustedClientIp(req, config.clientIp); return { url, origin: url.origin, protocol, host, ...(clientIp !== undefined ? { clientIp } : {}), clientIpTrusted: Boolean(config.clientIp), trustedProxy: true, }; } //# sourceMappingURL=trusted-proxy.js.map