@kitstack/nest-powertools
Version:
A comprehensive collection of NestJS powertools, decorators, and utilities to supercharge your backend development
116 lines • 4.59 kB
JavaScript
"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