From 1843bcf1dbde3330e4da56088a106cb804590a2d Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 15 Feb 2026 16:15:53 +0000 Subject: [PATCH] refactor(gateway): share host header parsing --- src/gateway/auth.ts | 18 ++-------------- src/gateway/net.ts | 19 +++++++++++++++++ src/gateway/origin-check.ts | 21 +------------------ .../server/ws-connection/auth-messages.ts | 15 ------------- .../server/ws-connection/message-handler.ts | 7 ++----- 5 files changed, 24 insertions(+), 56 deletions(-) diff --git a/src/gateway/auth.ts b/src/gateway/auth.ts index 5ae84ce715a..3212885c6ac 100644 --- a/src/gateway/auth.ts +++ b/src/gateway/auth.ts @@ -14,6 +14,7 @@ import { import { isLoopbackAddress, isTrustedProxyAddress, + resolveHostName, parseForwardedForClientIp, resolveGatewayClientIp, } from "./net.js"; @@ -56,21 +57,6 @@ function normalizeLogin(login: string): string { return login.trim().toLowerCase(); } -function getHostName(hostHeader?: string): string { - const host = (hostHeader ?? "").trim().toLowerCase(); - if (!host) { - return ""; - } - if (host.startsWith("[")) { - const end = host.indexOf("]"); - if (end !== -1) { - return host.slice(1, end); - } - } - const [name] = host.split(":"); - return name ?? ""; -} - function headerValue(value: string | string[] | undefined): string | undefined { return Array.isArray(value) ? value[0] : value; } @@ -107,7 +93,7 @@ export function isLocalDirectRequest(req?: IncomingMessage, trustedProxies?: str return false; } - const host = getHostName(req.headers?.host); + const host = resolveHostName(req.headers?.host); const hostIsLocal = host === "localhost" || host === "127.0.0.1" || host === "::1"; const hostIsTailscaleServe = host.endsWith(".ts.net"); diff --git a/src/gateway/net.ts b/src/gateway/net.ts index 977102eb766..20f54265169 100644 --- a/src/gateway/net.ts +++ b/src/gateway/net.ts @@ -25,6 +25,25 @@ export function pickPrimaryLanIPv4(): string | undefined { return undefined; } +export function normalizeHostHeader(hostHeader?: string): string { + return (hostHeader ?? "").trim().toLowerCase(); +} + +export function resolveHostName(hostHeader?: string): string { + const host = normalizeHostHeader(hostHeader); + if (!host) { + return ""; + } + if (host.startsWith("[")) { + const end = host.indexOf("]"); + if (end !== -1) { + return host.slice(1, end); + } + } + const [name] = host.split(":"); + return name ?? ""; +} + export function isLoopbackAddress(ip: string | undefined): boolean { if (!ip) { return false; diff --git a/src/gateway/origin-check.ts b/src/gateway/origin-check.ts index 0648bd7393e..50aea0315ec 100644 --- a/src/gateway/origin-check.ts +++ b/src/gateway/origin-check.ts @@ -1,26 +1,7 @@ -import { isLoopbackHost } from "./net.js"; +import { isLoopbackHost, normalizeHostHeader, resolveHostName } from "./net.js"; type OriginCheckResult = { ok: true } | { ok: false; reason: string }; -function normalizeHostHeader(hostHeader?: string): string { - return (hostHeader ?? "").trim().toLowerCase(); -} - -function resolveHostName(hostHeader?: string): string { - const host = normalizeHostHeader(hostHeader); - if (!host) { - return ""; - } - if (host.startsWith("[")) { - const end = host.indexOf("]"); - if (end !== -1) { - return host.slice(1, end); - } - } - const [name] = host.split(":"); - return name ?? ""; -} - function parseOrigin( originRaw?: string, ): { origin: string; host: string; hostname: string } | null { diff --git a/src/gateway/server/ws-connection/auth-messages.ts b/src/gateway/server/ws-connection/auth-messages.ts index 12b167fb2c8..c4900a6eaef 100644 --- a/src/gateway/server/ws-connection/auth-messages.ts +++ b/src/gateway/server/ws-connection/auth-messages.ts @@ -4,21 +4,6 @@ import { GATEWAY_CLIENT_IDS } from "../../protocol/client-info.js"; export type AuthProvidedKind = "token" | "password" | "none"; -export function resolveHostName(hostHeader?: string): string { - const host = (hostHeader ?? "").trim().toLowerCase(); - if (!host) { - return ""; - } - if (host.startsWith("[")) { - const end = host.indexOf("]"); - if (end !== -1) { - return host.slice(1, end); - } - } - const [name] = host.split(":"); - return name ?? ""; -} - export function formatGatewayAuthFailureMessage(params: { authMode: ResolvedGatewayAuth["mode"]; authProvided: AuthProvidedKind; diff --git a/src/gateway/server/ws-connection/message-handler.ts b/src/gateway/server/ws-connection/message-handler.ts index 345c304779a..d533796ecd8 100644 --- a/src/gateway/server/ws-connection/message-handler.ts +++ b/src/gateway/server/ws-connection/message-handler.ts @@ -33,6 +33,7 @@ import { import { authorizeGatewayConnect, isLocalDirectRequest } from "../../auth.js"; import { buildDeviceAuthPayload } from "../../device-auth.js"; import { isLoopbackAddress, isTrustedProxyAddress, resolveGatewayClientIp } from "../../net.js"; +import { resolveHostName } from "../../net.js"; import { resolveNodeCommandAllowlist } from "../../node-command-policy.js"; import { checkBrowserOrigin } from "../../origin-check.js"; import { GATEWAY_CLIENT_IDS } from "../../protocol/client-info.js"; @@ -58,11 +59,7 @@ import { incrementPresenceVersion, refreshGatewayHealthSnapshot, } from "../health-state.js"; -import { - formatGatewayAuthFailureMessage, - resolveHostName, - type AuthProvidedKind, -} from "./auth-messages.js"; +import { formatGatewayAuthFailureMessage, type AuthProvidedKind } from "./auth-messages.js"; type SubsystemLogger = ReturnType;