import "server-only"; import { isIP } from "node:net"; export type ManualIpBlockEntry = { id: string; ip: string; reason?: string | null; expiresAt?: Date | string | null; }; function ipv4ToNumber(value: string) { const parts = value.split(".").map((part) => Number.parseInt(part, 10)); if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part))) { return null; } if (parts.some((part) => part < 0 || part > 255)) { return null; } return ( ((parts[0] << 24) >>> 0) + ((parts[1] << 16) >>> 0) + ((parts[2] << 8) >>> 0) + (parts[3] >>> 0) ) >>> 0; } function normalizePlainIp(value: string) { const normalized = value.trim().replace(/^\[/, "").replace(/\]$/, ""); return isIP(normalized) ? normalized : null; } function normalizeIpv4Cidr(value: string) { const [rawIp, rawPrefix] = value.split("/"); const ip = normalizePlainIp(rawIp || ""); const prefix = Number.parseInt(rawPrefix || "", 10); if (!ip || isIP(ip) !== 4) return null; if (!Number.isInteger(prefix) || prefix < 0 || prefix > 32) return null; return `${ip}/${prefix}`; } export function normalizeManualBlockIp(input: unknown) { const normalized = String(input || "").trim(); if (!normalized) return null; if (normalized.includes("/")) { return normalizeIpv4Cidr(normalized); } return normalizePlainIp(normalized); } function ipv4MatchesCidr(ip: string, cidr: string) { const [rangeIp, rawPrefix] = cidr.split("/"); const ipNumber = ipv4ToNumber(ip); const rangeNumber = ipv4ToNumber(rangeIp || ""); const prefix = Number.parseInt(rawPrefix || "", 10); if (ipNumber == null || rangeNumber == null) return false; if (!Number.isInteger(prefix) || prefix < 0 || prefix > 32) return false; const mask = prefix === 0 ? 0 : (0xffffffff << (32 - prefix)) >>> 0; return (ipNumber & mask) === (rangeNumber & mask); } export function matchManualIpBlock( ip: string | null | undefined, entries: ManualIpBlockEntry[] ) { const normalizedIp = normalizePlainIp(String(ip || "")); if (!normalizedIp) return null; const ipVersion = isIP(normalizedIp); return ( entries.find((entry) => { const rule = normalizeManualBlockIp(entry.ip); if (!rule) return false; if (rule.includes("/")) { return ipVersion === 4 && ipv4MatchesCidr(normalizedIp, rule); } return rule.toLowerCase() === normalizedIp.toLowerCase(); }) || null ); }