fix(#199): implement rate limiting on webhook endpoints
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed

Implements comprehensive rate limiting on all webhook and coordinator endpoints
to prevent DoS attacks. Follows TDD protocol with 14 passing tests.

Implementation:
- Added @nestjs/throttler package for rate limiting
- Created ThrottlerApiKeyGuard for per-API-key rate limiting
- Created ThrottlerValkeyStorageService for distributed rate limiting via Redis
- Configured rate limits on stitcher endpoints (60 req/min)
- Configured rate limits on coordinator endpoints (100 req/min)
- Higher limits for health endpoints (300 req/min for monitoring)
- Added environment variables for rate limit configuration
- Rate limiting logs violations for security monitoring

Rate Limits:
- Stitcher webhooks: 60 requests/minute per API key
- Coordinator endpoints: 100 requests/minute per API key
- Health endpoints: 300 requests/minute (higher for monitoring)

Storage:
- Uses Valkey (Redis) for distributed rate limiting across API instances
- Falls back to in-memory storage if Redis unavailable

Testing:
- 14 comprehensive rate limiting tests (all passing)
- Tests verify: rate limit enforcement, Retry-After headers, per-API-key isolation
- TDD approach: RED (failing tests) → GREEN (implementation) → REFACTOR

Additional improvements:
- Type safety improvements in websocket gateway
- Array type notation standardization in coordinator service

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Jason Woltje
2026-02-02 13:07:16 -06:00
parent 210b3d2e8f
commit 41d56dadf0
14 changed files with 990 additions and 11 deletions

View File

@@ -1,5 +1,7 @@
import { Module } from "@nestjs/common";
import { APP_INTERCEPTOR } from "@nestjs/core";
import { APP_INTERCEPTOR, APP_GUARD } from "@nestjs/core";
import { ThrottlerModule } from "@nestjs/throttler";
import { ThrottlerValkeyStorageService, ThrottlerApiKeyGuard } from "./common/throttler";
import { AppController } from "./app.controller";
import { AppService } from "./app.service";
import { PrismaModule } from "./prisma/prisma.module";
@@ -31,6 +33,23 @@ import { CoordinatorIntegrationModule } from "./coordinator-integration/coordina
@Module({
imports: [
// Rate limiting configuration
ThrottlerModule.forRootAsync({
useFactory: () => {
const ttl = parseInt(process.env.RATE_LIMIT_TTL ?? "60", 10) * 1000; // Convert to milliseconds
const limit = parseInt(process.env.RATE_LIMIT_GLOBAL_LIMIT ?? "100", 10);
return {
throttlers: [
{
ttl,
limit,
},
],
storage: new ThrottlerValkeyStorageService(),
};
},
}),
TelemetryModule,
PrismaModule,
DatabaseModule,
@@ -65,6 +84,10 @@ import { CoordinatorIntegrationModule } from "./coordinator-integration/coordina
provide: APP_INTERCEPTOR,
useClass: TelemetryInterceptor,
},
{
provide: APP_GUARD,
useClass: ThrottlerApiKeyGuard,
},
],
})
export class AppModule {}