feat: enhance configuration and rate limiting

- Added new configuration options: trustProxy, uploadConcurrency, batchMaxItems, batchMaxSizeBytes, and maxRequestBodyBytes to AppConfig.
- Implemented utility functions for parsing environment variables and masking sensitive data.
- Updated rate limiting logic to use configurable window size and maximum requests per window.
- Introduced a middleware for rate limiting on specific routes.
- Refactored file handling routes to support streaming downloads instead of redirects.
- Improved error handling and response formatting in file routes.
- Added support for oversized request rejection based on Content-Length header.
- Updated Swagger documentation to reflect changes in API behavior and responses.
- Enhanced tests to cover new features and ensure proper functionality.
This commit is contained in:
MythEclipse
2026-05-29 03:33:39 +07:00
parent be813b1c0e
commit 5425f6d33d
16 changed files with 466 additions and 201 deletions
+38 -10
View File
@@ -11,6 +11,11 @@ interface AppConfig {
logLevel: string;
rateLimitWindowMs: number;
rateLimitMaxRequests: number;
trustProxy: boolean;
uploadConcurrency: number;
batchMaxItems: number;
batchMaxSizeBytes: number;
maxRequestBodyBytes: number;
}
const requiredEnv = {
@@ -30,25 +35,48 @@ if (missing.length > 0) {
throw new Error(`Missing environment variables: ${missing.join(', ')}`);
}
const parseNumber = (value: string | undefined, fallback: number): number => {
const parsed = Number.parseInt(value || '', 10);
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
};
const parseTokens = (value: string | undefined): string[] =>
(value || '')
.split(',')
.map((t) => t.trim())
.filter((t) => t !== '');
const maskSecret = (value: string): string => {
if (!value) return '';
if (value.length <= 10) return '***';
return `${value.slice(0, 6)}...${value.slice(-4)}`;
};
const maskDatabaseUrl = (value: string): string => value.replace(/:\/\/([^:]+):([^@]+)@/, '://$1:***@');
export const config: AppConfig = {
botToken: process.env.BOT_TOKEN!,
additionalBotTokens:
process.env.NODE_ENV === 'test'
? []
: (process.env.ADDITIONAL_BOT_TOKENS || '')
.split(',')
.map((t) => t.trim())
.filter((t) => t !== ''),
additionalBotTokens: process.env.NODE_ENV === 'test' ? [] : parseTokens(process.env.ADDITIONAL_BOT_TOKENS),
storageChatId: parseInt(process.env.STORAGE_CHANNEL_ID!, 10),
baseUrl: process.env.BASE_URL!,
databaseUrl: process.env.DATABASE_URL!,
port: parseInt(process.env.PORT!, 10) || 3000,
nodeEnv: process.env.NODE_ENV || 'development',
logLevel: process.env.LOG_LEVEL || 'info',
rateLimitWindowMs: parseInt(process.env.RATE_LIMIT_WINDOW_MS!, 10) || 60000,
rateLimitMaxRequests: parseInt(process.env.RATE_LIMIT_MAX_REQUESTS!, 10) || 30,
rateLimitWindowMs: parseNumber(process.env.RATE_LIMIT_WINDOW_MS, 60000),
rateLimitMaxRequests: parseNumber(process.env.RATE_LIMIT_MAX_REQUESTS, 150),
trustProxy: process.env.TRUST_PROXY === 'true',
uploadConcurrency: parseNumber(process.env.UPLOAD_CONCURRENCY, 8),
batchMaxItems: parseNumber(process.env.BATCH_MAX_ITEMS, 20),
batchMaxSizeBytes: parseNumber(process.env.BATCH_MAX_SIZE_BYTES, 500 * 1024 * 1024),
maxRequestBodyBytes: parseNumber(process.env.MAX_REQUEST_BODY_BYTES, 2 * 1024 * 1024 * 1024),
};
logger.info('Environment variables loaded', {
config: { ...config, botToken: `${config.botToken?.substring(0, 10)}...` },
config: {
...config,
botToken: maskSecret(config.botToken),
additionalBotTokens: config.additionalBotTokens.map(maskSecret),
databaseUrl: maskDatabaseUrl(config.databaseUrl),
},
});