UNPKG

@kitstack/nest-powertools

Version:

A comprehensive collection of NestJS powertools, decorators, and utilities to supercharge your backend development

116 lines 4.59 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); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.RateLimitGuard = void 0; const common_1 = require("@nestjs/common"); let RateLimitGuard = class RateLimitGuard { constructor(options = {}) { this.options = options; this.requests = new Map(); this.options = { windowMs: 15 * 60 * 1000, max: 100, strategy: 'delay', delayMs: 1000, maxDelayMs: 30000, delayMultiplier: 1.5, message: 'Rate limit exceeded. Request delayed.', ...options, }; if (!this.options.delayAfter) { this.options.delayAfter = Math.floor(this.options.max * 0.8); } } async canActivate(context) { const request = context.switchToHttp().getRequest(); const key = this.getKey(request); const now = Date.now(); let record = this.requests.get(key); if (!record || now > record.resetTime) { record = { count: 1, resetTime: now + this.options.windowMs, lastRequest: now, }; this.requests.set(key, record); return true; } record.count++; record.lastRequest = now; if (this.options.strategy === 'reject') { return this.handleRejectStrategy(record); } else { return await this.handleDelayStrategy(record); } } handleRejectStrategy(record) { if (record.count > this.options.max) { throw new common_1.HttpException(this.options.message, common_1.HttpStatus.TOO_MANY_REQUESTS); } return true; } async handleDelayStrategy(record) { if (record.count <= this.options.delayAfter) { return true; } const excessRequests = record.count - this.options.delayAfter; const baseDelay = this.options.delayMs; const multiplier = this.options.delayMultiplier; let delay = baseDelay * Math.pow(multiplier, excessRequests - 1); delay = Math.min(delay, this.options.maxDelayMs); const jitter = delay * 0.1 * (Math.random() - 0.5); delay = Math.max(0, delay + jitter); if (this.options.enableLogging) { console.log(`Rate limit delay applied: ${Math.round(delay)}ms for ${this.getKey(record)}`); } await this.sleep(delay); return true; } sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } getKey(request) { return request.ip || request.connection.remoteAddress || 'unknown'; } getStatus(request) { const key = this.getKey(request); const record = this.requests.get(key); const now = Date.now(); if (!record || now > record.resetTime) { return { requests: 0, remaining: this.options.max, resetTime: now + this.options.windowMs, resetIn: this.options.windowMs, strategy: this.options.strategy, willDelay: false, }; } const remaining = Math.max(0, this.options.max - record.count); const willDelay = this.options.strategy === 'delay' && record.count > this.options.delayAfter; return { requests: record.count, remaining, resetTime: record.resetTime, resetIn: record.resetTime - now, strategy: this.options.strategy, willDelay, }; } }; exports.RateLimitGuard = RateLimitGuard; exports.RateLimitGuard = RateLimitGuard = __decorate([ (0, common_1.Injectable)(), __metadata("design:paramtypes", [Object]) ], RateLimitGuard); //# sourceMappingURL=rate-limit.guard.js.map