import { createHash } from 'node:crypto'; import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; import { APP_FILTER, APP_GUARD, Reflector } from '@nestjs/core'; import { ThrottlerGuard, ThrottlerModule, type ThrottlerGenerateKeyFunction, type ThrottlerGetTrackerFunction, } from '@nestjs/throttler'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AuditModule } from './audit/audit.module'; import { AuthModule } from './auth/auth.module'; import { CsrfGuard } from './auth/guards/csrf.guard'; import { PermissionsGuard } from './auth/guards/permissions.guard'; import { AppConfigModule } from './config/config.module'; import { AppConfigService } from './config/config.service'; import { ApiExceptionFilter } from './common/errors/api-exception.filter'; import { RequestIdMiddleware } from './common/request-context/request-id.middleware'; import { SENSITIVE_RATE_LIMIT_KEY } from './common/rate-limit/sensitive-rate-limit.decorator'; import { typeOrmOptionsFactory } from './database/typeorm-options'; import { DatabaseModule } from './database/database.module'; import { DashboardModule } from './dashboard/dashboard.module'; import { HealthModule } from './health/health.module'; import { ItemsModule } from './items/items.module'; import { NotificationsModule } from './notifications/notifications.module'; import { ProjectsModule } from './projects/projects.module'; import { RolesModule } from './roles/roles.module'; import { RenovationModule } from './renovation/renovation.module'; import { SessionsModule } from './sessions/sessions.module'; import { UsersModule } from './users/users.module'; const getIpTracker: ThrottlerGetTrackerFunction = (req) => { const ip = typeof req['ip'] === 'string' ? req['ip'] : undefined; const socket = req['socket'] as { remoteAddress?: unknown } | undefined; const remoteAddress = typeof socket?.remoteAddress === 'string' ? socket.remoteAddress : undefined; return ip ?? remoteAddress ?? 'unknown'; }; const generateGlobalKey: ThrottlerGenerateKeyFunction = ( _context, tracker, throttlerName, ) => createHash('sha256').update(`${throttlerName}:${tracker}`).digest('hex'); @Module({ imports: [ AppConfigModule, TypeOrmModule.forRootAsync({ imports: [AppConfigModule], inject: [AppConfigService], useFactory: typeOrmOptionsFactory, }), DatabaseModule, DashboardModule, ThrottlerModule.forRootAsync({ imports: [AppConfigModule], inject: [AppConfigService, Reflector], useFactory: (config: AppConfigService, reflector: Reflector) => ({ getTracker: getIpTracker, generateKey: generateGlobalKey, throttlers: [ { name: 'default', ttl: config.rateLimit.global.windowSeconds * 1000, limit: config.rateLimit.global.maxRequests, }, { name: 'sensitive', ttl: config.rateLimit.sensitive.windowSeconds * 1000, limit: config.rateLimit.sensitive.maxRequests, skipIf: (context) => !reflector.getAllAndOverride(SENSITIVE_RATE_LIMIT_KEY, [ context.getHandler(), context.getClass(), ]), }, ], }), }), AuthModule, UsersModule, RolesModule, SessionsModule, NotificationsModule, ProjectsModule, RenovationModule, AuditModule, ItemsModule, HealthModule, ], providers: [ { provide: APP_FILTER, useClass: ApiExceptionFilter }, { provide: APP_GUARD, useClass: ThrottlerGuard }, { provide: APP_GUARD, useClass: CsrfGuard }, { provide: APP_GUARD, useClass: PermissionsGuard }, ], }) export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer): void { consumer.apply(RequestIdMiddleware).forRoutes('*'); } }