refactor(gateway): extract ws auth message helpers

This commit is contained in:
Peter Steinberger
2026-02-13 16:48:06 +00:00
parent 5429f2e635
commit a79c2de956
2 changed files with 84 additions and 76 deletions

View File

@@ -0,0 +1,79 @@
import type { ResolvedGatewayAuth } from "../../auth.js";
import { isGatewayCliClient, isWebchatClient } from "../../../utils/message-channel.js";
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;
reason?: string;
client?: { id?: string | null; mode?: string | null };
}): string {
const { authMode, authProvided, reason, client } = params;
const isCli = isGatewayCliClient(client);
const isControlUi = client?.id === GATEWAY_CLIENT_IDS.CONTROL_UI;
const isWebchat = isWebchatClient(client);
const uiHint = "open the dashboard URL and paste the token in Control UI settings";
const tokenHint = isCli
? "set gateway.remote.token to match gateway.auth.token"
: isControlUi || isWebchat
? uiHint
: "provide gateway auth token";
const passwordHint = isCli
? "set gateway.remote.password to match gateway.auth.password"
: isControlUi || isWebchat
? "enter the password in Control UI settings"
: "provide gateway auth password";
switch (reason) {
case "token_missing":
return `unauthorized: gateway token missing (${tokenHint})`;
case "token_mismatch":
return `unauthorized: gateway token mismatch (${tokenHint})`;
case "token_missing_config":
return "unauthorized: gateway token not configured on gateway (set gateway.auth.token)";
case "password_missing":
return `unauthorized: gateway password missing (${passwordHint})`;
case "password_mismatch":
return `unauthorized: gateway password mismatch (${passwordHint})`;
case "password_missing_config":
return "unauthorized: gateway password not configured on gateway (set gateway.auth.password)";
case "tailscale_user_missing":
return "unauthorized: tailscale identity missing (use Tailscale Serve auth or gateway token/password)";
case "tailscale_proxy_missing":
return "unauthorized: tailscale proxy headers missing (use Tailscale Serve or gateway token/password)";
case "tailscale_whois_failed":
return "unauthorized: tailscale identity check failed (use Tailscale Serve auth or gateway token/password)";
case "tailscale_user_mismatch":
return "unauthorized: tailscale identity mismatch (use Tailscale Serve auth or gateway token/password)";
case "rate_limited":
return "unauthorized: too many failed authentication attempts (retry later)";
case "device_token_mismatch":
return "unauthorized: device token mismatch (rotate/reissue device token)";
default:
break;
}
if (authMode === "token" && authProvided === "none") {
return `unauthorized: gateway token missing (${tokenHint})`;
}
if (authMode === "password" && authProvided === "none") {
return `unauthorized: gateway password missing (${passwordHint})`;
}
return "unauthorized";
}

View File

@@ -58,87 +58,16 @@ import {
incrementPresenceVersion,
refreshGatewayHealthSnapshot,
} from "../health-state.js";
import {
formatGatewayAuthFailureMessage,
resolveHostName,
type AuthProvidedKind,
} from "./auth-messages.js";
type SubsystemLogger = ReturnType<typeof createSubsystemLogger>;
const DEVICE_SIGNATURE_SKEW_MS = 10 * 60 * 1000;
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 ?? "";
}
type AuthProvidedKind = "token" | "password" | "none";
function formatGatewayAuthFailureMessage(params: {
authMode: ResolvedGatewayAuth["mode"];
authProvided: AuthProvidedKind;
reason?: string;
client?: { id?: string | null; mode?: string | null };
}): string {
const { authMode, authProvided, reason, client } = params;
const isCli = isGatewayCliClient(client);
const isControlUi = client?.id === GATEWAY_CLIENT_IDS.CONTROL_UI;
const isWebchat = isWebchatClient(client);
const uiHint = "open the dashboard URL and paste the token in Control UI settings";
const tokenHint = isCli
? "set gateway.remote.token to match gateway.auth.token"
: isControlUi || isWebchat
? uiHint
: "provide gateway auth token";
const passwordHint = isCli
? "set gateway.remote.password to match gateway.auth.password"
: isControlUi || isWebchat
? "enter the password in Control UI settings"
: "provide gateway auth password";
switch (reason) {
case "token_missing":
return `unauthorized: gateway token missing (${tokenHint})`;
case "token_mismatch":
return `unauthorized: gateway token mismatch (${tokenHint})`;
case "token_missing_config":
return "unauthorized: gateway token not configured on gateway (set gateway.auth.token)";
case "password_missing":
return `unauthorized: gateway password missing (${passwordHint})`;
case "password_mismatch":
return `unauthorized: gateway password mismatch (${passwordHint})`;
case "password_missing_config":
return "unauthorized: gateway password not configured on gateway (set gateway.auth.password)";
case "tailscale_user_missing":
return "unauthorized: tailscale identity missing (use Tailscale Serve auth or gateway token/password)";
case "tailscale_proxy_missing":
return "unauthorized: tailscale proxy headers missing (use Tailscale Serve or gateway token/password)";
case "tailscale_whois_failed":
return "unauthorized: tailscale identity check failed (use Tailscale Serve auth or gateway token/password)";
case "tailscale_user_mismatch":
return "unauthorized: tailscale identity mismatch (use Tailscale Serve auth or gateway token/password)";
case "rate_limited":
return "unauthorized: too many failed authentication attempts (retry later)";
case "device_token_mismatch":
return "unauthorized: device token mismatch (rotate/reissue device token)";
default:
break;
}
if (authMode === "token" && authProvided === "none") {
return `unauthorized: gateway token missing (${tokenHint})`;
}
if (authMode === "password" && authProvided === "none") {
return `unauthorized: gateway password missing (${passwordHint})`;
}
return "unauthorized";
}
export function attachGatewayWsMessageHandler(params: {
socket: WebSocket;
upgradeReq: IncomingMessage;