From 2af917427515737843f2b16631c4c59ceeafc7cb Mon Sep 17 00:00:00 2001 From: ivenkili Date: Sat, 29 Aug 2026 15:41:43 +0100 Subject: [PATCH] feat:Backend: Apply per-principal rate limiting --- .../common/rate-limit/rate-limit.config.ts | 9 ++++++++ .../src/common/rate-limit/rate-limit.guard.ts | 21 ++++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/apps/backend/src/common/rate-limit/rate-limit.config.ts b/apps/backend/src/common/rate-limit/rate-limit.config.ts index 1c4cbf491..77f6eb2a8 100644 --- a/apps/backend/src/common/rate-limit/rate-limit.config.ts +++ b/apps/backend/src/common/rate-limit/rate-limit.config.ts @@ -218,6 +218,15 @@ export function getTrackerId( const parts: string[] = []; + // Prefer an authenticated principal when available + const reqAny = request as any; + const user = reqAny.user as { id?: string; sub?: string; stellarPublicKey?: string } | undefined; + const principalId = user?.id || user?.sub || user?.stellarPublicKey || ''; + if (principalId) { + parts.push(`principal:${principalId}`); + return parts.join('|'); + } + if (settings.tracker.useApiKey && apiKey) { parts.push(`api-key:${apiKey}`); } diff --git a/apps/backend/src/common/rate-limit/rate-limit.guard.ts b/apps/backend/src/common/rate-limit/rate-limit.guard.ts index 75056c42b..32ac6cd16 100644 --- a/apps/backend/src/common/rate-limit/rate-limit.guard.ts +++ b/apps/backend/src/common/rate-limit/rate-limit.guard.ts @@ -6,7 +6,7 @@ import { } from '@nestjs/common'; import { ThrottlerGuard, ThrottlerLimitDetail } from '@nestjs/throttler'; import { ErrorCode } from '../enums/error-code.enum'; -import { Request } from 'express'; +import { Request, Response } from 'express'; import { config } from '../../lib/config'; import * as net from 'net'; @@ -76,6 +76,25 @@ export class RateLimitGuard extends ThrottlerGuard { void context; await Promise.resolve(); + // Set standard rate limit headers on the response when available + try { + const response = context.switchToHttp().getResponse(); + const ttlSeconds = Math.ceil(throttlerLimitDetail.ttl / 1000); + const retryAfter = Math.max( + Number(throttlerLimitDetail.timeToBlockExpire) || 0, + 0, + ); + + if (response && typeof response.setHeader === 'function') { + response.setHeader('Retry-After', String(retryAfter)); + response.setHeader('X-RateLimit-Limit', String(throttlerLimitDetail.limit)); + response.setHeader('X-RateLimit-Remaining', '0'); + response.setHeader('X-RateLimit-Reset', String(ttlSeconds)); + } + } catch { + // Ignore header errors and continue to throw the exception + } + throw new HttpException( { code: ErrorCode.SYS_RATE_LIMIT_EXCEEDED,