Refactor rate limiting implementation and update package dependencies

- Removed the @nestjs/throttler package and replaced it with a custom rate limiting solution using rate-limiter-flexible for enhanced control and flexibility.
- Updated relevant controllers and services to utilize the new rate limiting approach, ensuring consistent request handling across authentication and catalog endpoints.
- Cleaned up unused throttler configuration files and guards to streamline the codebase.
- Updated package.json and pnpm-lock.yaml to reflect the removal of outdated dependencies and improve overall package management.
This commit is contained in:
barsa 2025-12-11 11:25:23 +09:00
parent 7a5cc9f028
commit 1323600978
17 changed files with 290 additions and 172 deletions

View File

@ -70,6 +70,7 @@ WORKDIR /app
# =============================================================================
FROM node:${NODE_VERSION}-alpine AS production
ARG PNPM_VERSION
ARG PRISMA_VERSION
LABEL org.opencontainers.image.title="Customer Portal BFF" \
@ -78,6 +79,8 @@ LABEL org.opencontainers.image.title="Customer Portal BFF" \
# Install runtime dependencies only + security hardening
RUN apk add --no-cache dumb-init libc6-compat netcat-openbsd \
&& corepack enable \
&& corepack prepare pnpm@${PNPM_VERSION} --activate \
&& addgroup --system --gid 1001 nodejs \
&& adduser --system --uid 1001 nestjs \
# Remove apk cache and unnecessary files

View File

@ -39,14 +39,12 @@
"@nestjs/jwt": "^11.0.2",
"@nestjs/passport": "^11.0.5",
"@nestjs/platform-express": "^11.1.9",
"@nestjs/throttler": "^6.5.0",
"@prisma/adapter-pg": "^7.1.0",
"@prisma/client": "^7.1.0",
"@sendgrid/mail": "^8.1.6",
"bcrypt": "^6.0.0",
"bullmq": "^5.65.1",
"cookie-parser": "^1.4.7",
"express": "^5.2.1",
"helmet": "^8.1.0",
"ioredis": "^5.8.2",
"jsforce": "^3.10.10",
@ -58,7 +56,6 @@
"passport-jwt": "^4.0.1",
"passport-local": "^1.0.0",
"pg": "^8.16.3",
"pino": "^10.1.0",
"pino-http": "^11.0.0",
"rate-limiter-flexible": "^9.0.0",
"reflect-metadata": "^0.2.2",

View File

@ -1,18 +1,17 @@
import { Module } from "@nestjs/common";
import { APP_PIPE } from "@nestjs/core";
import { RouterModule } from "@nestjs/core";
import { ConfigModule, ConfigService } from "@nestjs/config";
import { ThrottlerModule } from "@nestjs/throttler";
import { ConfigModule } from "@nestjs/config";
import { ZodValidationPipe } from "nestjs-zod";
// Configuration
import { appConfig } from "@bff/core/config/app.config.js";
import { createThrottlerConfig } from "@bff/core/config/throttler.config.js";
import { apiRoutes } from "@bff/core/config/router.config.js";
// Core Modules
import { LoggingModule } from "@bff/core/logging/logging.module.js";
import { SecurityModule } from "@bff/core/security/security.module.js";
import { RateLimitModule } from "@bff/core/rate-limiting/index.js";
import { PrismaModule } from "@bff/infra/database/prisma.module.js";
import { RedisModule } from "@bff/infra/redis/redis.module.js";
import { CacheModule } from "@bff/infra/cache/cache.module.js";
@ -58,11 +57,7 @@ import { HealthModule } from "@bff/modules/health/health.module.js";
// === INFRASTRUCTURE ===
LoggingModule,
SecurityModule,
ThrottlerModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: createThrottlerConfig,
}),
RateLimitModule,
// === CORE SERVICES ===
PrismaModule,

View File

@ -1,19 +0,0 @@
import type { ConfigService } from "@nestjs/config";
import type { ThrottlerModuleOptions } from "@nestjs/throttler";
export const createThrottlerConfig = (configService: ConfigService): ThrottlerModuleOptions => [
{
ttl: configService.get<number>("RATE_LIMIT_TTL", 60),
limit: configService.get<number>("RATE_LIMIT_LIMIT", 100),
},
{
name: "auth",
ttl: configService.get<number>("AUTH_RATE_LIMIT_TTL", 900),
limit: configService.get<number>("AUTH_RATE_LIMIT_LIMIT", 3),
},
{
name: "auth-refresh",
ttl: configService.get<number>("AUTH_REFRESH_RATE_LIMIT_TTL", 300),
limit: configService.get<number>("AUTH_REFRESH_RATE_LIMIT_LIMIT", 10),
},
];

