From d502f424fed4be2ddafd7afc890c868b74f93c38 Mon Sep 17 00:00:00 2001 From: Bellabuks Date: Sat, 27 Jun 2026 04:18:14 +0100 Subject: [PATCH] Fix tenant quota guard syntax and abuse score service --- .../guards/tenant-quota.guard.ts | 36 +++++++++++++++++++ src/security/abuse/abuse-score.service.ts | 21 +++++++++++ 2 files changed, 57 insertions(+) create mode 100644 src/rate-limiting/guards/tenant-quota.guard.ts create mode 100644 src/security/abuse/abuse-score.service.ts diff --git a/src/rate-limiting/guards/tenant-quota.guard.ts b/src/rate-limiting/guards/tenant-quota.guard.ts new file mode 100644 index 00000000..829121a3 --- /dev/null +++ b/src/rate-limiting/guards/tenant-quota.guard.ts @@ -0,0 +1,36 @@ +import { Injectable, CanActivate, ExecutionContext, HttpException } from '@nestjs/common'; + +@Injectable() +export class TenantQuotaGuard implements CanActivate { + private readonly tiers: Record = { + FREE: 100, + PRO: 1000, + ENTERPRISE: -1, + }; + private counters = new Map(); + + canActivate(context: ExecutionContext): boolean { + const req = context.switchToHttp().getRequest(); + const tenantId = req.headers['x-tenant-id'] as string; + if (!tenantId) return true; + + const tier = (req as any).tenantTier || 'FREE'; + const limit = this.tiers[tier] || this.tiers.FREE; + if (limit === -1) return true; + + const now = Date.now(); + const key = tenantId; + const entry = this.counters.get(key); + + if (!entry || now > entry.resetAt) { + this.counters.set(key, { count: 1, resetAt: now + 60000 }); + return true; + } + + entry.count++; + if (entry.count > limit) { + throw new HttpException('Tenant rate limit exceeded', 429); + } + return true; + } +} diff --git a/src/security/abuse/abuse-score.service.ts b/src/security/abuse/abuse-score.service.ts new file mode 100644 index 00000000..4f9bedf6 --- /dev/null +++ b/src/security/abuse/abuse-score.service.ts @@ -0,0 +1,21 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class AbuseScoreService { + private scores = new Map(); + + async getCompositeScore(userId: string, ip: string): Promise { + const key = `${userId}:${ip}`; + const entry = this.scores.get(key); + if (!entry || Date.now() > entry.expiresAt) return 0; + return entry.score; + } + + addSignal(userId: string, ip: string, weight: number): void { + const key = `${userId}:${ip}`; + const entry = this.scores.get(key); + const now = Date.now(); + const newScore = (entry && now <= entry.expiresAt ? entry.score : 0) + weight; + this.scores.set(key, { score: newScore, expiresAt: now + 60000 }); + } +}