/**
 * SSRF guard for outbound webhook URLs.
 *
 * Validates that a target URL is not pointing to internal/private infrastructure
 * before allowing a webhook to be stored or dispatched.
 */
import { lookup } from "node:dns/promises";
import { TRPCError } from "@trpc/server";

/** Regex patterns matching IP ranges that must not be targeted. */
const BLOCKED_IP_PATTERNS: RegExp[] = [
  // Loopback IPv4
  /^127\./,
  // Loopback IPv6
  /^::1$/,
  // RFC 1918 private
  /^10\./,
  /^172\.(1[6-9]|2\d|3[01])\./,
  /^192\.168\./,
  // Link-local
  /^169\.254\./,
  // Cloud metadata (AWS, GCP, Azure)
  /^100\.64\./,
];

/** Hostnames that must never be resolved or contacted. */
const BLOCKED_HOSTNAMES = new Set([
  "localhost",
  "metadata.google.internal",
  "169.254.169.254",
]);

function isBlockedIp(ip: string): boolean {
  return BLOCKED_IP_PATTERNS.some((re) => re.test(ip));
}

/**
 * Throws a TRPCError if the given URL targets internal/private infrastructure.
 * Performs DNS resolution to catch attempts to bypass hostname checks.
 */
export async function assertWebhookUrlAllowed(urlString: string): Promise<void> {
  let parsed: URL;
  try {
    parsed = new URL(urlString);
  } catch {
    throw new TRPCError({ code: "BAD_REQUEST", message: "Invalid webhook URL." });
  }

  if (parsed.protocol !== "https:") {
    throw new TRPCError({ code: "BAD_REQUEST", message: "Webhook URLs must use HTTPS." });
  }

  const hostname = parsed.hostname.toLowerCase();

  if (BLOCKED_HOSTNAMES.has(hostname)) {
    throw new TRPCError({ code: "BAD_REQUEST", message: "Webhook URL target is not allowed." });
  }

  // Resolve hostname and validate the resulting IP address
  try {
    const { address } = await lookup(hostname);
    if (isBlockedIp(address) || BLOCKED_HOSTNAMES.has(address)) {
      throw new TRPCError({ code: "BAD_REQUEST", message: "Webhook URL target is not allowed." });
    }
  } catch (err) {
    if (err instanceof TRPCError) throw err;
    // DNS resolution failed — block by default (fail-closed)
    throw new TRPCError({ code: "BAD_REQUEST", message: "Webhook URL could not be validated." });
  }
}
