Files
OmniRoute/open-sse/services/ipFilter.ts
diegosouzapw 71d14209a4 feat: OmniRoute v1.0.0 — Intelligent AI Gateway & Universal LLM Proxy
OmniRoute is an intelligent API gateway that unifies 20+ AI providers behind a single
OpenAI-compatible endpoint. Features include intelligent routing with 6 strategies,
multi-format translation (OpenAI/Claude/Gemini/Responses API), circuit breakers,
semantic caching, combo fallback chains, real-time health monitoring, and a full
dashboard with provider management, analytics, and CLI tool integration.

Key highlights:
- 20+ providers (Claude Code, Codex, Gemini CLI, GitHub Copilot, iFlow, Qwen, Kiro, etc.)
- 6 routing strategies (Fill First, Round Robin, P2C, Random, Least Used, Cost Optimized)
- Export/Import database backup with full archive support
- Translator Playground with 4 modes (Playground, Chat Tester, Test Bench, Live Monitor)
- 100% TypeScript across src/ and open-sse/
- Docker support with multi-stage builds
- Comprehensive documentation and 9 dashboard screenshots
2026-02-18 00:02:15 -03:00

250 lines
6.3 KiB
TypeScript

/**
* IP Filter Middleware — Phase 6
*
* IP-based access control with blacklist, whitelist, priority modes, and temporary bans.
*/
// In-memory IP lists
let _config = {
enabled: false,
mode: "blacklist", // "blacklist" | "whitelist" | "whitelist-priority"
blacklist: new Set(),
whitelist: new Set(),
tempBans: new Map(), // IP → { until: timestamp, reason: string }
};
/**
* Configure the IP filter
*/
export function configureIPFilter(config) {
if (config.enabled !== undefined) _config.enabled = config.enabled;
if (config.mode) _config.mode = config.mode;
if (config.blacklist) _config.blacklist = new Set(config.blacklist);
if (config.whitelist) _config.whitelist = new Set(config.whitelist);
}
/**
* Get current IP filter config (for API)
*/
export function getIPFilterConfig() {
return {
enabled: _config.enabled,
mode: _config.mode,
blacklist: Array.from(_config.blacklist),
whitelist: Array.from(_config.whitelist),
tempBans: Array.from(_config.tempBans.entries()).map(([ip, info]) => ({
ip,
until: new Date(info.until).toISOString(),
reason: info.reason,
remainingMs: Math.max(0, info.until - Date.now()),
})),
};
}
/**
* Check if an IP is allowed
* @param {string} ip
* @returns {{ allowed: boolean, reason?: string }}
*/
export function checkIP(ip) {
if (!_config.enabled) return { allowed: true };
if (!ip) return { allowed: true };
const normalizedIP = normalizeIP(ip);
// Check temp bans first (highest priority)
const ban = _config.tempBans.get(normalizedIP);
if (ban) {
if (Date.now() < ban.until) {
return { allowed: false, reason: `Temporarily banned: ${ban.reason}` };
}
_config.tempBans.delete(normalizedIP); // Expired
}
switch (_config.mode) {
case "whitelist":
// Only whitelisted IPs allowed
if (!matchesAny(normalizedIP, _config.whitelist)) {
return { allowed: false, reason: "IP not in whitelist" };
}
return { allowed: true };
case "whitelist-priority":
// Whitelist overrides blacklist
if (matchesAny(normalizedIP, _config.whitelist)) {
return { allowed: true };
}
if (matchesAny(normalizedIP, _config.blacklist)) {
return { allowed: false, reason: "IP blacklisted" };
}
return { allowed: true };
case "blacklist":
default:
// Blacklisted IPs blocked
if (matchesAny(normalizedIP, _config.blacklist)) {
return { allowed: false, reason: "IP blacklisted" };
}
return { allowed: true };
}
}
/**
* Temporarily ban an IP
*/
export function tempBanIP(ip, durationMs, reason = "Automated ban") {
const normalizedIP = normalizeIP(ip);
_config.tempBans.set(normalizedIP, {
until: Date.now() + durationMs,
reason,
});
}
/**
* Remove a temporary ban
*/
export function removeTempBan(ip) {
_config.tempBans.delete(normalizeIP(ip));
}
/**
* Add IP to blacklist
*/
export function addToBlacklist(ip) {
_config.blacklist.add(normalizeIP(ip));
}
/**
* Remove IP from blacklist
*/
export function removeFromBlacklist(ip) {
_config.blacklist.delete(normalizeIP(ip));
}
/**
* Add IP to whitelist
*/
export function addToWhitelist(ip) {
_config.whitelist.add(normalizeIP(ip));
}
/**
* Remove IP from whitelist
*/
export function removeFromWhitelist(ip) {
_config.whitelist.delete(normalizeIP(ip));
}
/**
* Express/Next.js middleware factory
*/
export function createIPFilterMiddleware() {
return (req, res, next) => {
const ip = extractClientIP(req);
const { allowed, reason } = checkIP(ip);
if (!allowed) {
const statusCode = 403;
if (res.status) {
// Express
return res.status(statusCode).json({ error: reason || "Access denied" });
}
// Raw response
res.writeHead(statusCode, { "Content-Type": "application/json" });
return res.end(JSON.stringify({ error: reason || "Access denied" }));
}
if (next) next();
};
}
/**
* For Next.js App Router — check IP from request object
*/
export function checkRequestIP(request) {
const ip =
request.headers?.get?.("x-forwarded-for")?.split(",")[0].trim() ||
request.headers?.get?.("x-real-ip") ||
request.headers?.get?.("cf-connecting-ip") ||
request.ip ||
"unknown";
return checkIP(ip);
}
// ─── Internal Helpers ───────────────────────────────────────────────────────
function normalizeIP(ip) {
if (!ip) return "";
// Remove IPv6 prefix from IPv4-mapped addresses
return ip.replace(/^::ffff:/, "").trim();
}
function matchesAny(ip, ipSet) {
// Direct match
if (ipSet.has(ip)) return true;
// CIDR match
for (const entry of ipSet) {
if (entry.includes("/") && matchesCIDR(ip, entry)) return true;
// Wildcard match (e.g., "192.168.*.*")
if (entry.includes("*") && matchesWildcard(ip, entry)) return true;
}
return false;
}
function matchesCIDR(ip, cidr) {
try {
const [range, bits] = cidr.split("/");
const mask = parseInt(bits, 10);
if (isNaN(mask) || mask < 0 || mask > 32) return false;
const ipNum = ipToNum(ip);
const rangeNum = ipToNum(range);
if (ipNum === null || rangeNum === null) return false;
const maskBits = (-1 << (32 - mask)) >>> 0;
return (ipNum & maskBits) === (rangeNum & maskBits);
} catch {
return false;
}
}
function ipToNum(ip) {
const parts = ip.split(".");
if (parts.length !== 4) return null;
let num = 0;
for (const p of parts) {
const n = parseInt(p, 10);
if (isNaN(n) || n < 0 || n > 255) return null;
num = (num << 8) | n;
}
return num >>> 0;
}
function matchesWildcard(ip, pattern) {
const ipParts = ip.split(".");
const patParts = pattern.split(".");
if (ipParts.length !== 4 || patParts.length !== 4) return false;
return ipParts.every((part, i) => patParts[i] === "*" || part === patParts[i]);
}
function extractClientIP(req) {
return (
req.headers?.["x-forwarded-for"]?.split(",")[0].trim() ||
req.headers?.["x-real-ip"] ||
req.headers?.["cf-connecting-ip"] ||
req.socket?.remoteAddress ||
req.ip ||
"unknown"
);
}
/**
* Reset config (for testing)
*/
export function resetIPFilter() {
_config = {
enabled: false,
mode: "blacklist",
blacklist: new Set(),
whitelist: new Set(),
tempBans: new Map(),
};
}