View File

@ -0,0 +1,4 @@
export { RateLimitModule } from "./rate-limit.module.js";
export { RateLimitGuard } from "./rate-limit.guard.js";
export { RateLimit, SkipRateLimit, type RateLimitOptions, RATE_LIMIT_KEY } from "./rate-limit.decorator.js";

View File

@ -0,0 +1,44 @@
import { SetMetadata } from "@nestjs/common";
export interface RateLimitOptions {
/**
* Maximum number of requests allowed within the TTL window
*/
limit: number;
/**
* Time-to-live in seconds for the rate limit window
*/
ttl: number;
/**
* Optional key prefix for Redis storage (defaults to route path)
*/
keyPrefix?: string;
/**
* Whether to skip rate limiting (useful for conditional logic)
*/
skip?: boolean;
}
export const RATE_LIMIT_KEY = "RATE_LIMIT_OPTIONS";
/**
* Decorator to apply rate limiting to a controller or route handler.
* Uses rate-limiter-flexible with Redis backend.
*
* @example
* ```typescript
* @RateLimit({ limit: 10, ttl: 60 }) // 10 requests per minute
* @Get('endpoint')
* async myEndpoint() { ... }
* ```
*/
export const RateLimit = (options: RateLimitOptions) => SetMetadata(RATE_LIMIT_KEY, options);
/**
* Skip rate limiting for this route (useful when applied at controller level)
*/
export const SkipRateLimit = () => SetMetadata(RATE_LIMIT_KEY, { skip: true } as RateLimitOptions);

View File

