@beignet/core
Version:
Core framework primitives for Beignet
120 lines • 4.05 kB
JavaScript
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