From 44f6f2e320205a609d20cfa39c47db935cdadc1f Mon Sep 17 00:00:00 2001 From: Lauris Skraucis Date: Mon, 7 Oct 2024 13:02:41 +0200 Subject: [PATCH] refactor: v2 throttler guard (#16922) * chore: add logs * refactor: replace deprecated redis.setex with redis.set * chore: log response headers --- apps/api/v2/src/lib/throttler-guard.ts | 34 +++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/apps/api/v2/src/lib/throttler-guard.ts b/apps/api/v2/src/lib/throttler-guard.ts index 09249a7366..372489a40b 100644 --- a/apps/api/v2/src/lib/throttler-guard.ts +++ b/apps/api/v2/src/lib/throttler-guard.ts @@ -51,6 +51,11 @@ export class CustomThrottlerGuard extends ThrottlerGuard { const request = context.switchToHttp().getRequest(); const response = context.switchToHttp().getResponse(); const tracker = await this.getTracker(request); + this.logger.log( + `Tracker "${tracker}" generated based on: Bearer token "${request.get( + "Authorization" + )}", OAuth client ID "${request.get(X_CAL_CLIENT_ID)}" and IP "${request.ip}"` + ); if (tracker.startsWith("api_key_")) { return this.handleApiKeyRequest(tracker, response); @@ -79,6 +84,9 @@ export class CustomThrottlerGuard extends ThrottlerGuard { private async handleNonApiKeyRequest(tracker: string, response: Response): Promise { const rateLimit = this.getDefaultRateLimit(); + this.logger.log(`Tracker "${tracker}" uses default rate limits because it is not tracking api key: + ${JSON.stringify(rateLimit, null, 2)} + `); const { isBlocked } = await this.incrementRateLimit(tracker, rateLimit, response); if (isBlocked) { @@ -114,6 +122,9 @@ export class CustomThrottlerGuard extends ThrottlerGuard { const cachedRateLimits = await this.storageService.redis.get(cacheKey); if (cachedRateLimits) { + this.logger.log(`Tracker "${tracker}" rate limits retrieved from redis cache: + ${cachedRateLimits} + `); return rateLimitsSchema.parse(JSON.parse(cachedRateLimits)); } @@ -133,11 +144,18 @@ export class CustomThrottlerGuard extends ThrottlerGuard { select: { name: true, limit: true, ttl: true, blockDuration: true }, }); - if (!rateLimits || rateLimits.length === 0) { - rateLimits = [this.getDefaultRateLimit()]; + if (rateLimits) { + this.logger.log(`Tracker "${tracker}" rate limits retrieved from database: + ${JSON.stringify(rateLimits, null, 2)}`); } - await this.storageService.redis.setex(cacheKey, 3600, JSON.stringify(rateLimits)); + if (!rateLimits || rateLimits.length === 0) { + rateLimits = [this.getDefaultRateLimit()]; + this.logger.log(`Tracker "${tracker}" rate limits not found in database. Using default rate limits: + ${JSON.stringify(rateLimits, null, 2)}`); + } + + await this.storageService.redis.set(cacheKey, JSON.stringify(rateLimits), "EX", 3600); return rateLimits; } @@ -163,7 +181,15 @@ export class CustomThrottlerGuard extends ThrottlerGuard { ); response.setHeader(`X-RateLimit-Reset-${nameFirstUpper}`, timeToBlockExpire || timeToExpire); - this.logger.log(`Rate limit for ${tracker} incremented. Remaining: ${limit - totalHits}`); + this.logger.log( + `Tracker "${tracker}" rate limit "${name}" incremented. isBlocked ${isBlocked}, totalHits ${totalHits}, timeToExpire ${timeToExpire}, timeToBlockExpire ${timeToBlockExpire}` + ); + this.logger.log( + `Tracker "${tracker}" rate limit "${name}" response headers: + X-RateLimit-Limit-${nameFirstUpper}: ${limit}, + X-RateLimit-Remaining-${nameFirstUpper}: ${timeToBlockExpire ? 0 : Math.max(0, limit - totalHits)}, + X-RateLimit-Reset-${nameFirstUpper}: ${timeToBlockExpire || timeToExpire}` + ); return { isBlocked }; }