add upload rate limiter per IP to backend.

Nest throttler with nullcart-style default profile and upload override via throttleProfiles.
This commit is contained in:
2026-10-06 18:59:44 +02:00
parent 84f8a85214
commit 7e4a889b50
8 changed files with 77 additions and 25 deletions
+24
View File
@@ -1,5 +1,7 @@
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService, registerAs } from '@nestjs/config';
import { APP_GUARD } from '@nestjs/core';
import { ThrottlerGuard, ThrottlerModule } from '@nestjs/throttler';
import { TypeOrmModule } from '@nestjs/typeorm';
import { getAltchaConfig, getAppConfig, getPostgresConfig } from './config';
import { validate } from './config/validate';
@@ -20,6 +22,22 @@ import { Config } from './types/Config';
registerAs('altcha', getAltchaConfig)
]
}),
ThrottlerModule.forRootAsync({
inject: [ConfigService],
useFactory: (configService: ConfigService) => {
const { throttle } = configService.get('app') as Config['app'];
return {
throttlers: [
{
name: 'default',
ttl: throttle.ttlMs,
limit: throttle.limit
}
]
};
}
}),
TypeOrmModule.forRootAsync({
useFactory: (configService: ConfigService) => configService.get('postgres') as Config['postgres'],
inject: [ConfigService]
@@ -28,6 +46,12 @@ import { Config } from './types/Config';
AltchaModule,
StorageModule,
UploadModule
],
providers: [
{
provide: APP_GUARD,
useClass: ThrottlerGuard
}
]
})
export class AppModule {}
+9 -1
View File
@@ -41,6 +41,14 @@ export const getAppConfig = (): AppConfig => {
corsOrigins: rawOrigins
.split(',')
.map(o => o.trim())
.filter(Boolean)
.filter(Boolean),
throttle: {
ttlMs: envInt('THROTTLE_TTL_MS'),
limit: envInt('THROTTLE_LIMIT')
},
uploadThrottle: {
ttlMs: envInt('THROTTLE_UPLOAD_TTL_MS'),
limit: envInt('THROTTLE_UPLOAD_LIMIT')
}
};
};
+9
View File
@@ -0,0 +1,9 @@
import { getAppConfig } from './index';
const {
uploadThrottle: { limit, ttlMs }
} = getAppConfig();
export const throttleProfiles = {
upload: { default: { limit, ttl: ttlMs } }
} as const;
+12 -24
View File
@@ -75,6 +75,18 @@ class EnvironmentVariables {
@Max(3650)
NEVER_MODE_INACTIVITY_DAYS: number;
@IsNotEmpty()
@IsNumber()
@Min(1000)
@Max(3_600_000)
THROTTLE_TTL_MS: number;
@IsNotEmpty()
@IsNumber()
@Min(1)
@Max(1_000_000)
THROTTLE_LIMIT: number;
@IsNotEmpty()
@IsNumber()
@Min(1000)
@@ -86,30 +98,6 @@ class EnvironmentVariables {
@Min(1)
@Max(10_000)
THROTTLE_UPLOAD_LIMIT: number;
@IsNotEmpty()
@IsNumber()
@Min(1000)
@Max(3_600_000)
THROTTLE_HOTLINK_PER_IP_TTL_MS: number;
@IsNotEmpty()
@IsNumber()
@Min(1)
@Max(1_000_000)
THROTTLE_HOTLINK_PER_IP_LIMIT: number;
@IsNotEmpty()
@IsNumber()
@Min(1000)
@Max(3_600_000)
THROTTLE_HOTLINK_PER_ID_TTL_MS: number;
@IsNotEmpty()
@IsNumber()
@Min(1)
@Max(1_000_000)
THROTTLE_HOTLINK_PER_ID_LIMIT: number;
}
export const validate = (config: Record<string, unknown>) => {
+3
View File
@@ -1,7 +1,10 @@
import { NodeEnv } from './NodeEnv';
import { ThrottleConfig } from './ThrottleConfig';
export type AppConfig = {
port: number;
nodeEnv: NodeEnv;
corsOrigins: string[];
throttle: ThrottleConfig;
uploadThrottle: ThrottleConfig;
};
+4
View File
@@ -0,0 +1,4 @@
export type ThrottleConfig = {
ttlMs: number;
limit: number;
};