// Security guard CORE for the localhost MCP (:8765). Pure functions — no I/O — so they're unit-testable
// and both the MCP surface and any future surface call the SAME logic (parity principle).
//
// Closes two holes on a no-auth localhost server:
//  (1) DNS-rebinding / malicious web pages — browsers always send an Origin, and a rebinding attack carries
//      a non-loopback Host. Legit MCP clients (Claude Code, curl) send no browser Origin and a loopback Host,
//      so they pass untouched.
//  (2) other local processes — only when a shared secret (DEWX_MCP_TOKEN) is configured (opt-in).

/// True if a host[:port] string names the loopback interface (or is absent → don't reject).
/// Handles: bare hostname, host:port, bracketed IPv6 `[::1]:port`, and bare IPv6 `::1`.
export function isLoopbackHost(h?: string): boolean {
  if (!h) return true;                                   // Host absent → loopback (curl)
  let name = h.trim().toLowerCase();
  if (name.startsWith("[")) {                            // [::1]:8765 → ::1
    name = name.slice(1, name.indexOf("]") < 0 ? undefined : name.indexOf("]"));
  } else if (name.split(":").length === 2) {             // host:port (single colon) → strip port
    name = name.split(":")[0];
  }                                                      // else: bare IPv6 (multi-colon) / bare host → as-is
  return name === "127.0.0.1" || name === "localhost" || name === "::1";
}

export interface GuardHeaders {
  origin?: string | string[];
  host?: string | string[];
  authorization?: string | string[];
  "x-dewx-token"?: string | string[];
}
const first = (v?: string | string[]) => (Array.isArray(v) ? v[0] : v);

/// Returns a rejection reason string if the request must be REJECTED, else null (allowed).
/// `token` = the configured shared secret ("" = none required).
export function guardRequest(headers: GuardHeaders, token: string): string | null {
  const origin = first(headers["origin"]);
  if (origin) {                                          // present only from browsers
    try { const u = new URL(origin); if (!isLoopbackHost(u.host)) return "cross-origin request rejected"; }
    catch { return "invalid Origin header"; }
  }
  if (!isLoopbackHost(first(headers["host"]))) return "non-loopback Host rejected (DNS-rebinding guard)";
  if (token) {
    const auth = first(headers["authorization"]) || "";
    const tok = auth.replace(/^Bearer\s+/i, "") || first(headers["x-dewx-token"]) || "";
    if (tok !== token) return "missing or invalid auth token";
  }
  return null;
}
