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:
Generated
+15
@@ -13,6 +13,7 @@
|
|||||||
"@nestjs/config": "^4.0.3",
|
"@nestjs/config": "^4.0.3",
|
||||||
"@nestjs/core": "^11.0.1",
|
"@nestjs/core": "^11.0.1",
|
||||||
"@nestjs/platform-express": "^11.0.1",
|
"@nestjs/platform-express": "^11.0.1",
|
||||||
|
"@nestjs/throttler": "^6.7.1",
|
||||||
"@nestjs/typeorm": "^12.0.2",
|
"@nestjs/typeorm": "^12.0.2",
|
||||||
"altcha-lib": "^2.6.0",
|
"altcha-lib": "^2.6.0",
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
@@ -1995,6 +1996,20 @@
|
|||||||
"tslib": "^2.1.0"
|
"tslib": "^2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@nestjs/throttler": {
|
||||||
|
"version": "6.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nestjs/throttler/-/throttler-6.7.1.tgz",
|
||||||
|
"integrity": "sha512-gAyY1AkU+cSK5DCjFOB43xax9oakameH4xZa9kAgqBqSBQZgmv3DZylLV8KyZNzOGhceL3kDVtby+CF8VAdmEA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0",
|
||||||
|
"@nestjs/core": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0",
|
||||||
|
"reflect-metadata": "^0.1.13 || ^0.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@nestjs/typeorm": {
|
"node_modules/@nestjs/typeorm": {
|
||||||
"version": "12.0.2",
|
"version": "12.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@nestjs/typeorm/-/typeorm-12.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@nestjs/typeorm/-/typeorm-12.0.2.tgz",
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
"@nestjs/config": "^4.0.3",
|
"@nestjs/config": "^4.0.3",
|
||||||
"@nestjs/core": "^11.0.1",
|
"@nestjs/core": "^11.0.1",
|
||||||
"@nestjs/platform-express": "^11.0.1",
|
"@nestjs/platform-express": "^11.0.1",
|
||||||
|
"@nestjs/throttler": "^6.7.1",
|
||||||
"@nestjs/typeorm": "^12.0.2",
|
"@nestjs/typeorm": "^12.0.2",
|
||||||
"altcha-lib": "^2.6.0",
|
"altcha-lib": "^2.6.0",
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { ConfigModule, ConfigService, registerAs } from '@nestjs/config';
|
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 { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
import { getAltchaConfig, getAppConfig, getPostgresConfig } from './config';
|
import { getAltchaConfig, getAppConfig, getPostgresConfig } from './config';
|
||||||
import { validate } from './config/validate';
|
import { validate } from './config/validate';
|
||||||
@@ -20,6 +22,22 @@ import { Config } from './types/Config';
|
|||||||
registerAs('altcha', getAltchaConfig)
|
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({
|
TypeOrmModule.forRootAsync({
|
||||||
useFactory: (configService: ConfigService) => configService.get('postgres') as Config['postgres'],
|
useFactory: (configService: ConfigService) => configService.get('postgres') as Config['postgres'],
|
||||||
inject: [ConfigService]
|
inject: [ConfigService]
|
||||||
@@ -28,6 +46,12 @@ import { Config } from './types/Config';
|
|||||||
AltchaModule,
|
AltchaModule,
|
||||||
StorageModule,
|
StorageModule,
|
||||||
UploadModule
|
UploadModule
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: APP_GUARD,
|
||||||
|
useClass: ThrottlerGuard
|
||||||
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class AppModule {}
|
export class AppModule {}
|
||||||
|
|||||||
@@ -41,6 +41,14 @@ export const getAppConfig = (): AppConfig => {
|
|||||||
corsOrigins: rawOrigins
|
corsOrigins: rawOrigins
|
||||||
.split(',')
|
.split(',')
|
||||||
.map(o => o.trim())
|
.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')
|
||||||
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { getAppConfig } from './index';
|
||||||
|
|
||||||
|
const {
|
||||||
|
uploadThrottle: { limit, ttlMs }
|
||||||
|
} = getAppConfig();
|
||||||
|
|
||||||
|
export const throttleProfiles = {
|
||||||
|
upload: { default: { limit, ttl: ttlMs } }
|
||||||
|
} as const;
|
||||||
@@ -75,6 +75,18 @@ class EnvironmentVariables {
|
|||||||
@Max(3650)
|
@Max(3650)
|
||||||
NEVER_MODE_INACTIVITY_DAYS: number;
|
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()
|
@IsNotEmpty()
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
@Min(1000)
|
@Min(1000)
|
||||||
@@ -86,30 +98,6 @@ class EnvironmentVariables {
|
|||||||
@Min(1)
|
@Min(1)
|
||||||
@Max(10_000)
|
@Max(10_000)
|
||||||
THROTTLE_UPLOAD_LIMIT: number;
|
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>) => {
|
export const validate = (config: Record<string, unknown>) => {
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import { NodeEnv } from './NodeEnv';
|
import { NodeEnv } from './NodeEnv';
|
||||||
|
import { ThrottleConfig } from './ThrottleConfig';
|
||||||
|
|
||||||
export type AppConfig = {
|
export type AppConfig = {
|
||||||
port: number;
|
port: number;
|
||||||
nodeEnv: NodeEnv;
|
nodeEnv: NodeEnv;
|
||||||
corsOrigins: string[];
|
corsOrigins: string[];
|
||||||
|
throttle: ThrottleConfig;
|
||||||
|
uploadThrottle: ThrottleConfig;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export type ThrottleConfig = {
|
||||||
|
ttlMs: number;
|
||||||
|
limit: number;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user