import fetch from "cross-fetch"; import { APIFenceConfig, APIFenceEvent } from "./types"; import { GeoBlockingModule } from "./modules/GeoBlockingModule"; import { IPRestrictionModule } from "./modules/IPRestrictionModule"; import { RateLimitingModule } from "./modules/RateLimitingModule"; import { BotDetectionModule } from "./modules/BotDetectionModule"; import { AnomalyDetectionModule } from "./modules/AnomalyDetectionModule"; import { RequestValidationModule } from "./modules/RequestValidationModule"; export class APIFenceAgent { private static config: APIFenceConfig; private static queue: APIFenceEvent[] = []; private static timer: NodeJS.Timeout; private static syncTimer: NodeJS.Timeout; // Modules private static geoBlocking: GeoBlockingModule; private static ipRestriction: IPRestrictionModule; private static rateLimiting: RateLimitingModule; private static botDetection: BotDetectionModule; private static anomalyDetection: AnomalyDetectionModule; private static requestValidation: RequestValidationModule; static init(config: APIFenceConfig) { this.config = { endpoint: "https://api.apifence.cloud/api/api-monitor", // default base maskFields: ["password", "token", ...(config.maskFields || [])], ...config, }; // Instantiate Modules this.geoBlocking = new GeoBlockingModule(); this.ipRestriction = new IPRestrictionModule(); this.rateLimiting = new RateLimitingModule(); this.botDetection = new BotDetectionModule(); this.anomalyDetection = new AnomalyDetectionModule(); this.requestValidation = new RequestValidationModule(); this.startQueue(); this.startSync(); } static async protect(req: any): Promise<{ action: 'ALLOW' | 'BLOCK'; reason?: string; countryCode?: string; countryName?: string; }> { if (!this.config) return { action: 'ALLOW' }; const ip = req.ip || req.socket.remoteAddress; const userAgent = req.headers['user-agent']; // 1. IP Restriction if (this.ipRestriction.isBlocked(ip)) { return { action: 'BLOCK', reason: 'IP_BLOCKING' }; } // 2. Bot Detection if (this.botDetection.isBot(userAgent)) { return { action: 'BLOCK', reason: 'BOT_DETECTED' }; } // 3. Geo Blocking (Async if remote, but for now blocking sync based on rules) // Assuming we have IP->Country mapping utility or header const countryCode = req.headers['cf-ipcountry'] || req.headers['x-country-code'] || 'UNKNOWN'; const countryName = req.headers['cf-ipcountry-name'] || req.headers['x-country-name']; if (this.geoBlocking.isBlocked(countryCode as string)) { return { action: 'BLOCK', reason: 'GEO_BLOCKING', countryCode: countryCode as string, countryName: countryName as string }; } // 4. Rate Limiting // Key could be IP or API Key const key = ip; if (!this.rateLimiting.check(key)) { return { action: 'BLOCK', reason: 'RATE_LIMIT' }; } // 5. Request Validation const validation = this.requestValidation.validate(req); if (!validation.valid) { return { action: 'BLOCK', reason: 'REQUEST_VALIDATION' }; } return { action: 'ALLOW' }; } static addEvent(event: APIFenceEvent) { if (!this.config) { return; } this.queue.push(event); } private static transformEvent(event: APIFenceEvent): any { // ... (Previous logic) // For brevity, assuming this logic remains or is imported. // To avoid deleting it, I will keep the original implementation below if possible, // or rewrite it fully. Since this is replace, I must rewrite. const isError = event.type === "error"; const transformed: any = { apiKey: this.config.apiKey, eventType: event.type, route: event.path, method: event.method || "GET", statusCode: event.statusCode || (isError ? 500 : 200), responseTime: event.duration || 0, timestamp: event.timestamp, ip: event.ip, userAgent: event.userAgent, service: this.config.serviceName, metadata: { source: "sdk", version: "0.1.6" } }; if (this.config.environment) transformed.environment = this.config.environment; // ... Simplified for now if (isError) { transformed.severity = "critical"; transformed.payload = { error: event.message, stack: event.stack }; } else { transformed.severity = "low"; if (event.statusCode && event.statusCode >= 400) transformed.severity = "medium"; if (event.statusCode && event.statusCode >= 500) transformed.severity = "high"; } return transformed; } private static startQueue() { this.timer = setInterval(async () => { if (!this.queue.length) return; const batch = [...this.queue]; this.queue = []; try { const eventsUrl = `${this.config.endpoint}/events/batch`; const transformedBatch = batch.map(event => this.transformEvent(event)); await fetch(eventsUrl, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ events: transformedBatch }) }); } catch (err) { console.error("APIFenceAgent send error:", err); } }, 1500); } private static startSync() { // Periodic sync of rules const syncRules = async () => { try { const configUrl = `${this.config.endpoint}/config`; const res = await fetch(configUrl, { method: 'GET', headers: { 'x-api-key': this.config.apiKey, 'Content-Type': 'application/json' } }); if (res.ok) { const data = await res.json(); if (data && data.rules) { this.updateRules(data.rules); } } } catch (e) { console.error("[APIFence] Sync failed", e); } }; // Initial sync syncRules(); // Interval (e.g. 60s) this.syncTimer = setInterval(syncRules, 60000); } private static updateRules(rules: any[]) { this.geoBlocking.setRules(rules); this.ipRestriction.setRules(rules); this.rateLimiting.setRules(rules); // Others if needed } }