UNPKG

bb-inspired

Version:

Core library for BB-inspired NestJS backend

210 lines 7.91 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var WebsocketRateLimiter_1; Object.defineProperty(exports, "__esModule", { value: true }); exports.WebsocketRateLimiter = void 0; const common_1 = require("@nestjs/common"); const logger_1 = require("../../utils/logger"); let WebsocketRateLimiter = WebsocketRateLimiter_1 = class WebsocketRateLimiter { constructor(options = {}) { this.options = options; this.logger = new logger_1.AppLogger(WebsocketRateLimiter_1.name); this.ipConnections = new Map(); this.clientMessages = new Map(); this.clientSubscriptions = new Map(); this.blockedIps = new Map(); this.ipViolations = new Map(); this.bannedIps = new Map(); this.options = { maxConnectionsPerIp: 10, maxMessagesPerMinute: 120, maxSubscriptionsPerClient: 30, blockDuration: 5 * 60 * 1000, autoDisconnect: true, enableBanning: true, banThreshold: 5, banDuration: 24 * 60 * 60 * 1000, ...options }; setInterval(() => this.cleanup(), 60 * 1000); setInterval(() => this.resetMessageCounters(), 60 * 1000); } isBanned(ip) { if (!this.bannedIps.has(ip)) { return false; } const banExpiry = this.bannedIps.get(ip); if (Date.now() > banExpiry) { this.bannedIps.delete(ip); return false; } return true; } isBlocked(ip) { if (!this.blockedIps.has(ip)) { return false; } const blockExpiry = this.blockedIps.get(ip); if (Date.now() > blockExpiry) { this.blockedIps.delete(ip); return false; } return true; } handleConnection(client) { const ip = this.getClientIp(client); if (this.isBanned(ip)) { this.logger.warn(`Rejected connection from banned IP: ${ip}`); return false; } if (this.isBlocked(ip)) { this.logger.warn(`Rejected connection from blocked IP: ${ip}`); return false; } const currentConnections = this.ipConnections.get(ip) || 0; if (currentConnections >= this.options.maxConnectionsPerIp) { this.logger.warn(`Connection limit exceeded for IP: ${ip}`); this.recordViolation(ip); return false; } this.ipConnections.set(ip, currentConnections + 1); this.clientMessages.set(client.id, { count: 0, reset: Date.now() + 60 * 1000 }); this.clientSubscriptions.set(client.id, new Set()); return true; } handleDisconnect(client) { const ip = this.getClientIp(client); const currentConnections = this.ipConnections.get(ip) || 0; if (currentConnections > 0) { this.ipConnections.set(ip, currentConnections - 1); } this.clientMessages.delete(client.id); this.clientSubscriptions.delete(client.id); } handleMessage(client) { const tracking = this.clientMessages.get(client.id); if (!tracking) { this.clientMessages.set(client.id, { count: 1, reset: Date.now() + 60 * 1000 }); return true; } if (Date.now() > tracking.reset) { tracking.count = 1; tracking.reset = Date.now() + 60 * 1000; return true; } if (tracking.count >= this.options.maxMessagesPerMinute) { this.logger.warn(`Message rate limit exceeded for client: ${client.id}`); this.recordViolation(this.getClientIp(client)); if (this.options.autoDisconnect) { client.disconnect(true); } return false; } tracking.count++; return true; } handleSubscription(client, channel) { const subscriptions = this.clientSubscriptions.get(client.id); if (!subscriptions) { const newSet = new Set([channel]); this.clientSubscriptions.set(client.id, newSet); return true; } if (subscriptions.has(channel)) { return true; } if (subscriptions.size >= this.options.maxSubscriptionsPerClient) { this.logger.warn(`Subscription limit exceeded for client: ${client.id}`); return false; } subscriptions.add(channel); return true; } handleUnsubscription(client, channel) { const subscriptions = this.clientSubscriptions.get(client.id); if (subscriptions) { subscriptions.delete(channel); } } blockIp(ip) { this.blockedIps.set(ip, Date.now() + this.options.blockDuration); this.logger.warn(`Blocked IP ${ip} for ${this.options.blockDuration / 1000} seconds`); } banIp(ip) { this.bannedIps.set(ip, Date.now() + this.options.banDuration); this.logger.warn(`Banned IP ${ip} for ${this.options.banDuration / (1000 * 60 * 60)} hours`); } recordViolation(ip) { this.blockIp(ip); if (!this.options.enableBanning) { return; } const violations = this.ipViolations.get(ip) || { count: 0, lastViolation: 0 }; if (Date.now() - violations.lastViolation > 24 * 60 * 60 * 1000) { violations.count = 1; } else { violations.count++; } violations.lastViolation = Date.now(); this.ipViolations.set(ip, violations); if (violations.count >= this.options.banThreshold) { this.banIp(ip); this.ipViolations.delete(ip); } } resetMessageCounters() { const now = Date.now(); for (const [clientId, tracking] of this.clientMessages.entries()) { if (now > tracking.reset) { tracking.count = 0; tracking.reset = now + 60 * 1000; } } } cleanup() { const now = Date.now(); for (const [ip, expiry] of this.blockedIps.entries()) { if (now > expiry) { this.blockedIps.delete(ip); } } for (const [ip, expiry] of this.bannedIps.entries()) { if (now > expiry) { this.bannedIps.delete(ip); } } for (const [ip, violation] of this.ipViolations.entries()) { if (now - violation.lastViolation > 24 * 60 * 60 * 1000) { this.ipViolations.delete(ip); } } } getClientIp(client) { const xForwardedFor = client.handshake.headers['x-forwarded-for']; if (xForwardedFor) { return xForwardedFor.split(',')[0].trim(); } return client.handshake.address; } }; exports.WebsocketRateLimiter = WebsocketRateLimiter; exports.WebsocketRateLimiter = WebsocketRateLimiter = WebsocketRateLimiter_1 = __decorate([ (0, common_1.Injectable)(), __metadata("design:paramtypes", [Object]) ], WebsocketRateLimiter); //# sourceMappingURL=websocket.rate-limiter.js.map