@ -0,0 +1,166 @@
import {
Injectable,
Inject,
type CanActivate,
type ExecutionContext,
HttpException,
HttpStatus,
} from "@nestjs/common";
import { Reflector } from "@nestjs/core";
import { RateLimiterRedis, RateLimiterRes } from "rate-limiter-flexible";
import type { Redis } from "ioredis";
import type { Request, Response } from "express";
import { Logger } from "nestjs-pino";
import { createHash } from "crypto";
import { RATE_LIMIT_KEY, type RateLimitOptions } from "./rate-limit.decorator.js";
/**
* Rate limit guard using rate-limiter-flexible with Redis backend.
* Replaces @nestjs/throttler with a unified, more flexible solution.
*
* Features:
* - Redis-backed for distributed rate limiting
* - Per-IP + User-Agent tracking for better security
* - In-memory blocking for reduced Redis load
* - Standard rate limit headers (X-RateLimit-*)
*/
@Injectable()
export class RateLimitGuard implements CanActivate {
private readonly limiters = new Map<string, RateLimiterRedis>();
constructor(
private readonly reflector: Reflector,
@Inject("REDIS_CLIENT") private readonly redis: Redis,
@Inject(Logger) private readonly logger: Logger
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
// Check for route-level options first, then controller-level
const options =
this.reflector.get<RateLimitOptions>(RATE_LIMIT_KEY, context.getHandler()) ??
this.reflector.get<RateLimitOptions>(RATE_LIMIT_KEY, context.getClass());
// No rate limit configured or explicitly skipped
if (!options || options.skip) {
return true;
}
const request = context.switchToHttp().getRequest<Request>();
const response = context.switchToHttp().getResponse<Response>();
const key = this.buildKey(request, context);
const limiter = this.getOrCreateLimiter(options, context);
try {
const result = await limiter.consume(key);
// Set standard rate limit headers
this.setRateLimitHeaders(response, options, result);
return true;
} catch (error: unknown) {
if (error instanceof RateLimiterRes) {
this.setRateLimitHeaders(response, options, error, true);
const retryAfterSeconds = Math.ceil(error.msBeforeNext / 1000);
this.logger.warn(
{ key, retryAfter: retryAfterSeconds, path: request.path },
"Rate limit exceeded"
);
throw new HttpException(
{
statusCode: HttpStatus.TOO_MANY_REQUESTS,
message: `Too many requests. Please try again in ${retryAfterSeconds} seconds.`,
error: "Too Many Requests",
retryAfter: retryAfterSeconds,
},
HttpStatus.TOO_MANY_REQUESTS,
{
cause: error,
}
);
}
// Redis connection error - fail open with warning
this.logger.error({ error, key }, "Rate limiter error - failing open");
return true;
}
}
/**
* Build a unique key for rate limiting based on IP and User-Agent
*/
private buildKey(request: Request, context: ExecutionContext): string {
const ip = this.extractIp(request);
const userAgent = request.headers["user-agent"] || "unknown";
const uaHash = createHash("sha256").update(String(userAgent)).digest("hex").slice(0, 16);
// Use handler name as part of key to separate limits per endpoint
const handlerName = context.getHandler().name;
const controllerName = context.getClass().name;
return `rl:${controllerName}:${handlerName}:${ip}:${uaHash}`;
}
/**
* Extract client IP address, handling proxies
*/
private extractIp(request: Request): string {
const forwarded = request.headers["x-forwarded-for"];
const forwardedIp = Array.isArray(forwarded) ? forwarded[0] : forwarded;
const rawIp =
(typeof forwardedIp === "string" ? forwardedIp.split(",")[0]?.trim() : undefined) ||
(request.headers["x-real-ip"] as string | undefined) ||
request.socket?.remoteAddress ||
request.ip ||
"unknown";
return rawIp.replace(/^::ffff:/, "");
}
/**
* Get or create a rate limiter for the given options
*/
private getOrCreateLimiter(options: RateLimitOptions, context: ExecutionContext): RateLimiterRedis {
const handlerName = context.getHandler().name;
const controllerName = context.getClass().name;
const cacheKey = `${controllerName}:${handlerName}:${options.limit}:${options.ttl}`;
let limiter = this.limiters.get(cacheKey);
if (!limiter) {
limiter = new RateLimiterRedis({
storeClient: this.redis,
keyPrefix: options.keyPrefix ?? `rate_limit:${controllerName}:${handlerName}`,
points: options.limit,
duration: options.ttl,
// Block in-memory after limit exceeded to reduce Redis load
inMemoryBlockOnConsumed: options.limit + 1,
});
this.limiters.set(cacheKey, limiter);
}
return limiter;
}
/**
* Set standard rate limit headers on response
*/
private setRateLimitHeaders(
response: Response,
options: RateLimitOptions,
result: RateLimiterRes,
exceeded = false
): void {
response.setHeader("X-RateLimit-Limit", String(options.limit));
response.setHeader("X-RateLimit-Remaining", String(Math.max(0, result.remainingPoints)));
response.setHeader("X-RateLimit-Reset", String(Math.ceil(result.msBeforeNext / 1000)));
if (exceeded) {
response.setHeader("Retry-After", String(Math.ceil(result.msBeforeNext / 1000)));
}
}
}

View File

@ -0,0 +1,29 @@
import { Global, Module } from "@nestjs/common";
import { RateLimitGuard } from "./rate-limit.guard.js";
/**
* Global rate limiting module using rate-limiter-flexible with Redis.
*
* This replaces @nestjs/throttler with a unified solution that:
* - Uses the same Redis-backed rate-limiter-flexible as auth rate limiting
* - Provides consistent rate limiting across the application
* - Reduces dependency count
*
* Usage:
* ```typescript
* @Controller('example')
* @UseGuards(RateLimitGuard)
* export class ExampleController {
* @RateLimit({ limit: 10, ttl: 60 })
* @Get()
* async example() { ... }
* }
* ```
*/
@Global()
@Module({
providers: [RateLimitGuard],
exports: [RateLimitGuard],
})
export class RateLimitModule {}

View File

@ -1,6 +1,5 @@
import { Inject, Injectable, InternalServerErrorException } from "@nestjs/common";
import { Inject, Injectable, InternalServerErrorException, HttpException, HttpStatus } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { ThrottlerException } from "@nestjs/throttler";
import { Logger } from "nestjs-pino";
import type { Request } from "express";
import { RateLimiterRedis, RateLimiterRes } from "rate-limiter-flexible";
@ -174,11 +173,21 @@ export class AuthRateLimitService {
this.logger.warn({ key, context, retryAfterMs }, "Auth rate limit reached");
throw new ThrottlerException(message);
throw new HttpException(
{ statusCode: HttpStatus.TOO_MANY_REQUESTS, message, error: "Too Many Requests" },
HttpStatus.TOO_MANY_REQUESTS
);
}
this.logger.error({ key, context, error: getErrorMessage(error) }, "Rate limiter failure");
throw new ThrottlerException("Authentication temporarily unavailable");
throw new HttpException(
{
statusCode: HttpStatus.TOO_MANY_REQUESTS,
message: "Authentication temporarily unavailable",
error: "Too Many Requests",
},
HttpStatus.TOO_MANY_REQUESTS
);
}
}

View File

@ -11,11 +11,10 @@ import {
Res,
} from "@nestjs/common";
import type { Request, Response } from "express";
import { Throttle } from "@nestjs/throttler";
import { RateLimitGuard, RateLimit } from "@bff/core/rate-limiting/index.js";
import { JwtService } from "@nestjs/jwt";
import { AuthFacade } from "@bff/modules/auth/application/auth.facade.js";
import { LocalAuthGuard } from "./guards/local-auth.guard.js";
import { AuthThrottleGuard } from "./guards/auth-throttle.guard.js";
import {
FailedLoginThrottleGuard,
type RequestWithRateLimit,
@ -137,8 +136,8 @@ export class AuthController {
@Public()
@Post("validate-signup")
@UseGuards(AuthThrottleGuard, SalesforceReadThrottleGuard)
@Throttle({ default: { limit: 20, ttl: 600 } }) // 20 validations per 10 minutes per IP
@UseGuards(RateLimitGuard, SalesforceReadThrottleGuard)
@RateLimit({ limit: 20, ttl: 600 }) // 20 validations per 10 minutes per IP
@UsePipes(new ZodValidationPipe(validateSignupRequestSchema))
async validateSignup(@Body() validateData: ValidateSignupRequest, @Req() req: Request) {
return this.authFacade.validateSignup(validateData, req);
@ -152,8 +151,8 @@ export class AuthController {
@Public()
@Post("signup-preflight")
@UseGuards(AuthThrottleGuard, SalesforceReadThrottleGuard)
@Throttle({ default: { limit: 20, ttl: 600 } }) // 20 validations per 10 minutes per IP
@UseGuards(RateLimitGuard, SalesforceReadThrottleGuard)
@RateLimit({ limit: 20, ttl: 600 }) // 20 validations per 10 minutes per IP
@UsePipes(new ZodValidationPipe(signupRequestSchema))
@HttpCode(200)
async signupPreflight(@Body() signupData: SignupRequest) {
@ -169,8 +168,8 @@ export class AuthController {
@Public()
@Post("signup")
@UseGuards(AuthThrottleGuard, SalesforceWriteThrottleGuard)
@Throttle({ default: { limit: 5, ttl: 900 } }) // 5 signups per 15 minutes per IP (reasonable for account creation)
@UseGuards(RateLimitGuard, SalesforceWriteThrottleGuard)
@RateLimit({ limit: 5, ttl: 900 }) // 5 signups per 15 minutes per IP (reasonable for account creation)
@UsePipes(new ZodValidationPipe(signupRequestSchema))
async signup(
@Body() signupData: SignupRequest,
@ -228,7 +227,8 @@ export class AuthController {
@Public()
@Post("refresh")
@Throttle({ default: { limit: 10, ttl: 300 } }) // 10 attempts per 5 minutes per IP
@UseGuards(RateLimitGuard)
@RateLimit({ limit: 10, ttl: 300 }) // 10 attempts per 5 minutes per IP
@UsePipes(new ZodValidationPipe(refreshTokenRequestSchema))
async refreshToken(
@Body() body: RefreshTokenRequest,
@ -248,8 +248,8 @@ export class AuthController {
@Public()
@Post("link-whmcs")
@UseGuards(AuthThrottleGuard, SalesforceWriteThrottleGuard)
@Throttle({ default: { limit: 5, ttl: 600 } }) // 5 attempts per 10 minutes per IP (industry standard)
@UseGuards(RateLimitGuard, SalesforceWriteThrottleGuard)
@RateLimit({ limit: 5, ttl: 600 }) // 5 attempts per 10 minutes per IP (industry standard)
@UsePipes(new ZodValidationPipe(linkWhmcsRequestSchema))
async linkWhmcs(@Body() linkData: LinkWhmcsRequest, @Req() _req: Request) {
const result = await this.authFacade.linkWhmcsUser(linkData);
@ -258,8 +258,8 @@ export class AuthController {
@Public()
@Post("set-password")
@UseGuards(AuthThrottleGuard)
@Throttle({ default: { limit: 5, ttl: 600 } }) // 5 attempts per 10 minutes per IP+UA (industry standard)
@UseGuards(RateLimitGuard)
@RateLimit({ limit: 5, ttl: 600 }) // 5 attempts per 10 minutes per IP+UA (industry standard)
@UsePipes(new ZodValidationPipe(setPasswordRequestSchema))
async setPassword(
@Body() setPasswordData: SetPasswordRequest,
@ -282,7 +282,8 @@ export class AuthController {
@Public()
@Post("request-password-reset")
@Throttle({ default: { limit: 5, ttl: 900 } }) // 5 attempts per 15 minutes (standard for password operations)
@UseGuards(RateLimitGuard)
@RateLimit({ limit: 5, ttl: 900 }) // 5 attempts per 15 minutes (standard for password operations)
@UsePipes(new ZodValidationPipe(passwordResetRequestSchema))
async requestPasswordReset(@Body() body: PasswordResetRequest, @Req() req: Request) {
await this.authFacade.requestPasswordReset(body.email, req);
@ -305,7 +306,8 @@ export class AuthController {
}
@Post("change-password")
@Throttle({ default: { limit: 5, ttl: 300 } })
@UseGuards(RateLimitGuard)
@RateLimit({ limit: 5, ttl: 300 }) // 5 attempts per 5 minutes
@UsePipes(new ZodValidationPipe(changePasswordRequestSchema))
async changePassword(
@Req() req: Request & { user: { id: string } },

View File

@ -1,27 +0,0 @@
import { Injectable } from "@nestjs/common";
import { ThrottlerGuard } from "@nestjs/throttler";
import { createHash } from "crypto";
import type { Request } from "express";
@Injectable()
export class AuthThrottleGuard extends ThrottlerGuard {
protected override async getTracker(req: Request): Promise<string> {
// Track by IP address + User Agent for better security on sensitive auth operations
const forwarded = req.headers["x-forwarded-for"];
const forwardedIp = Array.isArray(forwarded) ? forwarded[0] : forwarded;
const ip =
(typeof forwardedIp === "string" ? forwardedIp.split(",")[0]?.trim() : undefined) ||
(req.headers["x-real-ip"] as string | undefined) ||
req.socket?.remoteAddress ||
req.ip ||
"unknown";
const userAgent = req.headers["user-agent"] || "unknown";
const userAgentHash = createHash("sha256").update(userAgent).digest("hex").slice(0, 16);
const normalizedIp = ip.replace(/^::ffff:/, "");
const resolvedIp = await Promise.resolve(normalizedIp);
return `auth_${resolvedIp}_${userAgentHash}`;
}
}

View File

@ -1,5 +1,5 @@
import { Controller, Get, Request, UseGuards, Header } from "@nestjs/common";
import { Throttle, ThrottlerGuard } from "@nestjs/throttler";
import { RateLimitGuard, RateLimit } from "@bff/core/rate-limiting/index.js";
import type { RequestWithUser } from "@bff/modules/auth/auth.types.js";
import {
parseInternetCatalog,
@ -18,7 +18,7 @@ import { VpnCatalogService } from "./services/vpn-catalog.service.js";
import { SalesforceReadThrottleGuard } from "@bff/integrations/salesforce/guards/salesforce-read-throttle.guard.js";
@Controller("catalog")
@UseGuards(SalesforceReadThrottleGuard, ThrottlerGuard)
@UseGuards(SalesforceReadThrottleGuard, RateLimitGuard)
export class CatalogController {
constructor(
private internetCatalog: InternetCatalogService,
@ -27,7 +27,7 @@ export class CatalogController {
) {}
@Get("internet/plans")
@Throttle({ default: { limit: 20, ttl: 60 } }) // 20 requests per minute
@RateLimit({ limit: 20, ttl: 60 }) // 20 requests per minute
@Header("Cache-Control", "private, max-age=300") // Personalised responses: prevent shared caching
async getInternetPlans(@Request() req: RequestWithUser): Promise<{
plans: InternetPlanCatalogItem[];
@ -62,7 +62,7 @@ export class CatalogController {
}
@Get("sim/plans")
@Throttle({ default: { limit: 20, ttl: 60 } }) // 20 requests per minute
@RateLimit({ limit: 20, ttl: 60 }) // 20 requests per minute
@Header("Cache-Control", "private, max-age=300") // Personalised responses: prevent shared caching
async getSimCatalogData(@Request() req: RequestWithUser): Promise<SimCatalogCollection> {
const userId = req.user?.id;
@ -96,7 +96,7 @@ export class CatalogController {
}
@Get("vpn/plans")
@Throttle({ default: { limit: 20, ttl: 60 } }) // 20 requests per minute
@RateLimit({ limit: 20, ttl: 60 }) // 20 requests per minute
@Header("Cache-Control", "public, max-age=300, s-maxage=300") // 5 minutes
async getVpnPlans(): Promise<VpnCatalogProduct[]> {
return this.vpnCatalog.getPlans();

View File

@ -11,7 +11,7 @@ import {
UnauthorizedException,
type MessageEvent,
} from "@nestjs/common";
import { Throttle, ThrottlerGuard } from "@nestjs/throttler";
import { RateLimitGuard, RateLimit } from "@bff/core/rate-limiting/index.js";
import { OrderOrchestrator } from "./services/order-orchestrator.service.js";
import type { RequestWithUser } from "@bff/modules/auth/auth.types.js";
import { Logger } from "nestjs-pino";
@ -30,7 +30,7 @@ import { SalesforceReadThrottleGuard } from "@bff/integrations/salesforce/guards
import { SalesforceWriteThrottleGuard } from "@bff/integrations/salesforce/guards/salesforce-write-throttle.guard.js";
@Controller("orders")
@UseGuards(ThrottlerGuard)
@UseGuards(RateLimitGuard)
export class OrdersController {
constructor(
private orderOrchestrator: OrderOrchestrator,
@ -42,7 +42,7 @@ export class OrdersController {
@Post()
@UseGuards(SalesforceWriteThrottleGuard)
@Throttle({ default: { limit: 5, ttl: 60 } }) // 5 order creations per minute
@RateLimit({ limit: 5, ttl: 60 }) // 5 order creations per minute
@UsePipes(new ZodValidationPipe(createOrderRequestSchema))
async create(@Request() req: RequestWithUser, @Body() body: CreateOrderRequest) {
this.logger.log(

View File

@ -41,7 +41,6 @@
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
"tailwindcss": "^4.1.17",
"typescript": "^5.9.3",
"webpack-bundle-analyzer": "^5.1.0"
"typescript": "^5.9.3"
}
}

84
pnpm-lock.yaml generated
View File

@ -76,9 +76,6 @@ importers:
'@nestjs/platform-express':
specifier: ^11.1.9
version: 11.1.9(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)
'@nestjs/throttler':
specifier: ^6.5.0
version: 6.5.0(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(reflect-metadata@0.2.2)
'@prisma/adapter-pg':
specifier: ^7.1.0
version: 7.1.0
@ -97,9 +94,6 @@ importers:
cookie-parser:
specifier: ^1.4.7
version: 1.4.7
express:
specifier: ^5.2.1
version: 5.2.1
helmet:
specifier: ^8.1.0
version: 8.1.0
@ -133,9 +127,6 @@ importers:
pg:
specifier: ^8.16.3
version: 8.16.3
pino:
specifier: ^10.1.0
version: 10.1.0
pino-http:
specifier: ^11.0.0
version: 11.0.0
@ -294,9 +285,6 @@ importers:
typescript:
specifier: ^5.9.3
version: 5.9.3
webpack-bundle-analyzer:
specifier: ^5.1.0
version: 5.1.0
packages/domain:
dependencies:
@ -1477,13 +1465,6 @@ packages:
'@nestjs/platform-express':
optional: true
'@nestjs/throttler@6.5.0':
resolution: {integrity: sha512-9j0ZRfH0QE1qyrj9JjIRDz5gQLPqq9yVC2nHsrosDVAfI5HHw08/aUAWx9DZLSdQf4HDkmhTTEGLrRFHENvchQ==}
peerDependencies:
'@nestjs/common': ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0
'@nestjs/core': ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0
reflect-metadata: ^0.1.13 || ^0.2.0
'@next/bundle-analyzer@16.0.8':
resolution: {integrity: sha512-LFWHAWdurTCpqLq1ZRUah1nuDK8cti6kN8vj4wqIW3yAnq3BDgJeNgMMUmeLLzR9C81AnVKfSZFBEhn+aUpe/g==}
@ -3327,10 +3308,6 @@ packages:
resolution: {integrity: sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==}
engines: {node: '>= 18'}
express@5.2.1:
resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==}
engines: {node: '>= 18'}
exsolve@1.0.8:
resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==}
@ -5750,11 +5727,6 @@ packages:
engines: {node: '>= 10.13.0'}
hasBin: true
webpack-bundle-analyzer@5.1.0:
resolution: {integrity: sha512-WAWwIoIUx4yC2AEBqXbDkcmh/LzAaenv0+nISBflP5l+XIXO9/x6poWarGA3RTrfavk9H3oWQ64Wm0z26/UGKA==}
engines: {node: '>= 20.9.0'}
hasBin: true
webpack-node-externals@3.0.0:
resolution: {integrity: sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==}
engines: {node: '>=6'}
@ -7090,12 +7062,6 @@ snapshots:
optionalDependencies:
'@nestjs/platform-express': 11.1.9(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)
'@nestjs/throttler@6.5.0(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(reflect-metadata@0.2.2)':
dependencies:
'@nestjs/common': 11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)
'@nestjs/core': 11.1.9(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2)
reflect-metadata: 0.2.2
'@next/bundle-analyzer@16.0.8':
dependencies:
webpack-bundle-analyzer: 4.10.1
@ -9258,39 +9224,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
express@5.2.1:
dependencies:
accepts: 2.0.0
body-parser: 2.2.1
content-disposition: 1.0.1
content-type: 1.0.5
cookie: 0.7.2
cookie-signature: 1.2.2
debug: 4.4.3
depd: 2.0.0
encodeurl: 2.0.0
escape-html: 1.0.3
etag: 1.8.1
finalhandler: 2.1.1
fresh: 2.0.0
http-errors: 2.0.1
merge-descriptors: 2.0.0
mime-types: 3.0.2
on-finished: 2.4.1
once: 1.4.0
parseurl: 1.3.3
proxy-addr: 2.0.7
qs: 6.14.0
range-parser: 1.2.1
router: 2.2.0
send: 1.2.0
serve-static: 2.2.0
statuses: 2.0.2
type-is: 2.0.1
vary: 1.1.2
transitivePeerDependencies:
- supports-color
exsolve@1.0.8: {}
ext-list@2.2.2:
@ -12090,23 +12023,6 @@ snapshots:
- bufferutil
- utf-8-validate
webpack-bundle-analyzer@5.1.0:
dependencies:
'@discoveryjs/json-ext': 0.5.7
acorn: 8.15.0
acorn-walk: 8.3.4
commander: 7.2.0
debounce: 1.2.1
escape-string-regexp: 4.0.0
html-escaper: 2.0.2
opener: 1.5.2
picocolors: 1.1.1
sirv: 2.0.4
ws: 7.5.10
transitivePeerDependencies:
- bufferutil
- utf-8-validate
webpack-node-externals@3.0.0: {}
webpack-sources@3.3.3: {}

View File

@ -1 +1 @@
53a3fba0f80abe58f8d3c0cde37b99027ce93f03486b4124c0664c7d53233921 /home/barsa/projects/customer_portal/customer-portal/portal-backend.latest.tar.gz
b809f14715623b94d3a38d058ab09ef4cb3f9aa655031c4613c2feeedacce14b /home/barsa/projects/customer_portal/customer-portal/portal-backend.latest.tar.gz

View File

@ -1 +1 @@
85dcbdfe8c81740c1d5d6d2bc09a81a92148f1f79c1a6ee5dcc8baf85e57be32 /home/barsa/projects/customer_portal/customer-portal/portal-frontend.latest.tar.gz
447e6f2bebb4670bab788977187704c10a5b8cf0e318f950d16bc15d1e459ce2 /home/barsa/projects/customer_portal/customer-portal/portal-frontend.latest.tar.gz