From 9fdd296d70a6f302ef25038cd0a6b23df6ef6089 Mon Sep 17 00:00:00 2001 From: sujitaw Date: Tue, 16 Jun 2026 13:57:25 +0530 Subject: [PATCH 01/57] wip Signed-off-by: sujitaw --- apps/api-gateway/src/main.ts | 40 ++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/apps/api-gateway/src/main.ts b/apps/api-gateway/src/main.ts index 67f0d0bb4..86c69c46f 100755 --- a/apps/api-gateway/src/main.ts +++ b/apps/api-gateway/src/main.ts @@ -21,6 +21,46 @@ import { EcosystemSwaggerFilter } from './authz/guards/ecosystem-swagger.filter' dotenv.config(); async function bootstrap(): Promise { + const baoUrl = process.env.BAO_URL; + const baoToken = process.env.BAO_TOKEN; + const secretPath = process.env.BAO_SECRET_PATH; + + if (!baoToken) { + throw new Error('Missing BAO_TOKEN environment variable'); + } + + // Fetch secrets from OpenBao before initializing the NestJS ConfigModule + // console.log('🔐 Fetching secrets from OpenBao...',baoUrl, baoToken, secretPath); + try { + const response = await fetch(`${baoUrl}/v1/${secretPath}`, { + method: 'GET', + headers: { + 'X-Vault-Token': baoToken, + 'Content-Type': 'application/json' + } + }); + // console.log('Received response from OpenBao with status:', response); + if (!response.ok) { + throw new Error(`OpenBao responded with status: ${response.status}`); + } + + const result = await response.json(); + + // OpenBao structures KV v2 data inside data.data + const secrets = result.data.data; + + // Inject the fetched secrets into the process.env so NestJS ConfigService can read them + Object.keys(secrets).forEach((key) => { + process.env[key] = secrets[key]; + }); + + // console.log('✅ Environment variables successfully loaded from OpenBao',secrets); + } catch (error) { + // eslint-disable-next-line no-console + console.error('❌ Failed to load secrets from OpenBao:', error.message); + process.exit(1); // Stop the app if secrets can't be loaded + } + try { if (otelSDK) { await otelSDK.start(); From 30435067b94323afbfcac821c52cf7dcac9fb24c Mon Sep 17 00:00:00 2001 From: sujitaw Date: Tue, 16 Jun 2026 14:08:11 +0530 Subject: [PATCH 02/57] comments code rabbit Signed-off-by: sujitaw --- apps/api-gateway/src/main.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/api-gateway/src/main.ts b/apps/api-gateway/src/main.ts index 86c69c46f..4ad189833 100755 --- a/apps/api-gateway/src/main.ts +++ b/apps/api-gateway/src/main.ts @@ -51,7 +51,9 @@ async function bootstrap(): Promise { // Inject the fetched secrets into the process.env so NestJS ConfigService can read them Object.keys(secrets).forEach((key) => { - process.env[key] = secrets[key]; + if ('__proto__' !== key && 'constructor' !== key && 'prototype' !== key) { + process.env[key] = secrets[key]; + } }); // console.log('✅ Environment variables successfully loaded from OpenBao',secrets); From 78e89afa65a825c0a297d03145f99a088c618384 Mon Sep 17 00:00:00 2001 From: sujitaw Date: Tue, 16 Jun 2026 15:53:03 +0530 Subject: [PATCH 03/57] fix comments Signed-off-by: sujitaw --- apps/api-gateway/src/main.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/api-gateway/src/main.ts b/apps/api-gateway/src/main.ts index 4ad189833..2564ed36d 100755 --- a/apps/api-gateway/src/main.ts +++ b/apps/api-gateway/src/main.ts @@ -51,7 +51,7 @@ async function bootstrap(): Promise { // Inject the fetched secrets into the process.env so NestJS ConfigService can read them Object.keys(secrets).forEach((key) => { - if ('__proto__' !== key && 'constructor' !== key && 'prototype' !== key) { + if (Object.prototype.hasOwnProperty.call(secrets, key)) { process.env[key] = secrets[key]; } }); From 901dfd36e115604b229cce760a0761521c8aeac4 Mon Sep 17 00:00:00 2001 From: sujitaw Date: Tue, 16 Jun 2026 16:00:44 +0530 Subject: [PATCH 04/57] fix comments Signed-off-by: sujitaw --- apps/api-gateway/src/main.ts | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/apps/api-gateway/src/main.ts b/apps/api-gateway/src/main.ts index 2564ed36d..9188685de 100755 --- a/apps/api-gateway/src/main.ts +++ b/apps/api-gateway/src/main.ts @@ -49,11 +49,32 @@ async function bootstrap(): Promise { // OpenBao structures KV v2 data inside data.data const secrets = result.data.data; - // Inject the fetched secrets into the process.env so NestJS ConfigService can read them + const FORBIDDEN_KEYS = new Set(['__proto__', 'constructor', 'prototype']); + + if (!secrets || 'object' !== typeof secrets || Array.isArray(secrets)) { + throw new Error('Unexpected secrets payload received from OpenBao'); + } + Object.keys(secrets).forEach((key) => { - if (Object.prototype.hasOwnProperty.call(secrets, key)) { - process.env[key] = secrets[key]; + if (FORBIDDEN_KEYS.has(key)) { + // eslint-disable-next-line no-console + console.warn(`Skipping forbidden key from OpenBao response: ${key}`); + return; } + + if (!Object.prototype.hasOwnProperty.call(secrets, key)) { + return; + } + + const value = secrets[key]; + + // process.env values must be strings + Object.defineProperty(process.env, key, { + value: String(value), + enumerable: true, + configurable: true, + writable: true + }); }); // console.log('✅ Environment variables successfully loaded from OpenBao',secrets); From 383aaf4c72b3ea0beb1a2196681d331eeb6414c6 Mon Sep 17 00:00:00 2001 From: sujitaw Date: Tue, 16 Jun 2026 16:12:22 +0530 Subject: [PATCH 05/57] fix comments Signed-off-by: sujitaw --- apps/api-gateway/src/main.ts | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/apps/api-gateway/src/main.ts b/apps/api-gateway/src/main.ts index 9188685de..210b64e78 100755 --- a/apps/api-gateway/src/main.ts +++ b/apps/api-gateway/src/main.ts @@ -48,7 +48,6 @@ async function bootstrap(): Promise { // OpenBao structures KV v2 data inside data.data const secrets = result.data.data; - const FORBIDDEN_KEYS = new Set(['__proto__', 'constructor', 'prototype']); if (!secrets || 'object' !== typeof secrets || Array.isArray(secrets)) { @@ -57,20 +56,11 @@ async function bootstrap(): Promise { Object.keys(secrets).forEach((key) => { if (FORBIDDEN_KEYS.has(key)) { - // eslint-disable-next-line no-console - console.warn(`Skipping forbidden key from OpenBao response: ${key}`); return; } - if (!Object.prototype.hasOwnProperty.call(secrets, key)) { - return; - } - - const value = secrets[key]; - - // process.env values must be strings Object.defineProperty(process.env, key, { - value: String(value), + value: String(secrets[key]), enumerable: true, configurable: true, writable: true From 6a8d21ba4077eac69fbc4585b5f75be851ca040c Mon Sep 17 00:00:00 2001 From: sujitaw Date: Wed, 17 Jun 2026 16:54:13 +0530 Subject: [PATCH 06/57] feat: fetch token using role and secret Signed-off-by: sujitaw --- .env.demo | 4 ++++ apps/api-gateway/src/main.ts | 34 +++++++++++++++++++++++++++++----- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/.env.demo b/.env.demo index f5de4cd3c..66b606b06 100644 --- a/.env.demo +++ b/.env.demo @@ -368,3 +368,7 @@ ADMIN_KEYCLOAK_SECRET= # Add in case the keycloak id of the old platform client is changed and want to update all the users in database with the new admin client PLATFORM_ADMIN_OLD_CLIENT_ID= +BAO_URL= +BAO_SECRET_PATH= +BAO_ROLE_ID= +BAO_SECRET_ID= \ No newline at end of file diff --git a/apps/api-gateway/src/main.ts b/apps/api-gateway/src/main.ts index 210b64e78..f3f5f7acf 100755 --- a/apps/api-gateway/src/main.ts +++ b/apps/api-gateway/src/main.ts @@ -22,15 +22,42 @@ dotenv.config(); async function bootstrap(): Promise { const baoUrl = process.env.BAO_URL; - const baoToken = process.env.BAO_TOKEN; + let baoToken = ''; const secretPath = process.env.BAO_SECRET_PATH; + const roleId = process.env.BAO_ROLE_ID; + const secretId = process.env.BAO_SECRET_ID; + + if (!roleId || !secretId) { + // eslint-disable-next-line no-console + console.error('❌ Critical Error: BAO_ROLE_ID and BAO_SECRET_ID must be set in the environment.'); + process.exit(1); + } + + try { + // 2. Simple POST request to exchange RoleID/SecretID for an access token + const authResponse = await fetch(`${baoUrl}/v1/auth/approle/login`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ role_id: roleId, secret_id: secretId }) + }); + + if (!authResponse.ok) { + throw new Error(`Authentication failed with status ${authResponse.status}`); + } + + const authData: { auth: { client_token: string } } = await authResponse.json(); + baoToken = authData.auth.client_token; + } catch (error) { + // eslint-disable-next-line no-console + console.error('❌ OpenBao Lifecycle Boot Failure:', error.message); + process.exit(1); // Force shutdown if secrets cannot be securely mounted + } if (!baoToken) { throw new Error('Missing BAO_TOKEN environment variable'); } // Fetch secrets from OpenBao before initializing the NestJS ConfigModule - // console.log('🔐 Fetching secrets from OpenBao...',baoUrl, baoToken, secretPath); try { const response = await fetch(`${baoUrl}/v1/${secretPath}`, { method: 'GET', @@ -39,7 +66,6 @@ async function bootstrap(): Promise { 'Content-Type': 'application/json' } }); - // console.log('Received response from OpenBao with status:', response); if (!response.ok) { throw new Error(`OpenBao responded with status: ${response.status}`); } @@ -66,8 +92,6 @@ async function bootstrap(): Promise { writable: true }); }); - - // console.log('✅ Environment variables successfully loaded from OpenBao',secrets); } catch (error) { // eslint-disable-next-line no-console console.error('❌ Failed to load secrets from OpenBao:', error.message); From be08e7c801c9c3974ed1b6ca596be214d20f1090 Mon Sep 17 00:00:00 2001 From: sujitaw Date: Fri, 19 Jun 2026 15:48:29 +0530 Subject: [PATCH 07/57] wip Signed-off-by: sujitaw --- apps/agent-provisioning/src/main.ts | 6 +++ apps/agent-service/src/main.ts | 5 ++ apps/api-gateway/src/main.ts | 78 +-------------------------- apps/cloud-wallet/src/main.ts | 5 ++ apps/connection/src/main.ts | 5 ++ apps/ecosystem/src/main.ts | 5 ++ apps/geo-location/src/main.ts | 5 ++ apps/issuance/src/main.ts | 5 ++ apps/ledger/src/main.ts | 5 ++ apps/notification/src/main.ts | 5 ++ apps/oid4vc-issuance/src/main.ts | 6 +++ apps/oid4vc-verification/src/main.ts | 5 ++ apps/organization/src/main.ts | 5 ++ apps/user/src/main.ts | 5 ++ apps/utility/src/main.ts | 5 ++ apps/verification/src/main.ts | 5 ++ apps/webhook/src/main.ts | 5 ++ apps/x509/src/main.ts | 5 ++ libs/aws/src/aws.service.ts | 1 + libs/common/src/resend-helper-file.ts | 19 ++++--- libs/config/src/index.ts | 3 +- 21 files changed, 103 insertions(+), 85 deletions(-) diff --git a/apps/agent-provisioning/src/main.ts b/apps/agent-provisioning/src/main.ts index 6b3e2963c..fd7f31d86 100644 --- a/apps/agent-provisioning/src/main.ts +++ b/apps/agent-provisioning/src/main.ts @@ -1,3 +1,4 @@ +import * as dotenv from 'dotenv'; import { NestFactory } from '@nestjs/core'; import { HttpExceptionFilter } from 'libs/http-exception.filter'; import { Logger } from '@nestjs/common'; @@ -6,9 +7,14 @@ import { AgentProvisioningModule } from './agent-provisioning.module'; import { getNatsOptions } from '@credebl/common/nats.config'; import { CommonConstants } from '@credebl/common/common.constant'; import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; +import { loadBaoSecrets } from '@credebl/config/bao-secrets'; + +dotenv.config(); + const logger = new Logger(); async function bootstrap(): Promise { + await loadBaoSecrets(); const app = await NestFactory.createMicroservice(AgentProvisioningModule, { transport: Transport.NATS, options: getNatsOptions( diff --git a/apps/agent-service/src/main.ts b/apps/agent-service/src/main.ts index c949023a5..7bf1b795a 100644 --- a/apps/agent-service/src/main.ts +++ b/apps/agent-service/src/main.ts @@ -1,3 +1,4 @@ +import * as dotenv from 'dotenv'; import { HttpExceptionFilter } from 'libs/http-exception.filter'; import { Logger } from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; @@ -9,10 +10,14 @@ import { getNatsOptions } from '@credebl/common/nats.config'; import { CommonConstants } from '@credebl/common/common.constant'; import { Ledgers } from '@credebl/enum/enum'; import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; +import { loadBaoSecrets } from '@credebl/config/bao-secrets'; + +dotenv.config(); const logger = new Logger(); async function bootstrap(): Promise { + await loadBaoSecrets(); const app = await NestFactory.createMicroservice(AgentServiceModule, { transport: Transport.NATS, options: getNatsOptions( diff --git a/apps/api-gateway/src/main.ts b/apps/api-gateway/src/main.ts index f3f5f7acf..b8bcddbf6 100755 --- a/apps/api-gateway/src/main.ts +++ b/apps/api-gateway/src/main.ts @@ -17,86 +17,12 @@ import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapt import { UpdatableValidationPipe } from '@credebl/common/custom-overrideable-validation-pipe'; import * as useragent from 'express-useragent'; import { EcosystemSwaggerFilter } from './authz/guards/ecosystem-swagger.filter'; +import { loadBaoSecrets } from '@credebl/config/bao-secrets'; dotenv.config(); async function bootstrap(): Promise { - const baoUrl = process.env.BAO_URL; - let baoToken = ''; - const secretPath = process.env.BAO_SECRET_PATH; - const roleId = process.env.BAO_ROLE_ID; - const secretId = process.env.BAO_SECRET_ID; - - if (!roleId || !secretId) { - // eslint-disable-next-line no-console - console.error('❌ Critical Error: BAO_ROLE_ID and BAO_SECRET_ID must be set in the environment.'); - process.exit(1); - } - - try { - // 2. Simple POST request to exchange RoleID/SecretID for an access token - const authResponse = await fetch(`${baoUrl}/v1/auth/approle/login`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ role_id: roleId, secret_id: secretId }) - }); - - if (!authResponse.ok) { - throw new Error(`Authentication failed with status ${authResponse.status}`); - } - - const authData: { auth: { client_token: string } } = await authResponse.json(); - baoToken = authData.auth.client_token; - } catch (error) { - // eslint-disable-next-line no-console - console.error('❌ OpenBao Lifecycle Boot Failure:', error.message); - process.exit(1); // Force shutdown if secrets cannot be securely mounted - } - - if (!baoToken) { - throw new Error('Missing BAO_TOKEN environment variable'); - } - - // Fetch secrets from OpenBao before initializing the NestJS ConfigModule - try { - const response = await fetch(`${baoUrl}/v1/${secretPath}`, { - method: 'GET', - headers: { - 'X-Vault-Token': baoToken, - 'Content-Type': 'application/json' - } - }); - if (!response.ok) { - throw new Error(`OpenBao responded with status: ${response.status}`); - } - - const result = await response.json(); - - // OpenBao structures KV v2 data inside data.data - const secrets = result.data.data; - const FORBIDDEN_KEYS = new Set(['__proto__', 'constructor', 'prototype']); - - if (!secrets || 'object' !== typeof secrets || Array.isArray(secrets)) { - throw new Error('Unexpected secrets payload received from OpenBao'); - } - - Object.keys(secrets).forEach((key) => { - if (FORBIDDEN_KEYS.has(key)) { - return; - } - - Object.defineProperty(process.env, key, { - value: String(secrets[key]), - enumerable: true, - configurable: true, - writable: true - }); - }); - } catch (error) { - // eslint-disable-next-line no-console - console.error('❌ Failed to load secrets from OpenBao:', error.message); - process.exit(1); // Stop the app if secrets can't be loaded - } + await loadBaoSecrets(); try { if (otelSDK) { diff --git a/apps/cloud-wallet/src/main.ts b/apps/cloud-wallet/src/main.ts index a411b0700..d0d4b9811 100644 --- a/apps/cloud-wallet/src/main.ts +++ b/apps/cloud-wallet/src/main.ts @@ -1,3 +1,4 @@ +import * as dotenv from 'dotenv'; import { NestFactory } from '@nestjs/core'; import { CloudWalletModule } from './cloud-wallet.module'; import { HttpExceptionFilter } from 'libs/http-exception.filter'; @@ -6,10 +7,14 @@ import { MicroserviceOptions, Transport } from '@nestjs/microservices'; import { getNatsOptions } from '@credebl/common/nats.config'; import { CommonConstants } from '@credebl/common/common.constant'; import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; +import { loadBaoSecrets } from '@credebl/config/bao-secrets'; + +dotenv.config(); const logger = new Logger(); async function bootstrap(): Promise { + await loadBaoSecrets(); const app = await NestFactory.createMicroservice(CloudWalletModule, { transport: Transport.NATS, options: getNatsOptions( diff --git a/apps/connection/src/main.ts b/apps/connection/src/main.ts index c98e3ea74..81dac6a5f 100644 --- a/apps/connection/src/main.ts +++ b/apps/connection/src/main.ts @@ -1,3 +1,4 @@ +import * as dotenv from 'dotenv'; import { NestFactory } from '@nestjs/core'; import { ConnectionModule } from './connection.module'; import { HttpExceptionFilter } from 'libs/http-exception.filter'; @@ -6,10 +7,14 @@ import { MicroserviceOptions, Transport } from '@nestjs/microservices'; import { getNatsOptions } from '@credebl/common/nats.config'; import { CommonConstants } from '@credebl/common/common.constant'; import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; +import { loadBaoSecrets } from '@credebl/config/bao-secrets'; + +dotenv.config(); const logger = new Logger(); async function bootstrap(): Promise { + await loadBaoSecrets(); const app = await NestFactory.createMicroservice(ConnectionModule, { transport: Transport.NATS, options: getNatsOptions( diff --git a/apps/ecosystem/src/main.ts b/apps/ecosystem/src/main.ts index ebb2a53df..16fd8fc8b 100644 --- a/apps/ecosystem/src/main.ts +++ b/apps/ecosystem/src/main.ts @@ -1,3 +1,4 @@ +import * as dotenv from 'dotenv'; import { MicroserviceOptions, Transport } from '@nestjs/microservices'; import { CommonConstants } from '@credebl/common/common.constant'; @@ -8,10 +9,14 @@ import { NestFactory } from '@nestjs/core'; import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; // import { nkeyAuthenticator } from 'nats'; import { getNatsOptions } from '@credebl/common/nats.config'; +import { loadBaoSecrets } from '@credebl/config/bao-secrets'; + +dotenv.config(); const logger = new Logger(); async function bootstrap(): Promise { + await loadBaoSecrets(); const app = await NestFactory.createMicroservice(EcosystemModule, { transport: Transport.NATS, options: getNatsOptions( diff --git a/apps/geo-location/src/main.ts b/apps/geo-location/src/main.ts index 84800754f..cb4b94602 100644 --- a/apps/geo-location/src/main.ts +++ b/apps/geo-location/src/main.ts @@ -1,3 +1,4 @@ +import * as dotenv from 'dotenv'; import { HttpExceptionFilter } from 'libs/http-exception.filter'; import { Logger } from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; @@ -6,10 +7,14 @@ import { getNatsOptions } from '@credebl/common/nats.config'; import { GeoLocationModule } from './geo-location.module'; import { CommonConstants } from '@credebl/common/common.constant'; import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; +import { loadBaoSecrets } from '@credebl/config/bao-secrets'; + +dotenv.config(); const logger = new Logger(); async function bootstrap(): Promise { + await loadBaoSecrets(); const app = await NestFactory.createMicroservice(GeoLocationModule, { transport: Transport.NATS, options: getNatsOptions( diff --git a/apps/issuance/src/main.ts b/apps/issuance/src/main.ts index b69e8e57c..d89fb2437 100644 --- a/apps/issuance/src/main.ts +++ b/apps/issuance/src/main.ts @@ -1,3 +1,4 @@ +import * as dotenv from 'dotenv'; import { NestFactory } from '@nestjs/core'; import { HttpExceptionFilter } from 'libs/http-exception.filter'; import { Logger } from '@nestjs/common'; @@ -6,10 +7,14 @@ import { IssuanceModule } from '../src/issuance.module'; import { getNatsOptions } from '@credebl/common/nats.config'; import { CommonConstants } from '@credebl/common/common.constant'; import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; +import { loadBaoSecrets } from '@credebl/config/bao-secrets'; + +dotenv.config(); const logger = new Logger(); async function bootstrap(): Promise { + await loadBaoSecrets(); const app = await NestFactory.createMicroservice(IssuanceModule, { transport: Transport.NATS, options: getNatsOptions( diff --git a/apps/ledger/src/main.ts b/apps/ledger/src/main.ts index 022dffa76..6c2ddbb5b 100644 --- a/apps/ledger/src/main.ts +++ b/apps/ledger/src/main.ts @@ -1,3 +1,4 @@ +import * as dotenv from 'dotenv'; import { NestFactory } from '@nestjs/core'; import { LedgerModule } from './ledger.module'; import { HttpExceptionFilter } from 'libs/http-exception.filter'; @@ -6,8 +7,12 @@ import { MicroserviceOptions, Transport } from '@nestjs/microservices'; import { getNatsOptions } from '@credebl/common/nats.config'; import { CommonConstants } from '@credebl/common/common.constant'; import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; +import { loadBaoSecrets } from '@credebl/config/bao-secrets'; + +dotenv.config(); async function bootstrap(): Promise { + await loadBaoSecrets(); const app = await NestFactory.createMicroservice(LedgerModule, { transport: Transport.NATS, options: getNatsOptions(CommonConstants.LEDGER_SERVICE, process.env.LEDGER_NKEY_SEED, process.env.NATS_CREDS_FILE) diff --git a/apps/notification/src/main.ts b/apps/notification/src/main.ts index 64d505ed7..4112bc44a 100644 --- a/apps/notification/src/main.ts +++ b/apps/notification/src/main.ts @@ -1,3 +1,4 @@ +import * as dotenv from 'dotenv'; import { NestFactory } from '@nestjs/core'; import { HttpExceptionFilter } from 'libs/http-exception.filter'; import { Logger } from '@nestjs/common'; @@ -6,10 +7,14 @@ import { NotificationModule } from '../src/notification.module'; import { getNatsOptions } from '@credebl/common/nats.config'; import { CommonConstants } from '@credebl/common/common.constant'; import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; +import { loadBaoSecrets } from '@credebl/config/bao-secrets'; + +dotenv.config(); const logger = new Logger(); async function bootstrap(): Promise { + await loadBaoSecrets(); const app = await NestFactory.createMicroservice(NotificationModule, { transport: Transport.NATS, options: getNatsOptions( diff --git a/apps/oid4vc-issuance/src/main.ts b/apps/oid4vc-issuance/src/main.ts index 68aa19bbf..bfe17a72f 100644 --- a/apps/oid4vc-issuance/src/main.ts +++ b/apps/oid4vc-issuance/src/main.ts @@ -1,3 +1,4 @@ +import * as dotenv from 'dotenv'; import { NestFactory } from '@nestjs/core'; import { HttpExceptionFilter } from 'libs/http-exception.filter'; import { Logger } from '@nestjs/common'; @@ -6,10 +7,15 @@ import { getNatsOptions } from '@credebl/common/nats.config'; import { CommonConstants } from '@credebl/common/common.constant'; import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; import { Oid4vcIssuanceModule } from './oid4vc-issuance.module'; +import { loadBaoSecrets } from '@credebl/config/bao-secrets'; + +dotenv.config(); const logger = new Logger(); async function bootstrap(): Promise { + await loadBaoSecrets(); + if (!process.env.STATUS_LIST_HOST) { logger.warn('STATUS_LIST_HOST is not configured. Revocable SD-JWT flow will be disabled.'); } diff --git a/apps/oid4vc-verification/src/main.ts b/apps/oid4vc-verification/src/main.ts index 667a31102..e86a8ee32 100644 --- a/apps/oid4vc-verification/src/main.ts +++ b/apps/oid4vc-verification/src/main.ts @@ -1,3 +1,4 @@ +import * as dotenv from 'dotenv'; import { NestFactory } from '@nestjs/core'; import { Logger } from '@nestjs/common'; import { MicroserviceOptions, Transport } from '@nestjs/microservices'; @@ -5,10 +6,14 @@ import { getNatsOptions } from '@credebl/common/nats.config'; import { CommonConstants } from '@credebl/common/common.constant'; import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; import { Oid4vpModule } from './oid4vc-verification.module'; +import { loadBaoSecrets } from '@credebl/config/bao-secrets'; + +dotenv.config(); const logger = new Logger(); async function bootstrap(): Promise { + await loadBaoSecrets(); const app = await NestFactory.createMicroservice(Oid4vpModule, { transport: Transport.NATS, options: getNatsOptions( diff --git a/apps/organization/src/main.ts b/apps/organization/src/main.ts index 8ec4de9d2..f8dc27f48 100644 --- a/apps/organization/src/main.ts +++ b/apps/organization/src/main.ts @@ -1,3 +1,4 @@ +import * as dotenv from 'dotenv'; import { HttpExceptionFilter } from 'libs/http-exception.filter'; import { Logger } from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; @@ -7,10 +8,14 @@ import { MicroserviceOptions, Transport } from '@nestjs/microservices'; import { getNatsOptions } from '@credebl/common/nats.config'; import { CommonConstants } from '@credebl/common/common.constant'; import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; +import { loadBaoSecrets } from '@credebl/config/bao-secrets'; + +dotenv.config(); const logger = new Logger(); async function bootstrap(): Promise { + await loadBaoSecrets(); const app = await NestFactory.createMicroservice(OrganizationModule, { transport: Transport.NATS, options: getNatsOptions( diff --git a/apps/user/src/main.ts b/apps/user/src/main.ts index 01411c446..cf52215ad 100644 --- a/apps/user/src/main.ts +++ b/apps/user/src/main.ts @@ -1,3 +1,4 @@ +import * as dotenv from 'dotenv'; import { MicroserviceOptions, Transport } from '@nestjs/microservices'; import { CommonConstants } from '@credebl/common/common.constant'; @@ -7,10 +8,14 @@ import { NestFactory } from '@nestjs/core'; import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; import { UserModule } from './user.module'; import { getNatsOptions } from '@credebl/common/nats.config'; +import { loadBaoSecrets } from '@credebl/config/bao-secrets'; + +dotenv.config(); const logger = new Logger(); async function bootstrap(): Promise { + await loadBaoSecrets(); const app = await NestFactory.createMicroservice(UserModule, { transport: Transport.NATS, options: getNatsOptions(CommonConstants.USER_SERVICE, process.env.USER_NKEY_SEED, process.env.NATS_CREDS_FILE) diff --git a/apps/utility/src/main.ts b/apps/utility/src/main.ts index fcf71b53a..e0173b4ee 100644 --- a/apps/utility/src/main.ts +++ b/apps/utility/src/main.ts @@ -1,3 +1,4 @@ +import * as dotenv from 'dotenv'; import { HttpExceptionFilter } from 'libs/http-exception.filter'; import { Logger } from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; @@ -6,10 +7,14 @@ import { getNatsOptions } from '@credebl/common/nats.config'; import { UtilitiesModule } from './utilities.module'; import { CommonConstants } from '@credebl/common/common.constant'; import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; +import { loadBaoSecrets } from '@credebl/config/bao-secrets'; + +dotenv.config(); const logger = new Logger(); async function bootstrap(): Promise { + await loadBaoSecrets(); const app = await NestFactory.createMicroservice(UtilitiesModule, { transport: Transport.NATS, options: getNatsOptions( diff --git a/apps/verification/src/main.ts b/apps/verification/src/main.ts index 0195a8ee1..046d9a0e0 100644 --- a/apps/verification/src/main.ts +++ b/apps/verification/src/main.ts @@ -1,3 +1,4 @@ +import * as dotenv from 'dotenv'; import { NestFactory } from '@nestjs/core'; import { HttpExceptionFilter } from 'libs/http-exception.filter'; import { Logger } from '@nestjs/common'; @@ -6,10 +7,14 @@ import { VerificationModule } from './verification.module'; import { getNatsOptions } from '@credebl/common/nats.config'; import { CommonConstants } from '@credebl/common/common.constant'; import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; +import { loadBaoSecrets } from '@credebl/config/bao-secrets'; + +dotenv.config(); const logger = new Logger(); async function bootstrap(): Promise { + await loadBaoSecrets(); const app = await NestFactory.createMicroservice(VerificationModule, { transport: Transport.NATS, options: getNatsOptions( diff --git a/apps/webhook/src/main.ts b/apps/webhook/src/main.ts index 07ca3bcea..25b3380ce 100644 --- a/apps/webhook/src/main.ts +++ b/apps/webhook/src/main.ts @@ -1,3 +1,4 @@ +import * as dotenv from 'dotenv'; import { NestFactory } from '@nestjs/core'; import { HttpExceptionFilter } from 'libs/http-exception.filter'; import { Logger } from '@nestjs/common'; @@ -6,10 +7,14 @@ import { WebhookModule } from '../src/webhook.module'; import { getNatsOptions } from '@credebl/common/nats.config'; import { CommonConstants } from '@credebl/common/common.constant'; import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; +import { loadBaoSecrets } from '@credebl/config/bao-secrets'; + +dotenv.config(); const logger = new Logger(); async function bootstrap(): Promise { + await loadBaoSecrets(); const app = await NestFactory.createMicroservice(WebhookModule, { transport: Transport.NATS, options: getNatsOptions( diff --git a/apps/x509/src/main.ts b/apps/x509/src/main.ts index eb77bb520..208e1d3aa 100644 --- a/apps/x509/src/main.ts +++ b/apps/x509/src/main.ts @@ -1,3 +1,4 @@ +import * as dotenv from 'dotenv'; import { NestFactory } from '@nestjs/core'; import { HttpExceptionFilter } from 'libs/http-exception.filter'; import { Logger } from '@nestjs/common'; @@ -6,8 +7,12 @@ import { getNatsOptions } from '@credebl/common/nats.config'; import { CommonConstants } from '@credebl/common/common.constant'; import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; import { X509Module } from './x509.module'; +import { loadBaoSecrets } from '@credebl/config/bao-secrets'; + +dotenv.config(); async function bootstrap(): Promise { + await loadBaoSecrets(); const app = await NestFactory.createMicroservice(X509Module, { transport: Transport.NATS, options: getNatsOptions(CommonConstants.X509_SERVICE, process.env.X509_NKEY_SEED, process.env.NATS_CREDS_FILE) diff --git a/libs/aws/src/aws.service.ts b/libs/aws/src/aws.service.ts index 19286edea..1f402a7f4 100644 --- a/libs/aws/src/aws.service.ts +++ b/libs/aws/src/aws.service.ts @@ -1,4 +1,5 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; + import { RpcException } from '@nestjs/microservices'; import { S3 } from 'aws-sdk'; import { promisify } from 'util'; diff --git a/libs/common/src/resend-helper-file.ts b/libs/common/src/resend-helper-file.ts index 4d0326bd8..1c1170724 100644 --- a/libs/common/src/resend-helper-file.ts +++ b/libs/common/src/resend-helper-file.ts @@ -1,26 +1,29 @@ -import * as dotenv from 'dotenv'; - import { EmailDto } from './dtos/email.dto'; import { Logger } from '@nestjs/common'; import { Resend } from 'resend'; -dotenv.config(); +let resend: Resend | null = null; -const emailProvider = process.env.EMAIL_PROVIDER; -const apiKey = process.env.RESEND_API_KEY; +function getResendClient(): Resend { + if (resend) { + return resend; + } -let resend: Resend | null = null; + const apiKey = process.env.RESEND_API_KEY; -if ('resend' === emailProvider) { if (!apiKey) { throw new Error('Missing RESEND_API_KEY in environment variables.'); } + resend = new Resend(apiKey); + return resend; } export const sendWithResend = async (emailDto: EmailDto): Promise => { try { - const response = await resend.emails.send({ + const client = getResendClient(); + + const response = await client.emails.send({ from: emailDto.emailFrom, to: emailDto.emailTo, subject: emailDto.emailSubject, diff --git a/libs/config/src/index.ts b/libs/config/src/index.ts index 8d518dbf6..0ec7a716f 100644 --- a/libs/config/src/index.ts +++ b/libs/config/src/index.ts @@ -1,3 +1,4 @@ export * from './config.service'; export * from './config.module'; -export * from './global-config.module'; \ No newline at end of file +export * from './global-config.module'; +export { loadBaoSecrets } from './bao-secrets'; From c54e8e2d3e6d5f84641f29ecfd0b0822326d4c80 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Jun 2026 19:26:21 +0000 Subject: [PATCH 08/57] chore(deps): bump nodemailer from 7.0.13 to 9.0.1 Bumps [nodemailer](https://github.com/nodemailer/nodemailer) from 7.0.13 to 9.0.1. - [Release notes](https://github.com/nodemailer/nodemailer/releases) - [Changelog](https://github.com/nodemailer/nodemailer/blob/master/CHANGELOG.md) - [Commits](https://github.com/nodemailer/nodemailer/compare/v7.0.13...v9.0.1) --- updated-dependencies: - dependency-name: nodemailer dependency-version: 9.0.1 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- package.json | 2 +- pnpm-lock.yaml | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index c6bdb9daf..45b843abe 100644 --- a/package.json +++ b/package.json @@ -99,7 +99,7 @@ "nestjs-typeorm-paginate": "catalog:", "node-html-to-image": "^4.0.0", "node-qpdf2": "^2.0.0", - "nodemailer": "^7.0.13", + "nodemailer": "^9.0.1", "p-limit": "^3.1.0", "papaparse": "^5.5.3", "passport": "^0.6.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 86fed180d..688a3c71b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -303,8 +303,8 @@ importers: specifier: ^2.0.0 version: 2.0.0 nodemailer: - specifier: ^7.0.13 - version: 7.0.13 + specifier: ^9.0.1 + version: 9.0.1 p-limit: specifier: ^3.1.0 version: 3.1.0 @@ -4921,6 +4921,7 @@ packages: nats@2.29.3: resolution: {integrity: sha512-tOQCRCwC74DgBTk4pWZ9V45sk4d7peoE2njVprMRCBXrhJ5q5cYM7i6W+Uvw2qUrcfOSnuisrX7bEx3b3Wx4QA==} engines: {node: '>= 14.0.0'} + deprecated: Package moved. Use @nats-io/transport-node from https://github.com/nats-io/nats.js natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} @@ -5022,8 +5023,8 @@ packages: node-releases@2.0.38: resolution: {integrity: sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==} - nodemailer@7.0.13: - resolution: {integrity: sha512-PNDFSJdP+KFgdsG3ZzMXCgquO7I6McjY2vlqILjtJd0hy8wEvtugS9xKRF2NWlPNGxvLCXlTNIae4serI7dinw==} + nodemailer@9.0.1: + resolution: {integrity: sha512-Gwv8SQewT616ZM/URn0H54b8PWo/Wum7md3EW2aWy1lO27+WZCX+Xyak3J+NlmHUjDh5ME+uesJUDRbR3Ye8Bw==} engines: {node: '>=6.0.0'} normalize-path@3.0.0: @@ -12365,7 +12366,7 @@ snapshots: node-releases@2.0.38: {} - nodemailer@7.0.13: {} + nodemailer@9.0.1: {} normalize-path@3.0.0: {} From 0ae3dc4d6d5dcb1b4179c1e27bf4e954e7c37122 Mon Sep 17 00:00:00 2001 From: sujitaw Date: Tue, 23 Jun 2026 17:37:04 +0530 Subject: [PATCH 09/57] wip Signed-off-by: sujitaw --- apps/agent-provisioning/src/main.ts | 14 ++++++++------ apps/agent-service/src/main.ts | 18 ++++++++++-------- apps/api-gateway/src/main.ts | 4 ++-- apps/cloud-wallet/src/main.ts | 14 ++++++++------ apps/connection/src/main.ts | 14 ++++++++------ apps/ecosystem/src/main.ts | 5 +++-- apps/geo-location/src/main.ts | 14 ++++++++------ apps/issuance/src/main.ts | 16 +++++++++------- apps/ledger/src/main.ts | 16 +++++++++------- apps/notification/src/main.ts | 14 ++++++++------ apps/oid4vc-issuance/src/main.ts | 14 ++++++++------ apps/oid4vc-verification/src/main.ts | 12 +++++++----- apps/organization/src/main.ts | 12 +++++++----- apps/user/src/main.ts | 5 +++-- apps/utility/src/main.ts | 14 ++++++++------ apps/verification/src/main.ts | 14 ++++++++------ apps/webhook/src/main.ts | 14 ++++++++------ apps/x509/src/main.ts | 14 ++++++++------ libs/config/src/index.ts | 2 +- 19 files changed, 131 insertions(+), 99 deletions(-) diff --git a/apps/agent-provisioning/src/main.ts b/apps/agent-provisioning/src/main.ts index fd7f31d86..f02b18dd2 100644 --- a/apps/agent-provisioning/src/main.ts +++ b/apps/agent-provisioning/src/main.ts @@ -1,20 +1,22 @@ import * as dotenv from 'dotenv'; -import { NestFactory } from '@nestjs/core'; -import { HttpExceptionFilter } from 'libs/http-exception.filter'; -import { Logger } from '@nestjs/common'; + import { MicroserviceOptions, Transport } from '@nestjs/microservices'; + import { AgentProvisioningModule } from './agent-provisioning.module'; -import { getNatsOptions } from '@credebl/common/nats.config'; import { CommonConstants } from '@credebl/common/common.constant'; +import { HttpExceptionFilter } from 'libs/http-exception.filter'; +import { Logger } from '@nestjs/common'; +import { NestFactory } from '@nestjs/core'; import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; -import { loadBaoSecrets } from '@credebl/config/bao-secrets'; +import { getNatsOptions } from '@credebl/common/nats.config'; +import { loadConfigSecrets } from '@credebl/config/secret-storage/secrets-loader'; dotenv.config(); const logger = new Logger(); async function bootstrap(): Promise { - await loadBaoSecrets(); + await loadConfigSecrets(); const app = await NestFactory.createMicroservice(AgentProvisioningModule, { transport: Transport.NATS, options: getNatsOptions( diff --git a/apps/agent-service/src/main.ts b/apps/agent-service/src/main.ts index 7bf1b795a..f992d5b96 100644 --- a/apps/agent-service/src/main.ts +++ b/apps/agent-service/src/main.ts @@ -1,23 +1,25 @@ import * as dotenv from 'dotenv'; -import { HttpExceptionFilter } from 'libs/http-exception.filter'; -import { Logger } from '@nestjs/common'; -import { NestFactory } from '@nestjs/core'; -import { AgentServiceModule } from './agent-service.module'; -import { AgentServiceService } from './agent-service.service'; + import { IAgentSpinupDto, IUserRequestInterface } from './interface/agent-service.interface'; import { MicroserviceOptions, Transport } from '@nestjs/microservices'; -import { getNatsOptions } from '@credebl/common/nats.config'; + +import { AgentServiceModule } from './agent-service.module'; +import { AgentServiceService } from './agent-service.service'; import { CommonConstants } from '@credebl/common/common.constant'; +import { HttpExceptionFilter } from 'libs/http-exception.filter'; import { Ledgers } from '@credebl/enum/enum'; +import { Logger } from '@nestjs/common'; +import { NestFactory } from '@nestjs/core'; import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; -import { loadBaoSecrets } from '@credebl/config/bao-secrets'; +import { getNatsOptions } from '@credebl/common/nats.config'; +import { loadConfigSecrets } from '@credebl/config/secret-storage/secrets-loader'; dotenv.config(); const logger = new Logger(); async function bootstrap(): Promise { - await loadBaoSecrets(); + await loadConfigSecrets(); const app = await NestFactory.createMicroservice(AgentServiceModule, { transport: Transport.NATS, options: getNatsOptions( diff --git a/apps/api-gateway/src/main.ts b/apps/api-gateway/src/main.ts index b8bcddbf6..c6e4da5cf 100755 --- a/apps/api-gateway/src/main.ts +++ b/apps/api-gateway/src/main.ts @@ -17,12 +17,12 @@ import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapt import { UpdatableValidationPipe } from '@credebl/common/custom-overrideable-validation-pipe'; import * as useragent from 'express-useragent'; import { EcosystemSwaggerFilter } from './authz/guards/ecosystem-swagger.filter'; -import { loadBaoSecrets } from '@credebl/config/bao-secrets'; +import { loadConfigSecrets } from '@credebl/config/secret-storage/secrets-loader'; dotenv.config(); async function bootstrap(): Promise { - await loadBaoSecrets(); + await loadConfigSecrets(); try { if (otelSDK) { diff --git a/apps/cloud-wallet/src/main.ts b/apps/cloud-wallet/src/main.ts index d0d4b9811..1da37b7ec 100644 --- a/apps/cloud-wallet/src/main.ts +++ b/apps/cloud-wallet/src/main.ts @@ -1,20 +1,22 @@ import * as dotenv from 'dotenv'; -import { NestFactory } from '@nestjs/core'; + +import { MicroserviceOptions, Transport } from '@nestjs/microservices'; + import { CloudWalletModule } from './cloud-wallet.module'; +import { CommonConstants } from '@credebl/common/common.constant'; import { HttpExceptionFilter } from 'libs/http-exception.filter'; import { Logger } from '@nestjs/common'; -import { MicroserviceOptions, Transport } from '@nestjs/microservices'; -import { getNatsOptions } from '@credebl/common/nats.config'; -import { CommonConstants } from '@credebl/common/common.constant'; +import { NestFactory } from '@nestjs/core'; import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; -import { loadBaoSecrets } from '@credebl/config/bao-secrets'; +import { getNatsOptions } from '@credebl/common/nats.config'; +import { loadConfigSecrets } from '@credebl/config/secret-storage/secrets-loader'; dotenv.config(); const logger = new Logger(); async function bootstrap(): Promise { - await loadBaoSecrets(); + await loadConfigSecrets(); const app = await NestFactory.createMicroservice(CloudWalletModule, { transport: Transport.NATS, options: getNatsOptions( diff --git a/apps/connection/src/main.ts b/apps/connection/src/main.ts index 81dac6a5f..10ee34ff8 100644 --- a/apps/connection/src/main.ts +++ b/apps/connection/src/main.ts @@ -1,20 +1,22 @@ import * as dotenv from 'dotenv'; -import { NestFactory } from '@nestjs/core'; + +import { MicroserviceOptions, Transport } from '@nestjs/microservices'; + +import { CommonConstants } from '@credebl/common/common.constant'; import { ConnectionModule } from './connection.module'; import { HttpExceptionFilter } from 'libs/http-exception.filter'; import { Logger } from '@nestjs/common'; -import { MicroserviceOptions, Transport } from '@nestjs/microservices'; -import { getNatsOptions } from '@credebl/common/nats.config'; -import { CommonConstants } from '@credebl/common/common.constant'; +import { NestFactory } from '@nestjs/core'; import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; -import { loadBaoSecrets } from '@credebl/config/bao-secrets'; +import { getNatsOptions } from '@credebl/common/nats.config'; +import { loadConfigSecrets } from '@credebl/config/secret-storage/secrets-loader'; dotenv.config(); const logger = new Logger(); async function bootstrap(): Promise { - await loadBaoSecrets(); + await loadConfigSecrets(); const app = await NestFactory.createMicroservice(ConnectionModule, { transport: Transport.NATS, options: getNatsOptions( diff --git a/apps/ecosystem/src/main.ts b/apps/ecosystem/src/main.ts index 16fd8fc8b..71fed8e97 100644 --- a/apps/ecosystem/src/main.ts +++ b/apps/ecosystem/src/main.ts @@ -1,4 +1,5 @@ import * as dotenv from 'dotenv'; + import { MicroserviceOptions, Transport } from '@nestjs/microservices'; import { CommonConstants } from '@credebl/common/common.constant'; @@ -9,14 +10,14 @@ import { NestFactory } from '@nestjs/core'; import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; // import { nkeyAuthenticator } from 'nats'; import { getNatsOptions } from '@credebl/common/nats.config'; -import { loadBaoSecrets } from '@credebl/config/bao-secrets'; +import { loadConfigSecrets } from '@credebl/config/secret-storage/secrets-loader'; dotenv.config(); const logger = new Logger(); async function bootstrap(): Promise { - await loadBaoSecrets(); + await loadConfigSecrets(); const app = await NestFactory.createMicroservice(EcosystemModule, { transport: Transport.NATS, options: getNatsOptions( diff --git a/apps/geo-location/src/main.ts b/apps/geo-location/src/main.ts index cb4b94602..9e16df13e 100644 --- a/apps/geo-location/src/main.ts +++ b/apps/geo-location/src/main.ts @@ -1,20 +1,22 @@ import * as dotenv from 'dotenv'; + +import { MicroserviceOptions, Transport } from '@nestjs/microservices'; + +import { CommonConstants } from '@credebl/common/common.constant'; +import { GeoLocationModule } from './geo-location.module'; import { HttpExceptionFilter } from 'libs/http-exception.filter'; import { Logger } from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; -import { MicroserviceOptions, Transport } from '@nestjs/microservices'; -import { getNatsOptions } from '@credebl/common/nats.config'; -import { GeoLocationModule } from './geo-location.module'; -import { CommonConstants } from '@credebl/common/common.constant'; import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; -import { loadBaoSecrets } from '@credebl/config/bao-secrets'; +import { getNatsOptions } from '@credebl/common/nats.config'; +import { loadConfigSecrets } from '@credebl/config/secret-storage/secrets-loader'; dotenv.config(); const logger = new Logger(); async function bootstrap(): Promise { - await loadBaoSecrets(); + await loadConfigSecrets(); const app = await NestFactory.createMicroservice(GeoLocationModule, { transport: Transport.NATS, options: getNatsOptions( diff --git a/apps/issuance/src/main.ts b/apps/issuance/src/main.ts index d89fb2437..9d137b58d 100644 --- a/apps/issuance/src/main.ts +++ b/apps/issuance/src/main.ts @@ -1,20 +1,22 @@ import * as dotenv from 'dotenv'; -import { NestFactory } from '@nestjs/core'; -import { HttpExceptionFilter } from 'libs/http-exception.filter'; -import { Logger } from '@nestjs/common'; + import { MicroserviceOptions, Transport } from '@nestjs/microservices'; -import { IssuanceModule } from '../src/issuance.module'; -import { getNatsOptions } from '@credebl/common/nats.config'; + import { CommonConstants } from '@credebl/common/common.constant'; +import { HttpExceptionFilter } from 'libs/http-exception.filter'; +import { IssuanceModule } from '../src/issuance.module'; +import { Logger } from '@nestjs/common'; +import { NestFactory } from '@nestjs/core'; import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; -import { loadBaoSecrets } from '@credebl/config/bao-secrets'; +import { getNatsOptions } from '@credebl/common/nats.config'; +import { loadConfigSecrets } from '@credebl/config/secret-storage/secrets-loader'; dotenv.config(); const logger = new Logger(); async function bootstrap(): Promise { - await loadBaoSecrets(); + await loadConfigSecrets(); const app = await NestFactory.createMicroservice(IssuanceModule, { transport: Transport.NATS, options: getNatsOptions( diff --git a/apps/ledger/src/main.ts b/apps/ledger/src/main.ts index 6c2ddbb5b..b25a6a6b0 100644 --- a/apps/ledger/src/main.ts +++ b/apps/ledger/src/main.ts @@ -1,18 +1,20 @@ import * as dotenv from 'dotenv'; -import { NestFactory } from '@nestjs/core'; -import { LedgerModule } from './ledger.module'; -import { HttpExceptionFilter } from 'libs/http-exception.filter'; -import { Logger } from '@nestjs/common'; + import { MicroserviceOptions, Transport } from '@nestjs/microservices'; -import { getNatsOptions } from '@credebl/common/nats.config'; + import { CommonConstants } from '@credebl/common/common.constant'; +import { HttpExceptionFilter } from 'libs/http-exception.filter'; +import { LedgerModule } from './ledger.module'; +import { Logger } from '@nestjs/common'; +import { NestFactory } from '@nestjs/core'; import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; -import { loadBaoSecrets } from '@credebl/config/bao-secrets'; +import { getNatsOptions } from '@credebl/common/nats.config'; +import { loadConfigSecrets } from '@credebl/config/secret-storage/secrets-loader'; dotenv.config(); async function bootstrap(): Promise { - await loadBaoSecrets(); + await loadConfigSecrets(); const app = await NestFactory.createMicroservice(LedgerModule, { transport: Transport.NATS, options: getNatsOptions(CommonConstants.LEDGER_SERVICE, process.env.LEDGER_NKEY_SEED, process.env.NATS_CREDS_FILE) diff --git a/apps/notification/src/main.ts b/apps/notification/src/main.ts index 4112bc44a..892524c40 100644 --- a/apps/notification/src/main.ts +++ b/apps/notification/src/main.ts @@ -1,20 +1,22 @@ import * as dotenv from 'dotenv'; -import { NestFactory } from '@nestjs/core'; + +import { MicroserviceOptions, Transport } from '@nestjs/microservices'; + +import { CommonConstants } from '@credebl/common/common.constant'; import { HttpExceptionFilter } from 'libs/http-exception.filter'; import { Logger } from '@nestjs/common'; -import { MicroserviceOptions, Transport } from '@nestjs/microservices'; +import { NestFactory } from '@nestjs/core'; +import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; import { NotificationModule } from '../src/notification.module'; import { getNatsOptions } from '@credebl/common/nats.config'; -import { CommonConstants } from '@credebl/common/common.constant'; -import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; -import { loadBaoSecrets } from '@credebl/config/bao-secrets'; +import { loadConfigSecrets } from '@credebl/config/secret-storage/secrets-loader'; dotenv.config(); const logger = new Logger(); async function bootstrap(): Promise { - await loadBaoSecrets(); + await loadConfigSecrets(); const app = await NestFactory.createMicroservice(NotificationModule, { transport: Transport.NATS, options: getNatsOptions( diff --git a/apps/oid4vc-issuance/src/main.ts b/apps/oid4vc-issuance/src/main.ts index bfe17a72f..4e9702a07 100644 --- a/apps/oid4vc-issuance/src/main.ts +++ b/apps/oid4vc-issuance/src/main.ts @@ -1,20 +1,22 @@ import * as dotenv from 'dotenv'; -import { NestFactory } from '@nestjs/core'; -import { HttpExceptionFilter } from 'libs/http-exception.filter'; -import { Logger } from '@nestjs/common'; + import { MicroserviceOptions, Transport } from '@nestjs/microservices'; -import { getNatsOptions } from '@credebl/common/nats.config'; + import { CommonConstants } from '@credebl/common/common.constant'; +import { HttpExceptionFilter } from 'libs/http-exception.filter'; +import { Logger } from '@nestjs/common'; +import { NestFactory } from '@nestjs/core'; import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; import { Oid4vcIssuanceModule } from './oid4vc-issuance.module'; -import { loadBaoSecrets } from '@credebl/config/bao-secrets'; +import { getNatsOptions } from '@credebl/common/nats.config'; +import { loadConfigSecrets } from '@credebl/config/secret-storage/secrets-loader'; dotenv.config(); const logger = new Logger(); async function bootstrap(): Promise { - await loadBaoSecrets(); + await loadConfigSecrets(); if (!process.env.STATUS_LIST_HOST) { logger.warn('STATUS_LIST_HOST is not configured. Revocable SD-JWT flow will be disabled.'); diff --git a/apps/oid4vc-verification/src/main.ts b/apps/oid4vc-verification/src/main.ts index e86a8ee32..2303643e6 100644 --- a/apps/oid4vc-verification/src/main.ts +++ b/apps/oid4vc-verification/src/main.ts @@ -1,19 +1,21 @@ import * as dotenv from 'dotenv'; -import { NestFactory } from '@nestjs/core'; -import { Logger } from '@nestjs/common'; + import { MicroserviceOptions, Transport } from '@nestjs/microservices'; -import { getNatsOptions } from '@credebl/common/nats.config'; + import { CommonConstants } from '@credebl/common/common.constant'; +import { Logger } from '@nestjs/common'; +import { NestFactory } from '@nestjs/core'; import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; import { Oid4vpModule } from './oid4vc-verification.module'; -import { loadBaoSecrets } from '@credebl/config/bao-secrets'; +import { getNatsOptions } from '@credebl/common/nats.config'; +import { loadConfigSecrets } from '@credebl/config/secret-storage/secrets-loader'; dotenv.config(); const logger = new Logger(); async function bootstrap(): Promise { - await loadBaoSecrets(); + await loadConfigSecrets(); const app = await NestFactory.createMicroservice(Oid4vpModule, { transport: Transport.NATS, options: getNatsOptions( diff --git a/apps/organization/src/main.ts b/apps/organization/src/main.ts index f8dc27f48..625c4f59c 100644 --- a/apps/organization/src/main.ts +++ b/apps/organization/src/main.ts @@ -1,21 +1,23 @@ import * as dotenv from 'dotenv'; + +import { MicroserviceOptions, Transport } from '@nestjs/microservices'; + +import { CommonConstants } from '@credebl/common/common.constant'; import { HttpExceptionFilter } from 'libs/http-exception.filter'; import { Logger } from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; +import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; import { OrganizationModule } from './organization.module'; -import { MicroserviceOptions, Transport } from '@nestjs/microservices'; // import { nkeyAuthenticator } from 'nats'; import { getNatsOptions } from '@credebl/common/nats.config'; -import { CommonConstants } from '@credebl/common/common.constant'; -import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; -import { loadBaoSecrets } from '@credebl/config/bao-secrets'; +import { loadConfigSecrets } from '@credebl/config/secret-storage/secrets-loader'; dotenv.config(); const logger = new Logger(); async function bootstrap(): Promise { - await loadBaoSecrets(); + await loadConfigSecrets(); const app = await NestFactory.createMicroservice(OrganizationModule, { transport: Transport.NATS, options: getNatsOptions( diff --git a/apps/user/src/main.ts b/apps/user/src/main.ts index cf52215ad..cd3d9ad77 100644 --- a/apps/user/src/main.ts +++ b/apps/user/src/main.ts @@ -1,4 +1,5 @@ import * as dotenv from 'dotenv'; + import { MicroserviceOptions, Transport } from '@nestjs/microservices'; import { CommonConstants } from '@credebl/common/common.constant'; @@ -8,14 +9,14 @@ import { NestFactory } from '@nestjs/core'; import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; import { UserModule } from './user.module'; import { getNatsOptions } from '@credebl/common/nats.config'; -import { loadBaoSecrets } from '@credebl/config/bao-secrets'; +import { loadConfigSecrets } from '@credebl/config/secret-storage/secrets-loader'; dotenv.config(); const logger = new Logger(); async function bootstrap(): Promise { - await loadBaoSecrets(); + await loadConfigSecrets(); const app = await NestFactory.createMicroservice(UserModule, { transport: Transport.NATS, options: getNatsOptions(CommonConstants.USER_SERVICE, process.env.USER_NKEY_SEED, process.env.NATS_CREDS_FILE) diff --git a/apps/utility/src/main.ts b/apps/utility/src/main.ts index e0173b4ee..07d9bfa10 100644 --- a/apps/utility/src/main.ts +++ b/apps/utility/src/main.ts @@ -1,20 +1,22 @@ import * as dotenv from 'dotenv'; + +import { MicroserviceOptions, Transport } from '@nestjs/microservices'; + +import { CommonConstants } from '@credebl/common/common.constant'; import { HttpExceptionFilter } from 'libs/http-exception.filter'; import { Logger } from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; -import { MicroserviceOptions, Transport } from '@nestjs/microservices'; -import { getNatsOptions } from '@credebl/common/nats.config'; -import { UtilitiesModule } from './utilities.module'; -import { CommonConstants } from '@credebl/common/common.constant'; import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; -import { loadBaoSecrets } from '@credebl/config/bao-secrets'; +import { UtilitiesModule } from './utilities.module'; +import { getNatsOptions } from '@credebl/common/nats.config'; +import { loadConfigSecrets } from '@credebl/config/secret-storage/secrets-loader'; dotenv.config(); const logger = new Logger(); async function bootstrap(): Promise { - await loadBaoSecrets(); + await loadConfigSecrets(); const app = await NestFactory.createMicroservice(UtilitiesModule, { transport: Transport.NATS, options: getNatsOptions( diff --git a/apps/verification/src/main.ts b/apps/verification/src/main.ts index 046d9a0e0..5fe2fd73d 100644 --- a/apps/verification/src/main.ts +++ b/apps/verification/src/main.ts @@ -1,20 +1,22 @@ import * as dotenv from 'dotenv'; -import { NestFactory } from '@nestjs/core'; + +import { MicroserviceOptions, Transport } from '@nestjs/microservices'; + +import { CommonConstants } from '@credebl/common/common.constant'; import { HttpExceptionFilter } from 'libs/http-exception.filter'; import { Logger } from '@nestjs/common'; -import { MicroserviceOptions, Transport } from '@nestjs/microservices'; +import { NestFactory } from '@nestjs/core'; +import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; import { VerificationModule } from './verification.module'; import { getNatsOptions } from '@credebl/common/nats.config'; -import { CommonConstants } from '@credebl/common/common.constant'; -import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; -import { loadBaoSecrets } from '@credebl/config/bao-secrets'; +import { loadConfigSecrets } from '@credebl/config/secret-storage/secrets-loader'; dotenv.config(); const logger = new Logger(); async function bootstrap(): Promise { - await loadBaoSecrets(); + await loadConfigSecrets(); const app = await NestFactory.createMicroservice(VerificationModule, { transport: Transport.NATS, options: getNatsOptions( diff --git a/apps/webhook/src/main.ts b/apps/webhook/src/main.ts index 25b3380ce..a7cc3bb61 100644 --- a/apps/webhook/src/main.ts +++ b/apps/webhook/src/main.ts @@ -1,20 +1,22 @@ import * as dotenv from 'dotenv'; -import { NestFactory } from '@nestjs/core'; + +import { MicroserviceOptions, Transport } from '@nestjs/microservices'; + +import { CommonConstants } from '@credebl/common/common.constant'; import { HttpExceptionFilter } from 'libs/http-exception.filter'; import { Logger } from '@nestjs/common'; -import { MicroserviceOptions, Transport } from '@nestjs/microservices'; +import { NestFactory } from '@nestjs/core'; +import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; import { WebhookModule } from '../src/webhook.module'; import { getNatsOptions } from '@credebl/common/nats.config'; -import { CommonConstants } from '@credebl/common/common.constant'; -import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; -import { loadBaoSecrets } from '@credebl/config/bao-secrets'; +import { loadConfigSecrets } from '@credebl/config/secret-storage/secrets-loader'; dotenv.config(); const logger = new Logger(); async function bootstrap(): Promise { - await loadBaoSecrets(); + await loadConfigSecrets(); const app = await NestFactory.createMicroservice(WebhookModule, { transport: Transport.NATS, options: getNatsOptions( diff --git a/apps/x509/src/main.ts b/apps/x509/src/main.ts index 208e1d3aa..b181e45fd 100644 --- a/apps/x509/src/main.ts +++ b/apps/x509/src/main.ts @@ -1,18 +1,20 @@ import * as dotenv from 'dotenv'; -import { NestFactory } from '@nestjs/core'; -import { HttpExceptionFilter } from 'libs/http-exception.filter'; -import { Logger } from '@nestjs/common'; + import { MicroserviceOptions, Transport } from '@nestjs/microservices'; -import { getNatsOptions } from '@credebl/common/nats.config'; + import { CommonConstants } from '@credebl/common/common.constant'; +import { HttpExceptionFilter } from 'libs/http-exception.filter'; +import { Logger } from '@nestjs/common'; +import { NestFactory } from '@nestjs/core'; import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; import { X509Module } from './x509.module'; -import { loadBaoSecrets } from '@credebl/config/bao-secrets'; +import { getNatsOptions } from '@credebl/common/nats.config'; +import { loadConfigSecrets } from '@credebl/config/secret-storage/secrets-loader'; dotenv.config(); async function bootstrap(): Promise { - await loadBaoSecrets(); + await loadConfigSecrets(); const app = await NestFactory.createMicroservice(X509Module, { transport: Transport.NATS, options: getNatsOptions(CommonConstants.X509_SERVICE, process.env.X509_NKEY_SEED, process.env.NATS_CREDS_FILE) diff --git a/libs/config/src/index.ts b/libs/config/src/index.ts index 0ec7a716f..c2451a178 100644 --- a/libs/config/src/index.ts +++ b/libs/config/src/index.ts @@ -1,4 +1,4 @@ export * from './config.service'; export * from './config.module'; export * from './global-config.module'; -export { loadBaoSecrets } from './bao-secrets'; +export { loadBaoSecrets } from './secret-storage/bao-secrets'; From 3f193b3bd2ae340176f9426ae68d884b490571fe Mon Sep 17 00:00:00 2001 From: sujitaw Date: Tue, 23 Jun 2026 17:40:51 +0530 Subject: [PATCH 10/57] wip Signed-off-by: sujitaw --- .../src/secret-storage/openbao.provider.ts | 61 +++++++++++++++++ .../secret-provider.interface.ts | 13 ++++ .../src/secret-storage/secrets-loader.ts | 65 +++++++++++++++++++ 3 files changed, 139 insertions(+) create mode 100644 libs/config/src/secret-storage/openbao.provider.ts create mode 100644 libs/config/src/secret-storage/secret-provider.interface.ts create mode 100644 libs/config/src/secret-storage/secrets-loader.ts diff --git a/libs/config/src/secret-storage/openbao.provider.ts b/libs/config/src/secret-storage/openbao.provider.ts new file mode 100644 index 000000000..7ec73b072 --- /dev/null +++ b/libs/config/src/secret-storage/openbao.provider.ts @@ -0,0 +1,61 @@ +import { SecretProvider } from './secret-provider.interface'; + +export class OpenBaoProvider implements SecretProvider { + readonly name = 'OpenBao'; + + isEnabled(): boolean { + return 'true' === process.env.ENABLE_BAO?.trim()?.toLowerCase(); + } + + async loadSecrets(): Promise> { + const baoUrl = process.env.BAO_URL; + const secretPath = process.env.BAO_SECRET_PATH; + const roleId = process.env.BAO_ROLE_ID; + const secretId = process.env.BAO_SECRET_ID; + + if (!roleId || !secretId) { + throw new Error('BAO_ROLE_ID and BAO_SECRET_ID must be set.'); + } + + // --- Authentication --- + const authResponse = await fetch(`${baoUrl}/v1/auth/approle/login`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + // eslint-disable-next-line camelcase + body: JSON.stringify({ role_id: roleId, secret_id: secretId }) + }); + + if (!authResponse.ok) { + throw new Error(`Authentication failed: Status ${authResponse.status}`); + } + + const authData = await authResponse.json(); + const baoToken = authData.auth?.client_token; + + if (!baoToken) { + throw new Error('Failed to retrieve client token from OpenBao.'); + } + + // --- Fetch Secrets --- + const response = await fetch(`${baoUrl}/v1/${secretPath}`, { + method: 'GET', + headers: { + 'X-Vault-Token': baoToken, + 'Content-Type': 'application/json' + } + }); + + if (!response.ok) { + throw new Error(`Fetch failed: Status ${response.status}`); + } + + const result = await response.json(); + const secrets = result.data?.data; + // console.log('🔐 Successfully fetched secrets from OpenBao', result); + if (!secrets || 'object' !== typeof secrets || Array.isArray(secrets)) { + throw new Error('Unexpected secrets payload structure.'); + } + + return secrets as Record; + } +} diff --git a/libs/config/src/secret-storage/secret-provider.interface.ts b/libs/config/src/secret-storage/secret-provider.interface.ts new file mode 100644 index 000000000..9d734319e --- /dev/null +++ b/libs/config/src/secret-storage/secret-provider.interface.ts @@ -0,0 +1,13 @@ +export interface SecretProvider { + /** + * A human-readable name used for logging purposes (e.g., 'AWS Secrets Manager') + */ + name: string; + + /** + * Fetches the secrets from the external service and returns them + * as a flat key-value object of strings. + * * @returns A promise resolving to Record + */ + loadSecrets(): Promise>; +} diff --git a/libs/config/src/secret-storage/secrets-loader.ts b/libs/config/src/secret-storage/secrets-loader.ts new file mode 100644 index 000000000..251a37f4d --- /dev/null +++ b/libs/config/src/secret-storage/secrets-loader.ts @@ -0,0 +1,65 @@ +// import { AwsSecretsProvider } from './providers/aws-secrets.provider'; +// libs/config/src/secrets-loader.ts +import { Logger } from '@nestjs/common'; +import { OpenBaoProvider } from './openbao.provider'; +import { SecretProvider } from './secret-provider.interface'; + +const FORBIDDEN_KEYS = new Set(['__proto__', 'constructor', 'prototype']); +const logger = new Logger('SecretsLoader'); + +/** + * Factory function to get the requested secret provider + */ +function getSecretProvider(providerType: string): SecretProvider | null { + switch (providerType.toLowerCase()) { + case 'openbao': + return new OpenBaoProvider(); + + default: + return null; + } +} + +export async function loadConfigSecrets(): Promise { + const providerType = process.env.SECRETS_PROVIDER?.trim(); + + if (!providerType) { + logger.log('SECRETS_PROVIDER not set. Skipping remote secrets fetching.'); + return; + } + + const provider = getSecretProvider(providerType); + + if (!provider) { + logger.warn( + `⚠️ Unsupported SECRETS_PROVIDER value "${providerType}". Falling back to local environment variables.` + ); + return; + } + + logger.log(`Secrets management enabled via [${provider.name}]. Fetching...`); + + try { + const secrets = await provider.loadSecrets(); + + for (const [key, value] of Object.entries(secrets)) { + if (FORBIDDEN_KEYS.has(key)) { + continue; + } + + Object.defineProperty(process.env, key, { + value: String(value), + enumerable: true, + configurable: true, + writable: true + }); + } + + logger.log(`Successfully injected secrets from ${provider.name} into process.env`); + } catch (error) { + // Safely handles instances where error might not be a standard Error object + const errorMessage = error instanceof Error ? error.message : String(error); + logger.error(`Critical Lifecycle Boot Failure (${provider.name}): ${errorMessage}`); + throw new Error(`Failed to load secrets from ${provider.name}`); + } +} From 8e5a3a14bd0870c867fb735f47ab77491fa04b4c Mon Sep 17 00:00:00 2001 From: sujitaw Date: Thu, 25 Jun 2026 17:31:15 +0530 Subject: [PATCH 11/57] wip Signed-off-by: sujitaw --- libs/common/src/common.constant.ts | 4 ++++ libs/common/src/resend-helper-file.ts | 13 ++++++++++--- libs/config/src/index.ts | 2 +- libs/config/src/secret-storage/openbao.provider.ts | 10 +++++----- 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/libs/common/src/common.constant.ts b/libs/common/src/common.constant.ts index 99d23bb4d..44fed668e 100644 --- a/libs/common/src/common.constant.ts +++ b/libs/common/src/common.constant.ts @@ -443,6 +443,10 @@ export enum CommonConstants { OIDC_VERIFIER_SESSION_RESPONSE_GET_BY_ID = 'get-oid4vp-verifier-session-response-id', OIDC_VERIFIER_SESSION_AUTH_RESPONSE_VERIFY = 'verify-oid4vp-verifier-session-auth-response', + // BAO_STORAGE_PATH + CREDEBL_RESEND_API_KEY_PATH = 'credebl_resend_api_key', + + //X509 X509_CREATE_CERTIFICATE = 'create-x509-certificate', X509_IMPORT_CERTIFICATE = 'import-x509-certificate', diff --git a/libs/common/src/resend-helper-file.ts b/libs/common/src/resend-helper-file.ts index 1c1170724..b83802f02 100644 --- a/libs/common/src/resend-helper-file.ts +++ b/libs/common/src/resend-helper-file.ts @@ -1,15 +1,22 @@ +import { CommonConstants } from './common.constant'; import { EmailDto } from './dtos/email.dto'; import { Logger } from '@nestjs/common'; +import { OpenBaoProvider } from 'libs/config/src/secret-storage/openbao.provider'; import { Resend } from 'resend'; let resend: Resend | null = null; -function getResendClient(): Resend { +async function getResendClient(): Promise { if (resend) { return resend; } + console.log("get resend client called"); + const openBaoProvider = new OpenBaoProvider(); + const secretPath = CommonConstants.CREDEBL_RESEND_API_KEY_PATH || ''; - const apiKey = process.env.RESEND_API_KEY; + const secrets = await openBaoProvider.loadSecrets(secretPath); + console.log('🔐 Successfully fetched secrets from OpenBao for Resend API Key', secrets); + const apiKey = secrets.RESEND_API_KEY; if (!apiKey) { throw new Error('Missing RESEND_API_KEY in environment variables.'); @@ -21,7 +28,7 @@ function getResendClient(): Resend { export const sendWithResend = async (emailDto: EmailDto): Promise => { try { - const client = getResendClient(); + const client = await getResendClient(); const response = await client.emails.send({ from: emailDto.emailFrom, diff --git a/libs/config/src/index.ts b/libs/config/src/index.ts index c2451a178..e5e70bc39 100644 --- a/libs/config/src/index.ts +++ b/libs/config/src/index.ts @@ -1,4 +1,4 @@ export * from './config.service'; export * from './config.module'; export * from './global-config.module'; -export { loadBaoSecrets } from './secret-storage/bao-secrets'; +export { loadConfigSecrets } from './secret-storage/secrets-loader'; diff --git a/libs/config/src/secret-storage/openbao.provider.ts b/libs/config/src/secret-storage/openbao.provider.ts index 7ec73b072..2b0c63f7a 100644 --- a/libs/config/src/secret-storage/openbao.provider.ts +++ b/libs/config/src/secret-storage/openbao.provider.ts @@ -7,12 +7,12 @@ export class OpenBaoProvider implements SecretProvider { return 'true' === process.env.ENABLE_BAO?.trim()?.toLowerCase(); } - async loadSecrets(): Promise> { + async loadSecrets(customPath:string = ''): Promise> { const baoUrl = process.env.BAO_URL; - const secretPath = process.env.BAO_SECRET_PATH; + const secretPath = customPath || process.env.BAO_SECRET_PATH; const roleId = process.env.BAO_ROLE_ID; const secretId = process.env.BAO_SECRET_ID; - + console.log(`🔐 OpenBaoProvider: Fetching secrets from ${baoUrl}/v1/${secretPath} with roleId=${roleId}`); if (!roleId || !secretId) { throw new Error('BAO_ROLE_ID and BAO_SECRET_ID must be set.'); } @@ -24,7 +24,7 @@ export class OpenBaoProvider implements SecretProvider { // eslint-disable-next-line camelcase body: JSON.stringify({ role_id: roleId, secret_id: secretId }) }); - + console.log(`🔐 OpenBaoProvider: Authentication response status: ${JSON.stringify(authResponse)}`); if (!authResponse.ok) { throw new Error(`Authentication failed: Status ${authResponse.status}`); } @@ -44,7 +44,7 @@ export class OpenBaoProvider implements SecretProvider { 'Content-Type': 'application/json' } }); - + console.log(`🔐 OpenBaoProvider: Fetch secrets response status: ${JSON.stringify(response)}`); if (!response.ok) { throw new Error(`Fetch failed: Status ${response.status}`); } From ff0733a875d477401bdaeff6d6303fcbcee48a6e Mon Sep 17 00:00:00 2001 From: sujitaw Date: Fri, 26 Jun 2026 13:13:08 +0530 Subject: [PATCH 12/57] wip Signed-off-by: sujitaw --- libs/common/src/common.constant.ts | 2 +- libs/common/src/resend-helper-file.ts | 4 ++-- .../src/secret-storage/openbao.provider.ts | 17 ++++++++++------- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/libs/common/src/common.constant.ts b/libs/common/src/common.constant.ts index 44fed668e..b00706aec 100644 --- a/libs/common/src/common.constant.ts +++ b/libs/common/src/common.constant.ts @@ -444,7 +444,7 @@ export enum CommonConstants { OIDC_VERIFIER_SESSION_AUTH_RESPONSE_VERIFY = 'verify-oid4vp-verifier-session-auth-response', // BAO_STORAGE_PATH - CREDEBL_RESEND_API_KEY_PATH = 'credebl_resend_api_key', + CREDEBL_RESEND_API_KEY_PATH = 'secret/data/credebl_resend_api_key', //X509 diff --git a/libs/common/src/resend-helper-file.ts b/libs/common/src/resend-helper-file.ts index b83802f02..499d24869 100644 --- a/libs/common/src/resend-helper-file.ts +++ b/libs/common/src/resend-helper-file.ts @@ -29,7 +29,7 @@ async function getResendClient(): Promise { export const sendWithResend = async (emailDto: EmailDto): Promise => { try { const client = await getResendClient(); - + console.log('Sending email with Resend:', emailDto); const response = await client.emails.send({ from: emailDto.emailFrom, to: emailDto.emailTo, @@ -38,7 +38,7 @@ export const sendWithResend = async (emailDto: EmailDto): Promise => { html: emailDto.emailHtml, attachments: emailDto.emailAttachments }); - + console.log('Resend email response:', response); return Boolean(response.data?.id); } catch (error) { Logger.error('Error while sending email with Resend', error); diff --git a/libs/config/src/secret-storage/openbao.provider.ts b/libs/config/src/secret-storage/openbao.provider.ts index 2b0c63f7a..e1ed90fc8 100644 --- a/libs/config/src/secret-storage/openbao.provider.ts +++ b/libs/config/src/secret-storage/openbao.provider.ts @@ -16,26 +16,28 @@ export class OpenBaoProvider implements SecretProvider { if (!roleId || !secretId) { throw new Error('BAO_ROLE_ID and BAO_SECRET_ID must be set.'); } - + console.log(`🔐 OpenBaoProvider: Using roleId=${roleId} and secretId=${secretId}`); + console.log(`${baoUrl}/v1/auth/approle/login`) // --- Authentication --- const authResponse = await fetch(`${baoUrl}/v1/auth/approle/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, // eslint-disable-next-line camelcase body: JSON.stringify({ role_id: roleId, secret_id: secretId }) - }); - console.log(`🔐 OpenBaoProvider: Authentication response status: ${JSON.stringify(authResponse)}`); + }) + console.log(`🔐 OpenBaoProvider: Authentication response status: ${authResponse}`); if (!authResponse.ok) { throw new Error(`Authentication failed: Status ${authResponse.status}`); } - + const authData = await authResponse.json(); + console.log("authresponse", authData); const baoToken = authData.auth?.client_token; if (!baoToken) { throw new Error('Failed to retrieve client token from OpenBao.'); } - + console.log("request url",`${baoUrl}/v1/${secretPath}`) // --- Fetch Secrets --- const response = await fetch(`${baoUrl}/v1/${secretPath}`, { method: 'GET', @@ -44,14 +46,15 @@ export class OpenBaoProvider implements SecretProvider { 'Content-Type': 'application/json' } }); - console.log(`🔐 OpenBaoProvider: Fetch secrets response status: ${JSON.stringify(response)}`); + console.log(`🔐 OpenBaoProvider: Fetch secrets response status: ${response}`); if (!response.ok) { + console.log("inside not ok") throw new Error(`Fetch failed: Status ${response.status}`); } const result = await response.json(); const secrets = result.data?.data; - // console.log('🔐 Successfully fetched secrets from OpenBao', result); + console.log('🔐 Successfully fetched secrets from OpenBao', result); if (!secrets || 'object' !== typeof secrets || Array.isArray(secrets)) { throw new Error('Unexpected secrets payload structure.'); } From 6d63f367f664aed291c2959f5961fb33c8fa0ec6 Mon Sep 17 00:00:00 2001 From: sujitaw Date: Mon, 29 Jun 2026 14:02:06 +0530 Subject: [PATCH 13/57] feat: completed implementation on aws Signed-off-by: sujitaw --- libs/aws/src/aws.service.ts | 76 +++++++++++-------- libs/common/src/common.constant.ts | 2 +- libs/common/src/resend-helper-file.ts | 20 +---- .../src/secret-storage/openbao.provider.ts | 21 +++-- 4 files changed, 57 insertions(+), 62 deletions(-) diff --git a/libs/aws/src/aws.service.ts b/libs/aws/src/aws.service.ts index 1f402a7f4..e125ddcc8 100644 --- a/libs/aws/src/aws.service.ts +++ b/libs/aws/src/aws.service.ts @@ -1,32 +1,36 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { CommonConstants } from 'libs/common/src/common.constant'; import { RpcException } from '@nestjs/microservices'; import { S3 } from 'aws-sdk'; -import { promisify } from 'util'; +import { fetchOpenBaoSecrets } from 'libs/common/src/utils/openbao.util'; @Injectable() export class AwsService { - private s3: S3; - private s4: S3; - private s3StoreObject: S3; - - constructor() { - this.s3 = new S3({ - accessKeyId: process.env.AWS_ACCESS_KEY, - secretAccessKey: process.env.AWS_SECRET_KEY, - region: process.env.AWS_REGION + private async getS3Client(): Promise { + const secrets = await fetchOpenBaoSecrets(CommonConstants.CREDEBL_AWS_KEY_PATH); + return new S3({ + accessKeyId: secrets.AWS_ACCESS_KEY, + secretAccessKey: secrets.AWS_SECRET_KEY, + region: secrets.AWS_REGION }); + } - this.s4 = new S3({ - accessKeyId: process.env.AWS_PUBLIC_ACCESS_KEY, - secretAccessKey: process.env.AWS_PUBLIC_SECRET_KEY, - region: process.env.AWS_PUBLIC_REGION + private async getPublicS3Client(): Promise { + const secrets = await fetchOpenBaoSecrets(CommonConstants.CREDEBL_AWS_KEY_PATH); + return new S3({ + accessKeyId: secrets.AWS_PUBLIC_ACCESS_KEY, + secretAccessKey: secrets.AWS_PUBLIC_SECRET_KEY, + region: secrets.AWS_PUBLIC_REGION }); + } - this.s3StoreObject = new S3({ - accessKeyId: process.env.AWS_S3_STOREOBJECT_ACCESS_KEY, - secretAccessKey: process.env.AWS_S3_STOREOBJECT_SECRET_KEY, - region: process.env.AWS_S3_STOREOBJECT_REGION + private async getStoreObjectS3Client(): Promise { + const secrets = await fetchOpenBaoSecrets(CommonConstants.CREDEBL_AWS_KEY_PATH); + return new S3({ + accessKeyId: secrets.AWS_S3_STOREOBJECT_ACCESS_KEY, + secretAccessKey: secrets.AWS_S3_STOREOBJECT_SECRET_KEY, + region: secrets.AWS_S3_STOREOBJECT_REGION }); } @@ -38,17 +42,19 @@ export class AwsService { encoding: string, pathAWS: string = '' ): Promise { + const s4 = await this.getPublicS3Client(); const timestamp = Date.now(); - const putObjectAsync = promisify(this.s4.putObject).bind(this.s4); try { - await putObjectAsync({ - Bucket: `${bucketName}`, - Key: `${pathAWS}/${encodeURIComponent(filename)}-${timestamp}.${ext}`, - Body: fileBuffer, - ContentEncoding: encoding, - ContentType: `image/png` - }); + await s4 + .putObject({ + Bucket: bucketName, + Key: `${pathAWS}/${encodeURIComponent(filename)}-${timestamp}.${ext}`, + Body: fileBuffer, + ContentEncoding: encoding, + ContentType: 'image/png' + }) + .promise(); const imageUrl = `https://${bucketName}.s3.${process.env.AWS_PUBLIC_REGION}.amazonaws.com/${pathAWS}/${encodeURIComponent(filename)}-${timestamp}.${ext}`; return imageUrl; @@ -58,44 +64,48 @@ export class AwsService { } async uploadCsvFile(key: string, body: unknown): Promise { + const s3 = await this.getS3Client(); const params: AWS.S3.PutObjectRequest = { - Bucket: process.env.AWS_BUCKET, + Bucket: process.env.FILE_SHARING_BUCKET, Key: key, Body: 'string' === typeof body ? body : body.toString() }; try { - await this.s3.upload(params).promise(); + await s3.upload(params).promise(); } catch (error) { throw new RpcException(error.response ? error.response : error); } } async getFile(key: string): Promise { + const s3 = await this.getS3Client(); const params: AWS.S3.GetObjectRequest = { - Bucket: process.env.AWS_BUCKET, + Bucket: process.env.FILE_SHARING_BUCKET, Key: key }; try { - return this.s3.getObject(params).promise(); + return s3.getObject(params).promise(); } catch (error) { throw new RpcException(error.response ? error.response : error); } } async deleteFile(key: string): Promise { + const s3 = await this.getS3Client(); const params: AWS.S3.DeleteObjectRequest = { - Bucket: process.env.AWS_BUCKET, + Bucket: process.env.FILE_SHARING_BUCKET, Key: key }; try { - await this.s3.deleteObject(params).promise(); + await s3.deleteObject(params).promise(); } catch (error) { throw new RpcException(error.response ? error.response : error); } } async storeObject(persistent: boolean, key: string, body: unknown): Promise { + const s3StoreObject = await this.getStoreObjectS3Client(); const objKey: string = persistent.valueOf() ? `persist/${key}` : `default/${key}`; const buf = Buffer.from(JSON.stringify(body)); const params: AWS.S3.PutObjectRequest = { @@ -107,7 +117,7 @@ export class AwsService { }; try { - const receivedData = await this.s3StoreObject.upload(params).promise(); + const receivedData = await s3StoreObject.upload(params).promise(); return receivedData; } catch (error) { throw new RpcException(error.response ? error.response : error); diff --git a/libs/common/src/common.constant.ts b/libs/common/src/common.constant.ts index b00706aec..6eac4ffff 100644 --- a/libs/common/src/common.constant.ts +++ b/libs/common/src/common.constant.ts @@ -445,7 +445,7 @@ export enum CommonConstants { // BAO_STORAGE_PATH CREDEBL_RESEND_API_KEY_PATH = 'secret/data/credebl_resend_api_key', - + CREDEBL_AWS_KEY_PATH = 'secret/data/credebl_aws_keys', //X509 X509_CREATE_CERTIFICATE = 'create-x509-certificate', diff --git a/libs/common/src/resend-helper-file.ts b/libs/common/src/resend-helper-file.ts index 499d24869..a19d5fd4f 100644 --- a/libs/common/src/resend-helper-file.ts +++ b/libs/common/src/resend-helper-file.ts @@ -1,35 +1,24 @@ import { CommonConstants } from './common.constant'; import { EmailDto } from './dtos/email.dto'; import { Logger } from '@nestjs/common'; -import { OpenBaoProvider } from 'libs/config/src/secret-storage/openbao.provider'; import { Resend } from 'resend'; - -let resend: Resend | null = null; +import { fetchOpenBaoSecrets } from './utils/openbao.util'; async function getResendClient(): Promise { - if (resend) { - return resend; - } - console.log("get resend client called"); - const openBaoProvider = new OpenBaoProvider(); - const secretPath = CommonConstants.CREDEBL_RESEND_API_KEY_PATH || ''; - - const secrets = await openBaoProvider.loadSecrets(secretPath); - console.log('🔐 Successfully fetched secrets from OpenBao for Resend API Key', secrets); + const secretPath = CommonConstants.CREDEBL_RESEND_API_KEY_PATH; + const secrets = await fetchOpenBaoSecrets(secretPath); const apiKey = secrets.RESEND_API_KEY; if (!apiKey) { throw new Error('Missing RESEND_API_KEY in environment variables.'); } - resend = new Resend(apiKey); - return resend; + return new Resend(apiKey); } export const sendWithResend = async (emailDto: EmailDto): Promise => { try { const client = await getResendClient(); - console.log('Sending email with Resend:', emailDto); const response = await client.emails.send({ from: emailDto.emailFrom, to: emailDto.emailTo, @@ -38,7 +27,6 @@ export const sendWithResend = async (emailDto: EmailDto): Promise => { html: emailDto.emailHtml, attachments: emailDto.emailAttachments }); - console.log('Resend email response:', response); return Boolean(response.data?.id); } catch (error) { Logger.error('Error while sending email with Resend', error); diff --git a/libs/config/src/secret-storage/openbao.provider.ts b/libs/config/src/secret-storage/openbao.provider.ts index e1ed90fc8..4aad27300 100644 --- a/libs/config/src/secret-storage/openbao.provider.ts +++ b/libs/config/src/secret-storage/openbao.provider.ts @@ -1,43 +1,42 @@ +import { Logger } from '@nestjs/common'; import { SecretProvider } from './secret-provider.interface'; export class OpenBaoProvider implements SecretProvider { readonly name = 'OpenBao'; + private readonly logger = new Logger(OpenBaoProvider.name); isEnabled(): boolean { return 'true' === process.env.ENABLE_BAO?.trim()?.toLowerCase(); } - async loadSecrets(customPath:string = ''): Promise> { + async loadSecrets(customPath: string = ''): Promise> { const baoUrl = process.env.BAO_URL; const secretPath = customPath || process.env.BAO_SECRET_PATH; const roleId = process.env.BAO_ROLE_ID; const secretId = process.env.BAO_SECRET_ID; - console.log(`🔐 OpenBaoProvider: Fetching secrets from ${baoUrl}/v1/${secretPath} with roleId=${roleId}`); + this.logger.log(`Fetching secrets from ${baoUrl}/v1/${secretPath}`); if (!roleId || !secretId) { throw new Error('BAO_ROLE_ID and BAO_SECRET_ID must be set.'); } - console.log(`🔐 OpenBaoProvider: Using roleId=${roleId} and secretId=${secretId}`); - console.log(`${baoUrl}/v1/auth/approle/login`) + this.logger.log(`Authenticating with AppRole at ${baoUrl}/v1/auth/approle/login`); // --- Authentication --- const authResponse = await fetch(`${baoUrl}/v1/auth/approle/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, // eslint-disable-next-line camelcase body: JSON.stringify({ role_id: roleId, secret_id: secretId }) - }) - console.log(`🔐 OpenBaoProvider: Authentication response status: ${authResponse}`); + }); if (!authResponse.ok) { throw new Error(`Authentication failed: Status ${authResponse.status}`); } - + const authData = await authResponse.json(); - console.log("authresponse", authData); const baoToken = authData.auth?.client_token; if (!baoToken) { throw new Error('Failed to retrieve client token from OpenBao.'); } - console.log("request url",`${baoUrl}/v1/${secretPath}`) + this.logger.log(`Fetching secrets from ${baoUrl}/v1/${secretPath}`); // --- Fetch Secrets --- const response = await fetch(`${baoUrl}/v1/${secretPath}`, { method: 'GET', @@ -46,15 +45,13 @@ export class OpenBaoProvider implements SecretProvider { 'Content-Type': 'application/json' } }); - console.log(`🔐 OpenBaoProvider: Fetch secrets response status: ${response}`); if (!response.ok) { - console.log("inside not ok") throw new Error(`Fetch failed: Status ${response.status}`); } const result = await response.json(); const secrets = result.data?.data; - console.log('🔐 Successfully fetched secrets from OpenBao', result); + this.logger.log('Successfully fetched secrets from OpenBao'); if (!secrets || 'object' !== typeof secrets || Array.isArray(secrets)) { throw new Error('Unexpected secrets payload structure.'); } From 2b9b87cb00559d5cbfcb7a4aeb0d845d5d53c43c Mon Sep 17 00:00:00 2001 From: sujitaw Date: Tue, 30 Jun 2026 11:50:49 +0530 Subject: [PATCH 14/57] fix: sonar cloud issues Signed-off-by: sujitaw --- .../src/providers/base-s3-storage.provider.ts | 31 +++-- .../src/providers/rustfs-storage.provider.ts | 31 ++--- .../src/providers/s3-storage.provider.ts | 108 +----------------- 3 files changed, 36 insertions(+), 134 deletions(-) diff --git a/libs/storage/src/providers/base-s3-storage.provider.ts b/libs/storage/src/providers/base-s3-storage.provider.ts index 4403130d3..25c274b7d 100644 --- a/libs/storage/src/providers/base-s3-storage.provider.ts +++ b/libs/storage/src/providers/base-s3-storage.provider.ts @@ -7,19 +7,9 @@ import { S3 } from 'aws-sdk'; import { promisify } from 'node:util'; export abstract class BaseS3StorageService implements IStorageService { - protected s3: S3; - protected s4: S3; - protected s3StoreObject: S3; - - constructor( - s3Config: Record, - s4Config: Record, - storeObjectConfig: Record - ) { - this.s3 = new S3(s3Config); - this.s4 = new S3(s4Config); - this.s3StoreObject = new S3(storeObjectConfig); - } + protected abstract getS3Client(): Promise; + protected abstract getPublicS3Client(): Promise; + protected abstract getStoreObjectS3Client(): Promise; abstract getPublicUrl(bucketName: string, fileKey: string): string; @@ -31,8 +21,9 @@ export abstract class BaseS3StorageService implements IStorageService { encoding: string, pathAWS: string = '' ): Promise { + const s4 = await this.getPublicS3Client(); const timestamp = Date.now(); - const putObjectAsync = promisify(this.s4.putObject).bind(this.s4); + const putObjectAsync = promisify(s4.putObject).bind(s4); const fileKey = `${pathAWS}/${encodeURIComponent(filename)}-${timestamp}.${ext}`; try { await putObjectAsync({ @@ -49,6 +40,7 @@ export abstract class BaseS3StorageService implements IStorageService { } async uploadCsvFile(key: string, body: unknown): Promise { + const s3 = await this.getS3Client(); let data: string; if ('string' === typeof body) { data = body; @@ -64,37 +56,40 @@ export abstract class BaseS3StorageService implements IStorageService { Body: data }; try { - await this.s3.upload(params).promise(); + await s3.upload(params).promise(); } catch (error) { throw new RpcException(error.response ? error.response : error); } } async getFile(key: string): Promise { + const s3 = await this.getS3Client(); const params: AWS.S3.GetObjectRequest = { Bucket: process.env.FILE_SHARING_BUCKET, Key: key }; try { - return this.s3.getObject(params).promise(); + return s3.getObject(params).promise(); } catch (error) { throw new RpcException(error.response ? error.response : error); } } async deleteFile(key: string): Promise { + const s3 = await this.getS3Client(); const params: AWS.S3.DeleteObjectRequest = { Bucket: process.env.FILE_SHARING_BUCKET, Key: key }; try { - await this.s3.deleteObject(params).promise(); + await s3.deleteObject(params).promise(); } catch (error) { throw new RpcException(error.response ? error.response : error); } } async storeObject(persistent: boolean, key: string, body: unknown): Promise { + const s3StoreObject = await this.getStoreObjectS3Client(); const objKey: string = persistent.valueOf() ? `persist/${key}` : `default/${key}`; const buf = Buffer.from(JSON.stringify(body)); const params: AWS.S3.PutObjectRequest = { @@ -106,7 +101,7 @@ export abstract class BaseS3StorageService implements IStorageService { }; try { - return await this.s3StoreObject.upload(params).promise(); + return await s3StoreObject.upload(params).promise(); } catch (error) { throw new RpcException(error.response ? error.response : error); } diff --git a/libs/storage/src/providers/rustfs-storage.provider.ts b/libs/storage/src/providers/rustfs-storage.provider.ts index c90287b95..9dee2d144 100644 --- a/libs/storage/src/providers/rustfs-storage.provider.ts +++ b/libs/storage/src/providers/rustfs-storage.provider.ts @@ -1,27 +1,30 @@ import { BaseS3StorageService } from './base-s3-storage.provider'; import { Injectable } from '@nestjs/common'; +import { S3 } from 'aws-sdk'; @Injectable() export class RustFsStorageService extends BaseS3StorageService { - constructor() { - const endpoint = process.env.RUSTFS_ENDPOINT || 'http://localhost:9000'; + private readonly endpoint = process.env.RUSTFS_ENDPOINT || 'http://localhost:9000'; + private readonly baseConfig = { + accessKeyId: process.env.RUSTFS_ACCESS_KEY_ID, + secretAccessKey: process.env.RUSTFS_SECRET_ACCESS_KEY, + endpoint: this.endpoint, + s3ForcePathStyle: true + }; - const baseConfig = { - accessKeyId: process.env.RUSTFS_ACCESS_KEY_ID, - secretAccessKey: process.env.RUSTFS_SECRET_ACCESS_KEY, - endpoint, - s3ForcePathStyle: true - }; + protected async getS3Client(): Promise { + return new S3({ ...this.baseConfig, region: process.env.AWS_REGION }); + } - const s3Config = { ...baseConfig, region: process.env.AWS_REGION }; - const s4Config = { ...baseConfig, region: process.env.AWS_PUBLIC_REGION }; - const storeObjectConfig = { ...baseConfig, region: process.env.AWS_S3_STOREOBJECT_REGION }; + protected async getPublicS3Client(): Promise { + return new S3({ ...this.baseConfig, region: process.env.AWS_PUBLIC_REGION }); + } - super(s3Config, s4Config, storeObjectConfig); + protected async getStoreObjectS3Client(): Promise { + return new S3({ ...this.baseConfig, region: process.env.AWS_S3_STOREOBJECT_REGION }); } getPublicUrl(bucketName: string, fileKey: string): string { - const endpoint = process.env.RUSTFS_ENDPOINT || 'http://localhost:9000'; - return `${endpoint}/${bucketName}/${fileKey}`; + return `${this.endpoint}/${bucketName}/${fileKey}`; } } diff --git a/libs/storage/src/providers/s3-storage.provider.ts b/libs/storage/src/providers/s3-storage.provider.ts index d9a75f6ff..be05b4ba3 100644 --- a/libs/storage/src/providers/s3-storage.provider.ts +++ b/libs/storage/src/providers/s3-storage.provider.ts @@ -1,15 +1,13 @@ -import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; -import { Buffer } from 'node:buffer'; import { CommonConstants } from 'libs/common/src/common.constant'; -import { RpcException } from '@nestjs/microservices'; import { S3 } from 'aws-sdk'; import { fetchOpenBaoSecrets } from 'libs/common/src/utils/openbao.util'; -import { promisify } from 'node:util'; +import { BaseS3StorageService } from './base-s3-storage.provider'; @Injectable() -export class S3StorageService { - private async getS3Client(): Promise { +export class S3StorageService extends BaseS3StorageService { + protected async getS3Client(): Promise { const secrets = await fetchOpenBaoSecrets(CommonConstants.CREDEBL_AWS_KEY_PATH); return new S3({ accessKeyId: secrets.AWS_ACCESS_KEY, @@ -18,7 +16,7 @@ export class S3StorageService { }); } - private async getPublicS3Client(): Promise { + protected async getPublicS3Client(): Promise { const secrets = await fetchOpenBaoSecrets(CommonConstants.CREDEBL_AWS_KEY_PATH); return new S3({ accessKeyId: secrets.AWS_PUBLIC_ACCESS_KEY, @@ -27,7 +25,7 @@ export class S3StorageService { }); } - private async getStoreObjectS3Client(): Promise { + protected async getStoreObjectS3Client(): Promise { const secrets = await fetchOpenBaoSecrets(CommonConstants.CREDEBL_AWS_KEY_PATH); return new S3({ accessKeyId: secrets.AWS_S3_STOREOBJECT_ACCESS_KEY, @@ -39,98 +37,4 @@ export class S3StorageService { getPublicUrl(bucketName: string, fileKey: string): string { return `https://${bucketName}.s3.${process.env.AWS_PUBLIC_REGION}.amazonaws.com/${fileKey}`; } - - async uploadFileToS3Bucket( - fileBuffer: Buffer, - ext: string, - filename: string, - bucketName: string, - encoding: string, - pathAWS: string = '' - ): Promise { - const s4 = await this.getPublicS3Client(); - const timestamp = Date.now(); - const putObjectAsync = promisify(s4.putObject).bind(s4); - const fileKey = `${pathAWS}/${encodeURIComponent(filename)}-${timestamp}.${ext}`; - try { - await putObjectAsync({ - Bucket: `${bucketName}`, - Key: `${pathAWS}/${encodeURIComponent(filename)}-${timestamp}.${ext}`, - Body: fileBuffer, - ContentEncoding: encoding, - ContentType: `image/png` - }); - return this.getPublicUrl(bucketName, fileKey); - } catch (error) { - throw new HttpException(error, HttpStatus.SERVICE_UNAVAILABLE); - } - } - - async uploadCsvFile(key: string, body: unknown): Promise { - const s3 = await this.getS3Client(); - let data: string; - if ('string' === typeof body) { - data = body; - } else if (Buffer.isBuffer(body)) { - data = (body as Buffer).toString('utf-8'); - } else { - data = JSON.stringify(body); - } - - const params: AWS.S3.PutObjectRequest = { - Bucket: process.env.FILE_SHARING_BUCKET, - Key: key, - Body: data - }; - try { - await s3.upload(params).promise(); - } catch (error) { - throw new RpcException(error.response ? error.response : error); - } - } - - async getFile(key: string): Promise { - const s3 = await this.getS3Client(); - const params: AWS.S3.GetObjectRequest = { - Bucket: process.env.FILE_SHARING_BUCKET, - Key: key - }; - try { - return s3.getObject(params).promise(); - } catch (error) { - throw new RpcException(error.response ? error.response : error); - } - } - - async deleteFile(key: string): Promise { - const s3 = await this.getS3Client(); - const params: AWS.S3.DeleteObjectRequest = { - Bucket: process.env.FILE_SHARING_BUCKET, - Key: key - }; - try { - await s3.deleteObject(params).promise(); - } catch (error) { - throw new RpcException(error.response ? error.response : error); - } - } - - async storeObject(persistent: boolean, key: string, body: unknown): Promise { - const s3StoreObject = await this.getStoreObjectS3Client(); - const objKey: string = persistent.valueOf() ? `persist/${key}` : `default/${key}`; - const buf = Buffer.from(JSON.stringify(body)); - const params: AWS.S3.PutObjectRequest = { - Bucket: process.env.STOREOBJECT_BUCKET, - Body: buf, - Key: objKey, - ContentEncoding: 'base64', - ContentType: 'application/json' - }; - - try { - return await s3StoreObject.upload(params).promise(); - } catch (error) { - throw new RpcException(error.response ? error.response : error); - } - } } From 1f9c116c53b7a42829af02a8102d98e4aa1f7b6b Mon Sep 17 00:00:00 2001 From: sujitaw Date: Tue, 30 Jun 2026 14:56:12 +0530 Subject: [PATCH 15/57] fix:code rabbit comments for openbao Signed-off-by: sujitaw --- libs/common/src/common.constant.ts | 4 +- libs/common/src/resend-helper-file.ts | 20 ++++-- .../src/secret-storage/openbao.provider.ts | 66 ++++++++++++++----- .../src/providers/base-s3-storage.provider.ts | 24 ++++++- .../src/providers/s3-storage.provider.ts | 11 ++-- 5 files changed, 95 insertions(+), 30 deletions(-) diff --git a/libs/common/src/common.constant.ts b/libs/common/src/common.constant.ts index 83314982f..db710b93d 100644 --- a/libs/common/src/common.constant.ts +++ b/libs/common/src/common.constant.ts @@ -455,7 +455,9 @@ export enum CommonConstants { // Storage Types STORAGE_TYPE_AWS = 'aws', STORAGE_TYPE_LOCAL = 'local', - STORAGE_TYPE_RUSTFS = 'rustfs' + STORAGE_TYPE_RUSTFS = 'rustfs', + + OPENBAO_REQUEST_TIMEOUT = 10000 } export const MICRO_SERVICE_NAME = Symbol('MICRO_SERVICE_NAME'); export const ATTRIBUTE_NAME_REGEX = /\['(.*?)'\]/; diff --git a/libs/common/src/resend-helper-file.ts b/libs/common/src/resend-helper-file.ts index a19d5fd4f..2c207c342 100644 --- a/libs/common/src/resend-helper-file.ts +++ b/libs/common/src/resend-helper-file.ts @@ -4,16 +4,22 @@ import { Logger } from '@nestjs/common'; import { Resend } from 'resend'; import { fetchOpenBaoSecrets } from './utils/openbao.util'; +let resendClientPromise: Promise | undefined; + async function getResendClient(): Promise { - const secretPath = CommonConstants.CREDEBL_RESEND_API_KEY_PATH; - const secrets = await fetchOpenBaoSecrets(secretPath); - const apiKey = secrets.RESEND_API_KEY; + resendClientPromise ??= (async (): Promise => { + const secretPath = CommonConstants.CREDEBL_RESEND_API_KEY_PATH; + const secrets = await fetchOpenBaoSecrets(secretPath); + const apiKey = secrets.RESEND_API_KEY; - if (!apiKey) { - throw new Error('Missing RESEND_API_KEY in environment variables.'); - } + if (!apiKey) { + throw new Error('Missing RESEND_API_KEY in secret payload.'); + } + + return new Resend(apiKey); + })(); - return new Resend(apiKey); + return resendClientPromise; } export const sendWithResend = async (emailDto: EmailDto): Promise => { diff --git a/libs/config/src/secret-storage/openbao.provider.ts b/libs/config/src/secret-storage/openbao.provider.ts index 4aad27300..f9b9a0070 100644 --- a/libs/config/src/secret-storage/openbao.provider.ts +++ b/libs/config/src/secret-storage/openbao.provider.ts @@ -1,6 +1,18 @@ +import { CommonConstants } from '@credebl/common/common.constant'; import { Logger } from '@nestjs/common'; import { SecretProvider } from './secret-provider.interface'; +async function fetchWithTimeout(url: string, options: RequestInit, timeoutMs: number): Promise { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), timeoutMs); + try { + const response = await fetch(url, { ...options, signal: controller.signal }); + return response; + } finally { + clearTimeout(timeoutId); + } +} + export class OpenBaoProvider implements SecretProvider { readonly name = 'OpenBao'; private readonly logger = new Logger(OpenBaoProvider.name); @@ -14,18 +26,30 @@ export class OpenBaoProvider implements SecretProvider { const secretPath = customPath || process.env.BAO_SECRET_PATH; const roleId = process.env.BAO_ROLE_ID; const secretId = process.env.BAO_SECRET_ID; - this.logger.log(`Fetching secrets from ${baoUrl}/v1/${secretPath}`); - if (!roleId || !secretId) { - throw new Error('BAO_ROLE_ID and BAO_SECRET_ID must be set.'); + if (!baoUrl || !secretPath || !roleId || !secretId) { + throw new Error('BAO_URL, BAO_SECRET_PATH, BAO_ROLE_ID, and BAO_SECRET_ID must be set.'); } + this.logger.log(`Fetching secrets from ${baoUrl}/v1/${secretPath}`); this.logger.log(`Authenticating with AppRole at ${baoUrl}/v1/auth/approle/login`); // --- Authentication --- - const authResponse = await fetch(`${baoUrl}/v1/auth/approle/login`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - // eslint-disable-next-line camelcase - body: JSON.stringify({ role_id: roleId, secret_id: secretId }) - }); + let authResponse: Response; + try { + authResponse = await fetchWithTimeout( + `${baoUrl}/v1/auth/approle/login`, + { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + // eslint-disable-next-line camelcase + body: JSON.stringify({ role_id: roleId, secret_id: secretId }) + }, + CommonConstants.OPENBAO_REQUEST_TIMEOUT + ); + } catch (error) { + if (error instanceof DOMException && 'AbortError' === error.name) { + throw new Error('OpenBao authentication request timed out.'); + } + throw error; + } if (!authResponse.ok) { throw new Error(`Authentication failed: Status ${authResponse.status}`); } @@ -38,13 +62,25 @@ export class OpenBaoProvider implements SecretProvider { } this.logger.log(`Fetching secrets from ${baoUrl}/v1/${secretPath}`); // --- Fetch Secrets --- - const response = await fetch(`${baoUrl}/v1/${secretPath}`, { - method: 'GET', - headers: { - 'X-Vault-Token': baoToken, - 'Content-Type': 'application/json' + let response: Response; + try { + response = await fetchWithTimeout( + `${baoUrl}/v1/${secretPath}`, + { + method: 'GET', + headers: { + 'X-Vault-Token': baoToken, + 'Content-Type': 'application/json' + } + }, + CommonConstants.OPENBAO_REQUEST_TIMEOUT + ); + } catch (error) { + if (error instanceof DOMException && 'AbortError' === error.name) { + throw new Error('OpenBao secrets fetch request timed out.'); } - }); + throw error; + } if (!response.ok) { throw new Error(`Fetch failed: Status ${response.status}`); } diff --git a/libs/storage/src/providers/base-s3-storage.provider.ts b/libs/storage/src/providers/base-s3-storage.provider.ts index 25c274b7d..fbb55b9b3 100644 --- a/libs/storage/src/providers/base-s3-storage.provider.ts +++ b/libs/storage/src/providers/base-s3-storage.provider.ts @@ -6,6 +6,28 @@ import { RpcException } from '@nestjs/microservices'; import { S3 } from 'aws-sdk'; import { promisify } from 'node:util'; +function extToMimeType(ext: string): string { + const mime: Record = { + png: 'image/png', + jpg: 'image/jpeg', + jpeg: 'image/jpeg', + gif: 'image/gif', + webp: 'image/webp', + svg: 'image/svg+xml', + bmp: 'image/bmp', + pdf: 'application/pdf', + csv: 'text/csv', + json: 'application/json', + txt: 'text/plain', + html: 'text/html', + xml: 'application/xml', + zip: 'application/zip', + doc: 'application/msword', + docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' + }; + return mime[ext.toLowerCase()] ?? 'application/octet-stream'; +} + export abstract class BaseS3StorageService implements IStorageService { protected abstract getS3Client(): Promise; protected abstract getPublicS3Client(): Promise; @@ -31,7 +53,7 @@ export abstract class BaseS3StorageService implements IStorageService { Key: `${pathAWS}/${encodeURIComponent(filename)}-${timestamp}.${ext}`, Body: fileBuffer, ContentEncoding: encoding, - ContentType: `image/png` + ContentType: extToMimeType(ext) }); return this.getPublicUrl(bucketName, fileKey); } catch (error) { diff --git a/libs/storage/src/providers/s3-storage.provider.ts b/libs/storage/src/providers/s3-storage.provider.ts index be05b4ba3..255e49fbf 100644 --- a/libs/storage/src/providers/s3-storage.provider.ts +++ b/libs/storage/src/providers/s3-storage.provider.ts @@ -1,9 +1,8 @@ -import { Injectable } from '@nestjs/common'; - +import { BaseS3StorageService } from './base-s3-storage.provider'; import { CommonConstants } from 'libs/common/src/common.constant'; +import { Injectable } from '@nestjs/common'; import { S3 } from 'aws-sdk'; import { fetchOpenBaoSecrets } from 'libs/common/src/utils/openbao.util'; -import { BaseS3StorageService } from './base-s3-storage.provider'; @Injectable() export class S3StorageService extends BaseS3StorageService { @@ -12,7 +11,7 @@ export class S3StorageService extends BaseS3StorageService { return new S3({ accessKeyId: secrets.AWS_ACCESS_KEY, secretAccessKey: secrets.AWS_SECRET_KEY, - region: secrets.AWS_REGION + region: process.env.AWS_REGION }); } @@ -21,7 +20,7 @@ export class S3StorageService extends BaseS3StorageService { return new S3({ accessKeyId: secrets.AWS_PUBLIC_ACCESS_KEY, secretAccessKey: secrets.AWS_PUBLIC_SECRET_KEY, - region: secrets.AWS_PUBLIC_REGION + region: process.env.AWS_PUBLIC_REGION }); } @@ -30,7 +29,7 @@ export class S3StorageService extends BaseS3StorageService { return new S3({ accessKeyId: secrets.AWS_S3_STOREOBJECT_ACCESS_KEY, secretAccessKey: secrets.AWS_S3_STOREOBJECT_SECRET_KEY, - region: secrets.AWS_S3_STOREOBJECT_REGION + region: process.env.AWS_S3_STOREOBJECT_REGION }); } From c90b5edd9a434b4db72d5d19fd4e82f54d4c761a Mon Sep 17 00:00:00 2001 From: sujitaw Date: Tue, 30 Jun 2026 15:19:11 +0530 Subject: [PATCH 16/57] fix: remove commented code Signed-off-by: sujitaw --- libs/config/src/secret-storage/secrets-loader.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/libs/config/src/secret-storage/secrets-loader.ts b/libs/config/src/secret-storage/secrets-loader.ts index 251a37f4d..951330d80 100644 --- a/libs/config/src/secret-storage/secrets-loader.ts +++ b/libs/config/src/secret-storage/secrets-loader.ts @@ -1,5 +1,3 @@ -// import { AwsSecretsProvider } from './providers/aws-secrets.provider'; -// libs/config/src/secrets-loader.ts import { Logger } from '@nestjs/common'; import { OpenBaoProvider } from './openbao.provider'; import { SecretProvider } from './secret-provider.interface'; @@ -57,7 +55,6 @@ export async function loadConfigSecrets(): Promise { logger.log(`Successfully injected secrets from ${provider.name} into process.env`); } catch (error) { - // Safely handles instances where error might not be a standard Error object const errorMessage = error instanceof Error ? error.message : String(error); logger.error(`Critical Lifecycle Boot Failure (${provider.name}): ${errorMessage}`); throw new Error(`Failed to load secrets from ${provider.name}`); From 8c1dfb73f859d4b98be537cd8a3fbd4899179ced Mon Sep 17 00:00:00 2001 From: sujitaw Date: Tue, 30 Jun 2026 15:39:04 +0530 Subject: [PATCH 17/57] feat: util files for open bao Signed-off-by: sujitaw --- libs/common/src/utils/openbao.util.ts | 19 +++++++++++++ libs/common/src/utils/ttl-cache.util.ts | 38 +++++++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 libs/common/src/utils/openbao.util.ts create mode 100644 libs/common/src/utils/ttl-cache.util.ts diff --git a/libs/common/src/utils/openbao.util.ts b/libs/common/src/utils/openbao.util.ts new file mode 100644 index 000000000..6f333f15b --- /dev/null +++ b/libs/common/src/utils/openbao.util.ts @@ -0,0 +1,19 @@ +import { OpenBaoProvider } from 'libs/config/src/secret-storage/openbao.provider'; +import { TtlCache } from './ttl-cache.util'; + +const CACHE_TTL_MS = 10 * 60 * 1000; + +const secretCaches = new Map>>(); + +export async function fetchOpenBaoSecrets(secretPath: string): Promise> { + let cache = secretCaches.get(secretPath); + if (!cache) { + cache = new TtlCache>(CACHE_TTL_MS); + secretCaches.set(secretPath, cache); + } + + return cache.get(async () => { + const openBaoProvider = new OpenBaoProvider(); + return openBaoProvider.loadSecrets(secretPath); + }); +} diff --git a/libs/common/src/utils/ttl-cache.util.ts b/libs/common/src/utils/ttl-cache.util.ts new file mode 100644 index 000000000..5574ea441 --- /dev/null +++ b/libs/common/src/utils/ttl-cache.util.ts @@ -0,0 +1,38 @@ +export class TtlCache { + private value: T | null = null; + private timer: ReturnType | null = null; + private readonly ttlMs: number; + + constructor(ttlMs: number) { + this.ttlMs = ttlMs; + } + + async get(fetchFn: () => Promise): Promise { + if (null !== this.value) { + this.refreshTimer(); + return this.value; + } + + this.value = await fetchFn(); + this.refreshTimer(); + return this.value; + } + + clear(): void { + this.value = null; + if (this.timer) { + clearTimeout(this.timer); + this.timer = null; + } + } + + private refreshTimer(): void { + if (this.timer) { + clearTimeout(this.timer); + } + this.timer = setTimeout(() => { + this.clear(); + }, this.ttlMs); + this.timer.unref(); + } +} From 6b61197f2e8bc4d2408d953a4ec365d1bcdac5fe Mon Sep 17 00:00:00 2001 From: sujitaw Date: Wed, 1 Jul 2026 10:22:25 +0530 Subject: [PATCH 18/57] feat: add openbao implementation for sendgrid and smtp Signed-off-by: sujitaw --- libs/common/src/common.constant.ts | 2 + libs/common/src/resend-helper-file.ts | 6 +- libs/common/src/send-grid-helper-file.ts | 24 ++++++- libs/common/src/smtp-helper-file.ts | 72 ++++++++++--------- libs/common/src/utils/openbao.util.ts | 19 ----- libs/common/src/utils/secretLoader.util.ts | 28 ++++++++ .../src/secret-storage/openbao.provider.ts | 6 +- .../secret-provider.interface.ts | 2 +- .../src/secret-storage/secrets-loader.ts | 6 +- .../src/providers/s3-storage.provider.ts | 20 +++--- 10 files changed, 110 insertions(+), 75 deletions(-) delete mode 100644 libs/common/src/utils/openbao.util.ts create mode 100644 libs/common/src/utils/secretLoader.util.ts diff --git a/libs/common/src/common.constant.ts b/libs/common/src/common.constant.ts index db710b93d..9613ed454 100644 --- a/libs/common/src/common.constant.ts +++ b/libs/common/src/common.constant.ts @@ -445,6 +445,8 @@ export enum CommonConstants { // BAO_STORAGE_PATH CREDEBL_RESEND_API_KEY_PATH = 'secret/data/credebl_resend_api_key', + CREDEBL_SMTP_CONFIG_PATH = 'secret/data/credebl_smtp_config', + CREDEBL_SENDGRID_API_KEY_PATH = 'secret/data/credebl_sendgrid_api_key', CREDEBL_AWS_KEY_PATH = 'secret/data/credebl_aws_keys', //X509 diff --git a/libs/common/src/resend-helper-file.ts b/libs/common/src/resend-helper-file.ts index 2c207c342..5c675a380 100644 --- a/libs/common/src/resend-helper-file.ts +++ b/libs/common/src/resend-helper-file.ts @@ -2,15 +2,15 @@ import { CommonConstants } from './common.constant'; import { EmailDto } from './dtos/email.dto'; import { Logger } from '@nestjs/common'; import { Resend } from 'resend'; -import { fetchOpenBaoSecrets } from './utils/openbao.util'; +import { fetchSecrets } from './utils/secretLoader.util'; let resendClientPromise: Promise | undefined; async function getResendClient(): Promise { resendClientPromise ??= (async (): Promise => { const secretPath = CommonConstants.CREDEBL_RESEND_API_KEY_PATH; - const secrets = await fetchOpenBaoSecrets(secretPath); - const apiKey = secrets.RESEND_API_KEY; + const secrets = await fetchSecrets(secretPath); + const apiKey = secrets.RESEND_API_KEY ?? process.env.RESEND_API_KEY; if (!apiKey) { throw new Error('Missing RESEND_API_KEY in secret payload.'); diff --git a/libs/common/src/send-grid-helper-file.ts b/libs/common/src/send-grid-helper-file.ts index 3ae07faf1..110a71598 100644 --- a/libs/common/src/send-grid-helper-file.ts +++ b/libs/common/src/send-grid-helper-file.ts @@ -1,14 +1,31 @@ -import * as dotenv from 'dotenv'; import * as sendgrid from '@sendgrid/mail'; +import { CommonConstants } from './common.constant'; import { EmailDto } from './dtos/email.dto'; +import { Logger } from '@nestjs/common'; +import { fetchSecrets } from './utils/secretLoader.util'; -dotenv.config(); +let sendgridInitPromise: Promise | undefined; -sendgrid.setApiKey(process.env.SENDGRID_API_KEY); +async function initSendgrid(): Promise { + sendgridInitPromise ??= (async (): Promise => { + const secretPath = CommonConstants.CREDEBL_SENDGRID_API_KEY_PATH; + const secrets = await fetchSecrets(secretPath); + const apiKey = secrets.SENDGRID_API_KEY ?? process.env.SENDGRID_API_KEY; + + if (!apiKey) { + throw new Error('Missing SENDGRID_API_KEY in secret payload.'); + } + + sendgrid.setApiKey(apiKey); + })(); + + return sendgridInitPromise; +} export const sendWithSendGrid = async (EmailDto: EmailDto): Promise => { try { + await initSendgrid(); const msg = { to: EmailDto.emailTo, from: EmailDto.emailFrom, @@ -22,6 +39,7 @@ export const sendWithSendGrid = async (EmailDto: EmailDto): Promise => .then(() => true) .catch(() => false); } catch (error) { + Logger.error('Error while sending email with SendGrid', error); return false; } }; diff --git a/libs/common/src/smtp-helper-file.ts b/libs/common/src/smtp-helper-file.ts index be0559e0a..8222f617f 100644 --- a/libs/common/src/smtp-helper-file.ts +++ b/libs/common/src/smtp-helper-file.ts @@ -1,47 +1,49 @@ -import * as dotenv from 'dotenv'; import * as nodemailer from 'nodemailer'; +import { CommonConstants } from './common.constant'; import { EmailDto } from './dtos/email.dto'; import { Logger } from '@nestjs/common'; +import { fetchSecrets } from './utils/secretLoader.util'; + +let transporterPromise: Promise | undefined; + +async function getSmtpTransporter(): Promise { + transporterPromise ??= (async (): Promise => { + const secretPath = CommonConstants.CREDEBL_SMTP_CONFIG_PATH; + const secrets = await fetchSecrets(secretPath); + const smtpHost = secrets.SMTP_HOST ?? process.env.SMTP_HOST; + const smtpPort = secrets.SMTP_PORT ?? process.env.SMTP_PORT; + const smtpUser = secrets.SMTP_USER ?? process.env.SMTP_USER; + const smtpPass = secrets.SMTP_PASS ?? process.env.SMTP_PASS; + + if (!smtpHost || !smtpPort || !smtpUser || !smtpPass) { + throw new Error('Missing SMTP configuration. Required: SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS'); + } + + const port = Number(smtpPort); + + if (!Number.isInteger(port) || 0 >= port) { + throw new Error(`Invalid SMTP_PORT value: "${smtpPort}". Must be a valid number.`); + } + + return nodemailer.createTransport({ + host: smtpHost, + port, + secure: 465 === port, + auth: { + user: smtpUser, + pass: smtpPass + }, + requireTLS: 587 === port + }); + })(); -dotenv.config(); - -const emailProvider = process.env.EMAIL_PROVIDER?.toLowerCase(); - -let transporter: nodemailer.Transporter | null = null; - -if ('smtp' === emailProvider) { - const { SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS } = process.env; - - if (!SMTP_HOST || !SMTP_PORT || !SMTP_USER || !SMTP_PASS) { - throw new Error('Missing SMTP configuration. Required: SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS'); - } - - const port = Number(SMTP_PORT); - - if (!Number.isInteger(port) || 0 >= port) { - throw new Error(`Invalid SMTP_PORT value: "${SMTP_PORT}". Must be a valid number.`); - } - - transporter = nodemailer.createTransport({ - host: SMTP_HOST, - port: Number(SMTP_PORT), - secure: 465 === Number(SMTP_PORT), - auth: { - user: SMTP_USER, - pass: SMTP_PASS - }, - requireTLS: 587 === Number(SMTP_PORT) - }); + return transporterPromise; } export const sendWithSMTP = async (emailDto: EmailDto): Promise => { - if (!transporter) { - Logger.error('SMTP email provider is not initialized'); - return false; - } - try { + const transporter = await getSmtpTransporter(); await transporter.sendMail({ from: emailDto.emailFrom, to: emailDto.emailTo, diff --git a/libs/common/src/utils/openbao.util.ts b/libs/common/src/utils/openbao.util.ts deleted file mode 100644 index 6f333f15b..000000000 --- a/libs/common/src/utils/openbao.util.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { OpenBaoProvider } from 'libs/config/src/secret-storage/openbao.provider'; -import { TtlCache } from './ttl-cache.util'; - -const CACHE_TTL_MS = 10 * 60 * 1000; - -const secretCaches = new Map>>(); - -export async function fetchOpenBaoSecrets(secretPath: string): Promise> { - let cache = secretCaches.get(secretPath); - if (!cache) { - cache = new TtlCache>(CACHE_TTL_MS); - secretCaches.set(secretPath, cache); - } - - return cache.get(async () => { - const openBaoProvider = new OpenBaoProvider(); - return openBaoProvider.loadSecrets(secretPath); - }); -} diff --git a/libs/common/src/utils/secretLoader.util.ts b/libs/common/src/utils/secretLoader.util.ts new file mode 100644 index 000000000..63cd53352 --- /dev/null +++ b/libs/common/src/utils/secretLoader.util.ts @@ -0,0 +1,28 @@ +import { TtlCache } from './ttl-cache.util'; +import { getSecretProvider } from 'libs/config/src/secret-storage/secrets-loader'; + +const CACHE_TTL_MS = 10 * 60 * 1000; + +const secretCaches = new Map>>(); + +export async function fetchSecrets(secretPath: string): Promise> { + if ('true' !== process.env.ENABLE_BAO?.trim()?.toLowerCase()) { + return {}; + } + let cache = secretCaches.get(secretPath); + if (!cache) { + cache = new TtlCache>(CACHE_TTL_MS); + secretCaches.set(secretPath, cache); + } + return cache.get(async () => { + const providerType = process.env.SECRETS_PROVIDER?.trim(); + if (!providerType) { + return {}; + } + const provider = getSecretProvider(providerType); + if (!provider) { + return {}; + } + return provider.loadSecrets({ customPath: secretPath }); + }); +} diff --git a/libs/config/src/secret-storage/openbao.provider.ts b/libs/config/src/secret-storage/openbao.provider.ts index f9b9a0070..a4e73e0ae 100644 --- a/libs/config/src/secret-storage/openbao.provider.ts +++ b/libs/config/src/secret-storage/openbao.provider.ts @@ -21,9 +21,9 @@ export class OpenBaoProvider implements SecretProvider { return 'true' === process.env.ENABLE_BAO?.trim()?.toLowerCase(); } - async loadSecrets(customPath: string = ''): Promise> { + async loadSecrets(options?: { customPath?: string }): Promise> { const baoUrl = process.env.BAO_URL; - const secretPath = customPath || process.env.BAO_SECRET_PATH; + const secretPath = options?.customPath || process.env.BAO_SECRET_PATH; const roleId = process.env.BAO_ROLE_ID; const secretId = process.env.BAO_SECRET_ID; if (!baoUrl || !secretPath || !roleId || !secretId) { @@ -87,7 +87,7 @@ export class OpenBaoProvider implements SecretProvider { const result = await response.json(); const secrets = result.data?.data; - this.logger.log('Successfully fetched secrets from OpenBao'); + this.logger.log('Successfully fetched secrets from OpenBao', secrets); if (!secrets || 'object' !== typeof secrets || Array.isArray(secrets)) { throw new Error('Unexpected secrets payload structure.'); } diff --git a/libs/config/src/secret-storage/secret-provider.interface.ts b/libs/config/src/secret-storage/secret-provider.interface.ts index 9d734319e..f982c868a 100644 --- a/libs/config/src/secret-storage/secret-provider.interface.ts +++ b/libs/config/src/secret-storage/secret-provider.interface.ts @@ -9,5 +9,5 @@ export interface SecretProvider { * as a flat key-value object of strings. * * @returns A promise resolving to Record */ - loadSecrets(): Promise>; + loadSecrets(options?: Record): Promise>; } diff --git a/libs/config/src/secret-storage/secrets-loader.ts b/libs/config/src/secret-storage/secrets-loader.ts index 951330d80..4458be121 100644 --- a/libs/config/src/secret-storage/secrets-loader.ts +++ b/libs/config/src/secret-storage/secrets-loader.ts @@ -8,7 +8,7 @@ const logger = new Logger('SecretsLoader'); /** * Factory function to get the requested secret provider */ -function getSecretProvider(providerType: string): SecretProvider | null { +export function getSecretProvider(providerType: string): SecretProvider | null { switch (providerType.toLowerCase()) { case 'openbao': return new OpenBaoProvider(); @@ -19,6 +19,10 @@ function getSecretProvider(providerType: string): SecretProvider | null { } export async function loadConfigSecrets(): Promise { + if ('true' !== process.env.ENABLE_BAO?.trim()?.toLowerCase()) { + logger.log('OpenBao secrets management is disabled. Skipping remote secrets fetching.'); + return; + } const providerType = process.env.SECRETS_PROVIDER?.trim(); if (!providerType) { diff --git a/libs/storage/src/providers/s3-storage.provider.ts b/libs/storage/src/providers/s3-storage.provider.ts index 255e49fbf..1fba7d5b0 100644 --- a/libs/storage/src/providers/s3-storage.provider.ts +++ b/libs/storage/src/providers/s3-storage.provider.ts @@ -2,33 +2,33 @@ import { BaseS3StorageService } from './base-s3-storage.provider'; import { CommonConstants } from 'libs/common/src/common.constant'; import { Injectable } from '@nestjs/common'; import { S3 } from 'aws-sdk'; -import { fetchOpenBaoSecrets } from 'libs/common/src/utils/openbao.util'; +import { fetchSecrets } from '@credebl/common/utils/secretLoader.util'; @Injectable() export class S3StorageService extends BaseS3StorageService { protected async getS3Client(): Promise { - const secrets = await fetchOpenBaoSecrets(CommonConstants.CREDEBL_AWS_KEY_PATH); + const secrets = await fetchSecrets(CommonConstants.CREDEBL_AWS_KEY_PATH); return new S3({ - accessKeyId: secrets.AWS_ACCESS_KEY, - secretAccessKey: secrets.AWS_SECRET_KEY, + accessKeyId: secrets.AWS_ACCESS_KEY ?? process.env.AWS_ACCESS_KEY, + secretAccessKey: secrets.AWS_SECRET_KEY ?? process.env.AWS_SECRET_KEY, region: process.env.AWS_REGION }); } protected async getPublicS3Client(): Promise { - const secrets = await fetchOpenBaoSecrets(CommonConstants.CREDEBL_AWS_KEY_PATH); + const secrets = await fetchSecrets(CommonConstants.CREDEBL_AWS_KEY_PATH); return new S3({ - accessKeyId: secrets.AWS_PUBLIC_ACCESS_KEY, - secretAccessKey: secrets.AWS_PUBLIC_SECRET_KEY, + accessKeyId: secrets.AWS_PUBLIC_ACCESS_KEY ?? process.env.AWS_PUBLIC_ACCESS_KEY, + secretAccessKey: secrets.AWS_PUBLIC_SECRET_KEY ?? process.env.AWS_PUBLIC_SECRET_KEY, region: process.env.AWS_PUBLIC_REGION }); } protected async getStoreObjectS3Client(): Promise { - const secrets = await fetchOpenBaoSecrets(CommonConstants.CREDEBL_AWS_KEY_PATH); + const secrets = await fetchSecrets(CommonConstants.CREDEBL_AWS_KEY_PATH); return new S3({ - accessKeyId: secrets.AWS_S3_STOREOBJECT_ACCESS_KEY, - secretAccessKey: secrets.AWS_S3_STOREOBJECT_SECRET_KEY, + accessKeyId: secrets.AWS_S3_STOREOBJECT_ACCESS_KEY ?? process.env.AWS_S3_STOREOBJECT_ACCESS_KEY, + secretAccessKey: secrets.AWS_S3_STOREOBJECT_SECRET_KEY ?? process.env.AWS_S3_STOREOBJECT_SECRET_KEY, region: process.env.AWS_S3_STOREOBJECT_REGION }); } From 8766ac6f003ec7fc781e11fbfeb907a2c42da3b6 Mon Sep 17 00:00:00 2001 From: sujitaw Date: Wed, 1 Jul 2026 12:09:28 +0530 Subject: [PATCH 19/57] fix: code rabbit comment on email data caching Signed-off-by: sujitaw --- libs/common/src/resend-helper-file.ts | 16 +++------------- libs/common/src/send-grid-helper-file.ts | 13 ++----------- libs/common/src/smtp-helper-file.ts | 15 +++------------ .../src/secret-storage/openbao.provider.ts | 2 +- libs/config/src/secret-storage/secrets-loader.ts | 2 +- 5 files changed, 10 insertions(+), 38 deletions(-) diff --git a/libs/common/src/resend-helper-file.ts b/libs/common/src/resend-helper-file.ts index 5c675a380..9950ee96a 100644 --- a/libs/common/src/resend-helper-file.ts +++ b/libs/common/src/resend-helper-file.ts @@ -4,10 +4,8 @@ import { Logger } from '@nestjs/common'; import { Resend } from 'resend'; import { fetchSecrets } from './utils/secretLoader.util'; -let resendClientPromise: Promise | undefined; - -async function getResendClient(): Promise { - resendClientPromise ??= (async (): Promise => { +export const sendWithResend = async (emailDto: EmailDto): Promise => { + try { const secretPath = CommonConstants.CREDEBL_RESEND_API_KEY_PATH; const secrets = await fetchSecrets(secretPath); const apiKey = secrets.RESEND_API_KEY ?? process.env.RESEND_API_KEY; @@ -16,15 +14,7 @@ async function getResendClient(): Promise { throw new Error('Missing RESEND_API_KEY in secret payload.'); } - return new Resend(apiKey); - })(); - - return resendClientPromise; -} - -export const sendWithResend = async (emailDto: EmailDto): Promise => { - try { - const client = await getResendClient(); + const client = new Resend(apiKey); const response = await client.emails.send({ from: emailDto.emailFrom, to: emailDto.emailTo, diff --git a/libs/common/src/send-grid-helper-file.ts b/libs/common/src/send-grid-helper-file.ts index 110a71598..01e3f666d 100644 --- a/libs/common/src/send-grid-helper-file.ts +++ b/libs/common/src/send-grid-helper-file.ts @@ -5,10 +5,8 @@ import { EmailDto } from './dtos/email.dto'; import { Logger } from '@nestjs/common'; import { fetchSecrets } from './utils/secretLoader.util'; -let sendgridInitPromise: Promise | undefined; - -async function initSendgrid(): Promise { - sendgridInitPromise ??= (async (): Promise => { +export const sendWithSendGrid = async (EmailDto: EmailDto): Promise => { + try { const secretPath = CommonConstants.CREDEBL_SENDGRID_API_KEY_PATH; const secrets = await fetchSecrets(secretPath); const apiKey = secrets.SENDGRID_API_KEY ?? process.env.SENDGRID_API_KEY; @@ -18,14 +16,7 @@ async function initSendgrid(): Promise { } sendgrid.setApiKey(apiKey); - })(); - return sendgridInitPromise; -} - -export const sendWithSendGrid = async (EmailDto: EmailDto): Promise => { - try { - await initSendgrid(); const msg = { to: EmailDto.emailTo, from: EmailDto.emailFrom, diff --git a/libs/common/src/smtp-helper-file.ts b/libs/common/src/smtp-helper-file.ts index 8222f617f..1f629ad67 100644 --- a/libs/common/src/smtp-helper-file.ts +++ b/libs/common/src/smtp-helper-file.ts @@ -5,10 +5,8 @@ import { EmailDto } from './dtos/email.dto'; import { Logger } from '@nestjs/common'; import { fetchSecrets } from './utils/secretLoader.util'; -let transporterPromise: Promise | undefined; - -async function getSmtpTransporter(): Promise { - transporterPromise ??= (async (): Promise => { +export const sendWithSMTP = async (emailDto: EmailDto): Promise => { + try { const secretPath = CommonConstants.CREDEBL_SMTP_CONFIG_PATH; const secrets = await fetchSecrets(secretPath); const smtpHost = secrets.SMTP_HOST ?? process.env.SMTP_HOST; @@ -26,7 +24,7 @@ async function getSmtpTransporter(): Promise { throw new Error(`Invalid SMTP_PORT value: "${smtpPort}". Must be a valid number.`); } - return nodemailer.createTransport({ + const transporter = nodemailer.createTransport({ host: smtpHost, port, secure: 465 === port, @@ -36,14 +34,7 @@ async function getSmtpTransporter(): Promise { }, requireTLS: 587 === port }); - })(); - return transporterPromise; -} - -export const sendWithSMTP = async (emailDto: EmailDto): Promise => { - try { - const transporter = await getSmtpTransporter(); await transporter.sendMail({ from: emailDto.emailFrom, to: emailDto.emailTo, diff --git a/libs/config/src/secret-storage/openbao.provider.ts b/libs/config/src/secret-storage/openbao.provider.ts index a4e73e0ae..2bffbe7fd 100644 --- a/libs/config/src/secret-storage/openbao.provider.ts +++ b/libs/config/src/secret-storage/openbao.provider.ts @@ -87,7 +87,7 @@ export class OpenBaoProvider implements SecretProvider { const result = await response.json(); const secrets = result.data?.data; - this.logger.log('Successfully fetched secrets from OpenBao', secrets); + this.logger.log('Successfully fetched secrets from OpenBao'); if (!secrets || 'object' !== typeof secrets || Array.isArray(secrets)) { throw new Error('Unexpected secrets payload structure.'); } diff --git a/libs/config/src/secret-storage/secrets-loader.ts b/libs/config/src/secret-storage/secrets-loader.ts index 4458be121..8c7d6c23b 100644 --- a/libs/config/src/secret-storage/secrets-loader.ts +++ b/libs/config/src/secret-storage/secrets-loader.ts @@ -61,6 +61,6 @@ export async function loadConfigSecrets(): Promise { } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); logger.error(`Critical Lifecycle Boot Failure (${provider.name}): ${errorMessage}`); - throw new Error(`Failed to load secrets from ${provider.name}`); + logger.error(`Failed to load secrets from ${provider.name}`); } } From 6d33404206ba576aa273c53368f1fe8326870ceb Mon Sep 17 00:00:00 2001 From: M Abdullah Date: Tue, 7 Jul 2026 16:10:16 +0500 Subject: [PATCH 20/57] empty attribute response Signed-off-by: M Abdullah --- .../utilities/dtos/shortening-url.dto.spec.ts | 31 +++++++++ .../src/utilities/dtos/shortening-url.dto.ts | 64 +++++++++---------- 2 files changed, 62 insertions(+), 33 deletions(-) create mode 100644 apps/api-gateway/src/utilities/dtos/shortening-url.dto.spec.ts diff --git a/apps/api-gateway/src/utilities/dtos/shortening-url.dto.spec.ts b/apps/api-gateway/src/utilities/dtos/shortening-url.dto.spec.ts new file mode 100644 index 000000000..829480aae --- /dev/null +++ b/apps/api-gateway/src/utilities/dtos/shortening-url.dto.spec.ts @@ -0,0 +1,31 @@ +import { plainToInstance } from 'class-transformer'; +import { validate } from 'class-validator'; +import { UtilitiesDto } from './shortening-url.dto'; + +describe('UtilitiesDto', () => { + const basePayload = { + credentialId: 'cred-id', + schemaId: 'schema-id', + credDefId: 'cred-def-id', + invitationUrl: 'https://example.com/invite' + }; + + it('should pass validation when attributes contains at least one item', async () => { + const dto = plainToInstance(UtilitiesDto, { + ...basePayload, + attributes: [{ name: 'name', value: 'value' }] + }); + const errors = await validate(dto); + expect(errors.length).toBe(0); + }); + + it('should fail validation when attributes is an empty array', async () => { + const dto = plainToInstance(UtilitiesDto, { + ...basePayload, + attributes: [] + }); + const errors = await validate(dto); + const attributesError = errors.find((error) => 'attributes' === error.property); + expect(attributesError?.constraints?.arrayNotEmpty).toBe('attributes should not be empty'); + }); +}); diff --git a/apps/api-gateway/src/utilities/dtos/shortening-url.dto.ts b/apps/api-gateway/src/utilities/dtos/shortening-url.dto.ts index c49e5a788..44bfc0da3 100644 --- a/apps/api-gateway/src/utilities/dtos/shortening-url.dto.ts +++ b/apps/api-gateway/src/utilities/dtos/shortening-url.dto.ts @@ -1,47 +1,45 @@ - import { ApiExtraModels, ApiProperty } from '@nestjs/swagger'; -import { IsArray, IsNotEmpty } from 'class-validator'; +import { ArrayNotEmpty, IsArray, IsNotEmpty } from 'class-validator'; @ApiExtraModels() - export class UtilitiesDto { + @ApiProperty() + @IsNotEmpty() + credentialId: string; - @ApiProperty() - @IsNotEmpty() - credentialId: string; - - @ApiProperty() - @IsNotEmpty() - schemaId: string; - - @ApiProperty() - @IsNotEmpty() - credDefId: string; + @ApiProperty() + @IsNotEmpty() + schemaId: string; - @ApiProperty() - @IsNotEmpty() - invitationUrl: string; + @ApiProperty() + @IsNotEmpty() + credDefId: string; - @ApiProperty({ - example: [ - { - name: 'name', - value: 'value' - } - ] - }) - @IsArray({ message: 'attributes must be a valid array' }) - @IsNotEmpty({ message: 'please provide valid attributes' }) - attributes: Attribute[]; + @ApiProperty() + @IsNotEmpty() + invitationUrl: string; + + @ApiProperty({ + example: [ + { + name: 'name', + value: 'value' + } + ] + }) + @IsArray({ message: 'attributes must be a valid array' }) + @ArrayNotEmpty({ message: 'attributes should not be empty' }) + @IsNotEmpty({ message: 'please provide valid attributes' }) + attributes: Attribute[]; } interface Attribute { - name: string; - value: string; - } + name: string; + value: string; +} - export class StoreObjectDto { +export class StoreObjectDto { @ApiProperty() @IsNotEmpty() data: string | object; -} \ No newline at end of file +} From 4f31b8dbbb2ad823b5f7f250b8962479d8304594 Mon Sep 17 00:00:00 2001 From: M Abdullah Date: Tue, 7 Jul 2026 17:16:02 +0500 Subject: [PATCH 21/57] match issuance pattern Signed-off-by: M Abdullah --- .../utilities/dtos/shortening-url.dto.spec.ts | 22 +++++++++++++++++++ .../src/utilities/dtos/shortening-url.dto.ts | 22 ++++++++++++++----- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/apps/api-gateway/src/utilities/dtos/shortening-url.dto.spec.ts b/apps/api-gateway/src/utilities/dtos/shortening-url.dto.spec.ts index 829480aae..49df14cae 100644 --- a/apps/api-gateway/src/utilities/dtos/shortening-url.dto.spec.ts +++ b/apps/api-gateway/src/utilities/dtos/shortening-url.dto.spec.ts @@ -28,4 +28,26 @@ describe('UtilitiesDto', () => { const attributesError = errors.find((error) => 'attributes' === error.property); expect(attributesError?.constraints?.arrayNotEmpty).toBe('attributes should not be empty'); }); + + it('should fail validation when an attribute item is missing required fields', async () => { + const dto = plainToInstance(UtilitiesDto, { + ...basePayload, + attributes: [{}] + }); + const errors = await validate(dto); + const attributesError = errors.find((error) => 'attributes' === error.property); + expect(attributesError?.children?.length).toBeGreaterThan(0); + expect(errors.length).toBeGreaterThan(0); + }); + + it('should fail validation when an attribute name is not a string', async () => { + const dto = plainToInstance(UtilitiesDto, { + ...basePayload, + attributes: [{ name: 123, value: 'value' }] + }); + const errors = await validate(dto); + const attributesError = errors.find((error) => 'attributes' === error.property); + const nameError = attributesError?.children?.[0]?.children?.find((error) => 'name' === error.property); + expect(nameError?.constraints?.isString).toBeDefined(); + }); }); diff --git a/apps/api-gateway/src/utilities/dtos/shortening-url.dto.ts b/apps/api-gateway/src/utilities/dtos/shortening-url.dto.ts index 44bfc0da3..29c3ab38f 100644 --- a/apps/api-gateway/src/utilities/dtos/shortening-url.dto.ts +++ b/apps/api-gateway/src/utilities/dtos/shortening-url.dto.ts @@ -1,5 +1,18 @@ import { ApiExtraModels, ApiProperty } from '@nestjs/swagger'; -import { ArrayNotEmpty, IsArray, IsNotEmpty } from 'class-validator'; +import { Type } from 'class-transformer'; +import { ArrayNotEmpty, IsArray, IsNotEmpty, IsString, ValidateNested } from 'class-validator'; + +export class Attribute { + @ApiProperty() + @IsString() + @IsNotEmpty() + name: string; + + @ApiProperty() + @IsString() + @IsNotEmpty() + value: string; +} @ApiExtraModels() export class UtilitiesDto { @@ -29,15 +42,12 @@ export class UtilitiesDto { }) @IsArray({ message: 'attributes must be a valid array' }) @ArrayNotEmpty({ message: 'attributes should not be empty' }) + @ValidateNested({ each: true }) + @Type(() => Attribute) @IsNotEmpty({ message: 'please provide valid attributes' }) attributes: Attribute[]; } -interface Attribute { - name: string; - value: string; -} - export class StoreObjectDto { @ApiProperty() @IsNotEmpty() From d6a48b5ae8465b15ff67b760268492a14dd54c22 Mon Sep 17 00:00:00 2001 From: Tipu_Singh Date: Wed, 15 Jul 2026 11:58:27 +0530 Subject: [PATCH 22/57] fix: dco issue (#1698) * empty attribute response Signed-off-by: M Abdullah Signed-off-by: Tipu_Singh * match issuance pattern Signed-off-by: M Abdullah Signed-off-by: Tipu_Singh --------- Signed-off-by: M Abdullah Signed-off-by: Tipu_Singh Co-authored-by: M Abdullah From 9c8c50c17cbf3ca2ebeb6fa163b961fc16f24334 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Jul 2026 23:19:29 +0000 Subject: [PATCH 23/57] chore(deps): bump body-parser from 1.20.5 to 1.20.6 Bumps [body-parser](https://github.com/expressjs/body-parser) from 1.20.5 to 1.20.6. - [Release notes](https://github.com/expressjs/body-parser/releases) - [Changelog](https://github.com/expressjs/body-parser/blob/master/HISTORY.md) - [Commits](https://github.com/expressjs/body-parser/compare/1.20.5...1.20.6) --- updated-dependencies: - dependency-name: body-parser dependency-version: 1.20.6 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- package.json | 2 +- pnpm-lock.yaml | 94 +++++++++++++++++++++++++++++++++++--------------- 2 files changed, 67 insertions(+), 29 deletions(-) diff --git a/package.json b/package.json index 79f2dbe33..ca57aebfc 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "axios": "^1.16.0", "bcrypt": "^6.0.0", "blob-stream": "^0.1.3", - "body-parser": "^1.20.4", + "body-parser": "^1.20.6", "buffer": "^6.0.3", "bull": "^4.16.5", "cache-manager": "^5.7.6", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0008ff7dd..e9c35dc3b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -195,8 +195,8 @@ importers: specifier: ^0.1.3 version: 0.1.3 body-parser: - specifier: ^1.20.4 - version: 1.20.5 + specifier: ^1.20.6 + version: 1.20.6 buffer: specifier: ^6.0.3 version: 6.0.3 @@ -2749,12 +2749,12 @@ packages: bn.js@4.12.3: resolution: {integrity: sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==} - body-parser@1.20.5: - resolution: {integrity: sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==} + body-parser@1.20.6: + resolution: {integrity: sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - body-parser@2.2.2: - resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} + body-parser@2.3.0: + resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==} engines: {node: '>=18'} bowser@2.14.1: @@ -3035,6 +3035,10 @@ packages: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} + content-type@2.0.0: + resolution: {integrity: sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==} + engines: {node: '>=18'} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -4095,8 +4099,8 @@ packages: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} - iconv-lite@0.7.2: - resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} + iconv-lite@0.7.3: + resolution: {integrity: sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==} engines: {node: '>=0.10.0'} idtoken-verifier@2.2.4: @@ -4921,6 +4925,7 @@ packages: nats@2.29.3: resolution: {integrity: sha512-tOQCRCwC74DgBTk4pWZ9V45sk4d7peoE2njVprMRCBXrhJ5q5cYM7i6W+Uvw2qUrcfOSnuisrX7bEx3b3Wx4QA==} engines: {node: '>= 14.0.0'} + deprecated: Package moved. Use @nats-io/transport-node from https://github.com/nats-io/nats.js natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} @@ -5469,6 +5474,10 @@ packages: resolution: {integrity: sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==} engines: {node: '>=0.6'} + qs@6.15.3: + resolution: {integrity: sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==} + engines: {node: '>=0.6'} + qs@6.5.5: resolution: {integrity: sha512-mzR4sElr1bfCaPJe7m8ilJ6ZXdDaGoObcYR0ZHSsktM/Lt21MVHj5De30GQH2eiZ1qGRTO7LCAzQsUeXTNexWQ==} engines: {node: '>=0.6'} @@ -5750,6 +5759,10 @@ packages: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} + side-channel@1.1.1: + resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} + engines: {node: '>= 0.4'} + signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -6193,6 +6206,10 @@ packages: resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} engines: {node: '>= 0.6'} + type-is@2.1.0: + resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} + engines: {node: '>= 18'} + typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} engines: {node: '>= 0.4'} @@ -7457,7 +7474,7 @@ snapshots: '@inquirer/external-editor@1.0.3(@types/node@20.19.39)': dependencies: chardet: 2.1.1 - iconv-lite: 0.7.2 + iconv-lite: 0.7.3 optionalDependencies: '@types/node': 20.19.39 @@ -9596,7 +9613,7 @@ snapshots: bn.js@4.12.3: {} - body-parser@1.20.5: + body-parser@1.20.6: dependencies: bytes: 3.1.2 content-type: 1.0.5 @@ -9606,24 +9623,24 @@ snapshots: http-errors: 2.0.1 iconv-lite: 0.4.24 on-finished: 2.4.1 - qs: 6.15.1 + qs: 6.15.3 raw-body: 2.5.3 type-is: 1.6.18 unpipe: 1.0.0 transitivePeerDependencies: - supports-color - body-parser@2.2.2: + body-parser@2.3.0: dependencies: bytes: 3.1.2 - content-type: 1.0.5 + content-type: 2.0.0 debug: 4.4.3 http-errors: 2.0.1 - iconv-lite: 0.7.2 + iconv-lite: 0.7.3 on-finished: 2.4.1 - qs: 6.15.1 + qs: 6.15.3 raw-body: 3.0.2 - type-is: 2.0.1 + type-is: 2.1.0 transitivePeerDependencies: - supports-color @@ -9907,6 +9924,8 @@ snapshots: content-type@1.0.5: {} + content-type@2.0.0: {} + convert-source-map@2.0.0: {} cookie-parser@1.4.7: @@ -10066,7 +10085,7 @@ snapshots: object-keys: 1.1.1 object.assign: 4.1.7 regexp.prototype.flags: 1.5.4 - side-channel: 1.1.0 + side-channel: 1.1.1 which-boxed-primitive: 1.1.1 which-collection: 1.0.2 which-typed-array: 1.1.20 @@ -10598,7 +10617,7 @@ snapshots: express@5.2.1: dependencies: accepts: 2.0.0 - body-parser: 2.2.2 + body-parser: 2.3.0 content-disposition: 1.1.0 content-type: 1.0.5 cookie: 0.7.2 @@ -10642,7 +10661,7 @@ snapshots: extract-zip@2.0.1: dependencies: - debug: 4.3.4 + debug: 4.4.3 get-stream: 5.2.0 yauzl: 2.10.0 optionalDependencies: @@ -11004,7 +11023,7 @@ snapshots: dependencies: basic-ftp: 5.3.1 data-uri-to-buffer: 6.0.2 - debug: 4.3.4 + debug: 4.4.3 transitivePeerDependencies: - supports-color @@ -11230,7 +11249,7 @@ snapshots: http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.4 - debug: 4.3.4 + debug: 4.4.3 transitivePeerDependencies: - supports-color @@ -11270,7 +11289,7 @@ snapshots: dependencies: safer-buffer: 2.1.2 - iconv-lite@0.7.2: + iconv-lite@0.7.3: dependencies: safer-buffer: 2.1.2 @@ -11321,7 +11340,7 @@ snapshots: dependencies: es-errors: 1.3.0 hasown: 2.0.3 - side-channel: 1.1.0 + side-channel: 1.1.1 ioredis@5.10.1: dependencies: @@ -12504,7 +12523,7 @@ snapshots: dependencies: '@tootallnate/quickjs-emscripten': 0.23.0 agent-base: 7.1.4 - debug: 4.3.4 + debug: 4.4.3 get-uri: 6.0.5 http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 @@ -12761,7 +12780,7 @@ snapshots: proxy-agent@6.3.0: dependencies: agent-base: 7.1.4 - debug: 4.3.4 + debug: 4.4.3 http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 lru-cache: 7.18.3 @@ -12774,7 +12793,7 @@ snapshots: proxy-agent@6.3.1: dependencies: agent-base: 7.1.4 - debug: 4.3.4 + debug: 4.4.3 http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 lru-cache: 7.18.3 @@ -12884,6 +12903,11 @@ snapshots: dependencies: side-channel: 1.1.0 + qs@6.15.3: + dependencies: + es-define-property: 1.0.1 + side-channel: 1.1.1 + qs@6.5.5: optional: true @@ -12906,7 +12930,7 @@ snapshots: dependencies: bytes: 3.1.2 http-errors: 2.0.1 - iconv-lite: 0.7.2 + iconv-lite: 0.7.3 unpipe: 1.0.0 react-is@18.3.1: {} @@ -13234,6 +13258,14 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + side-channel@1.1.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.1 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + signal-exit@3.0.7: {} signal-exit@4.1.0: {} @@ -13293,7 +13325,7 @@ snapshots: socks-proxy-agent@8.0.5: dependencies: agent-base: 7.1.4 - debug: 4.3.4 + debug: 4.4.3 socks: 2.8.8 transitivePeerDependencies: - supports-color @@ -13739,6 +13771,12 @@ snapshots: media-typer: 1.1.0 mime-types: 3.0.2 + type-is@2.1.0: + dependencies: + content-type: 2.0.0 + media-typer: 1.1.0 + mime-types: 3.0.2 + typed-array-buffer@1.0.3: dependencies: call-bound: 1.0.4 From 5fee42ccdd396404c39143cf7acf181663decc42 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Jul 2026 20:49:42 +0000 Subject: [PATCH 24/57] chore(deps): bump typeorm from 0.3.28 to 0.3.31 Bumps [typeorm](https://github.com/typeorm/typeorm) from 0.3.28 to 0.3.31. - [Release notes](https://github.com/typeorm/typeorm/releases) - [Changelog](https://github.com/typeorm/typeorm/blob/0.3.31/CHANGELOG.md) - [Commits](https://github.com/typeorm/typeorm/compare/0.3.28...0.3.31) --- updated-dependencies: - dependency-name: typeorm dependency-version: 0.3.31 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- package.json | 2 +- pnpm-lock.yaml | 88 +++++++++++++++++++++++++++++++------------------- 2 files changed, 56 insertions(+), 34 deletions(-) diff --git a/package.json b/package.json index 79f2dbe33..d35f0086e 100644 --- a/package.json +++ b/package.json @@ -118,7 +118,7 @@ "rxjs": "^7.8.2", "socket.io-client": "^4.8.3", "swagger-ui-express": "^5.0.1", - "typeorm": "^0.3.28", + "typeorm": "^0.3.31", "unzipper": "^0.12.3", "uuid": "^9.0.1", "validator": "^13.15.35", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0008ff7dd..2aacf3a20 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -121,7 +121,7 @@ importers: version: 11.4.2(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14) '@nestjs/typeorm': specifier: 'catalog:' - version: 11.0.1(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(reflect-metadata@0.1.14)(rxjs@7.8.2)(typeorm@0.3.28(ioredis@5.10.1)(pg@8.20.0)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3))) + version: 11.0.1(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(reflect-metadata@0.1.14)(rxjs@7.8.2)(typeorm@0.3.31(ioredis@5.10.1)(pg@8.20.0)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3))) '@nestjs/websockets': specifier: 'catalog:' version: 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/platform-socket.io@11.1.19)(reflect-metadata@0.1.14)(rxjs@7.8.2) @@ -295,7 +295,7 @@ importers: version: 1.0.9 nestjs-typeorm-paginate: specifier: 'catalog:' - version: 4.1.0(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(typeorm@0.3.28(ioredis@5.10.1)(pg@8.20.0)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3))) + version: 4.1.0(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(typeorm@0.3.31(ioredis@5.10.1)(pg@8.20.0)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3))) node-html-to-image: specifier: ^4.0.0 version: 4.0.0 @@ -363,8 +363,8 @@ importers: specifier: ^5.0.1 version: 5.0.1(express@5.2.1) typeorm: - specifier: ^0.3.28 - version: 0.3.28(ioredis@5.10.1)(pg@8.20.0)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)) + specifier: ^0.3.31 + version: 0.3.31(ioredis@5.10.1)(pg@8.20.0)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)) unzipper: specifier: ^0.12.3 version: 0.12.3 @@ -2506,8 +2506,8 @@ packages: resolution: {integrity: sha512-BGcItUBWSMRgOCe+SVZJ+S7yTRG0eGt9cXAHev72yuGcY23hnLA7Bky5L/xLyPINoSN95geovfBkqoTlNZYa7w==} engines: {node: '>=14'} - ansis@4.2.0: - resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==} + ansis@4.3.1: + resolution: {integrity: sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA==} engines: {node: '>=14'} anymatch@3.1.3: @@ -2766,6 +2766,9 @@ packages: brace-expansion@2.1.0: resolution: {integrity: sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==} + brace-expansion@2.1.2: + resolution: {integrity: sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==} + brace-expansion@5.0.5: resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} engines: {node: 18 || 20 || >=22} @@ -3142,8 +3145,8 @@ packages: resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} engines: {node: '>= 0.4'} - dayjs@1.11.20: - resolution: {integrity: sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==} + dayjs@1.11.21: + resolution: {integrity: sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==} debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} @@ -4921,6 +4924,7 @@ packages: nats@2.29.3: resolution: {integrity: sha512-tOQCRCwC74DgBTk4pWZ9V45sk4d7peoE2njVprMRCBXrhJ5q5cYM7i6W+Uvw2qUrcfOSnuisrX7bEx3b3Wx4QA==} engines: {node: '>= 14.0.0'} + deprecated: Package moved. Use @nats-io/transport-node from https://github.com/nats-io/nats.js natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} @@ -6212,8 +6216,8 @@ packages: typedarray@0.0.6: resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} - typeorm@0.3.28: - resolution: {integrity: sha512-6GH7wXhtfq2D33ZuRXYwIsl/qM5685WZcODZb7noOOcRMteM9KF2x2ap3H0EBjnSV0VO4gNAfJT5Ukp0PkOlvg==} + typeorm@0.3.31: + resolution: {integrity: sha512-6u9EFtdLBgHjnPm78NStVeM+I/1MolTzKykDDcydzKUkh6E++YS6XViU/fePJbvDvEGU4Xq34KOM/CLeer9I2A==} engines: {node: '>=16.13.0'} hasBin: true peerDependencies: @@ -6224,13 +6228,13 @@ packages: mongodb: ^5.8.0 || ^6.0.0 mssql: ^9.1.1 || ^10.0.0 || ^11.0.0 || ^12.0.0 mysql2: ^2.2.5 || ^3.0.1 - oracledb: ^6.3.0 + oracledb: ^6.3.0 || ^7.0.0 pg: ^8.5.1 pg-native: ^3.0.0 pg-query-stream: ^4.0.0 redis: ^3.1.1 || ^4.0.0 || ^5.0.14 sql.js: ^1.4.0 - sqlite3: ^5.0.3 + sqlite3: ^5.0.3 || ^6.0.0 ts-node: ^10.7.0 typeorm-aurora-data-api-driver: ^2.0.0 || ^3.0.0 peerDependenciesMeta: @@ -6621,6 +6625,10 @@ packages: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} + yargs@17.7.3: + resolution: {integrity: sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==} + engines: {node: '>=12'} + yauzl@2.10.0: resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} @@ -7384,7 +7392,7 @@ snapshots: lodash.camelcase: 4.3.0 long: 5.3.2 protobufjs: 7.5.6 - yargs: 17.7.2 + yargs: 17.7.3 optional: true '@grpc/proto-loader@0.8.0': @@ -7392,7 +7400,7 @@ snapshots: lodash.camelcase: 4.3.0 long: 5.3.2 protobufjs: 7.5.6 - yargs: 17.7.2 + yargs: 17.7.3 '@humanwhocodes/config-array@0.13.0': dependencies: @@ -7993,13 +8001,13 @@ snapshots: '@nestjs/microservices': 11.1.19(@grpc/grpc-js@1.14.3)(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/websockets@11.1.19)(cache-manager@5.7.6)(ioredis@5.10.1)(nats@2.29.3)(reflect-metadata@0.1.14)(rxjs@7.8.2) '@nestjs/platform-express': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19) - '@nestjs/typeorm@11.0.1(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(reflect-metadata@0.1.14)(rxjs@7.8.2)(typeorm@0.3.28(ioredis@5.10.1)(pg@8.20.0)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)))': + '@nestjs/typeorm@11.0.1(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(reflect-metadata@0.1.14)(rxjs@7.8.2)(typeorm@0.3.31(ioredis@5.10.1)(pg@8.20.0)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)))': dependencies: '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@11.1.19)(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.1.14)(rxjs@7.8.2) reflect-metadata: 0.1.14 rxjs: 7.8.2 - typeorm: 0.3.28(ioredis@5.10.1)(pg@8.20.0)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)) + typeorm: 0.3.31(ioredis@5.10.1)(pg@8.20.0)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)) '@nestjs/websockets@11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/platform-socket.io@11.1.19)(reflect-metadata@0.1.14)(rxjs@7.8.2)': dependencies: @@ -9304,7 +9312,7 @@ snapshots: ansis@4.1.0: {} - ansis@4.2.0: {} + ansis@4.3.1: {} anymatch@3.1.3: dependencies: @@ -9638,6 +9646,10 @@ snapshots: dependencies: balanced-match: 1.0.2 + brace-expansion@2.1.2: + dependencies: + balanced-match: 1.0.2 + brace-expansion@5.0.5: dependencies: balanced-match: 4.0.4 @@ -10028,7 +10040,7 @@ snapshots: es-errors: 1.3.0 is-data-view: 1.0.2 - dayjs@1.11.20: {} + dayjs@1.11.21: {} debug@2.6.9: dependencies: @@ -10642,7 +10654,7 @@ snapshots: extract-zip@2.0.1: dependencies: - debug: 4.3.4 + debug: 4.4.3 get-stream: 5.2.0 yauzl: 2.10.0 optionalDependencies: @@ -11004,7 +11016,7 @@ snapshots: dependencies: basic-ftp: 5.3.1 data-uri-to-buffer: 6.0.2 - debug: 4.3.4 + debug: 4.4.3 transitivePeerDependencies: - supports-color @@ -11230,7 +11242,7 @@ snapshots: http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.4 - debug: 4.3.4 + debug: 4.4.3 transitivePeerDependencies: - supports-color @@ -11586,7 +11598,7 @@ snapshots: jest-config: 29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)) jest-util: 29.7.0 jest-validate: 29.7.0 - yargs: 17.7.2 + yargs: 17.7.3 transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -12209,7 +12221,7 @@ snapshots: minimatch@9.0.9: dependencies: - brace-expansion: 2.1.0 + brace-expansion: 2.1.2 minimist@1.2.8: {} @@ -12298,10 +12310,10 @@ snapshots: nestjs-supabase-auth@1.0.9: {} - nestjs-typeorm-paginate@4.1.0(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(typeorm@0.3.28(ioredis@5.10.1)(pg@8.20.0)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3))): + nestjs-typeorm-paginate@4.1.0(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(typeorm@0.3.31(ioredis@5.10.1)(pg@8.20.0)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3))): dependencies: '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) - typeorm: 0.3.28(ioredis@5.10.1)(pg@8.20.0)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)) + typeorm: 0.3.31(ioredis@5.10.1)(pg@8.20.0)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)) netmask@2.1.1: {} @@ -12504,7 +12516,7 @@ snapshots: dependencies: '@tootallnate/quickjs-emscripten': 0.23.0 agent-base: 7.1.4 - debug: 4.3.4 + debug: 4.4.3 get-uri: 6.0.5 http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 @@ -12761,7 +12773,7 @@ snapshots: proxy-agent@6.3.0: dependencies: agent-base: 7.1.4 - debug: 4.3.4 + debug: 4.4.3 http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 lru-cache: 7.18.3 @@ -12774,7 +12786,7 @@ snapshots: proxy-agent@6.3.1: dependencies: agent-base: 7.1.4 - debug: 4.3.4 + debug: 4.4.3 http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 lru-cache: 7.18.3 @@ -13293,7 +13305,7 @@ snapshots: socks-proxy-agent@8.0.5: dependencies: agent-base: 7.1.4 - debug: 4.3.4 + debug: 4.4.3 socks: 2.8.8 transitivePeerDependencies: - supports-color @@ -13774,13 +13786,13 @@ snapshots: typedarray@0.0.6: {} - typeorm@0.3.28(ioredis@5.10.1)(pg@8.20.0)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)): + typeorm@0.3.31(ioredis@5.10.1)(pg@8.20.0)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)): dependencies: '@sqltools/formatter': 1.2.5 - ansis: 4.2.0 + ansis: 4.3.1 app-root-path: 3.1.0 buffer: 6.0.3 - dayjs: 1.11.20 + dayjs: 1.11.21 debug: 4.4.3 dedent: 1.7.2 dotenv: 16.6.1 @@ -13790,7 +13802,7 @@ snapshots: sql-highlight: 6.1.0 tslib: 2.8.1 uuid: 11.1.1 - yargs: 17.7.2 + yargs: 17.7.3 optionalDependencies: ioredis: 5.10.1 pg: 8.20.0 @@ -14176,6 +14188,16 @@ snapshots: y18n: 5.0.8 yargs-parser: 21.1.1 + yargs@17.7.3: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + yauzl@2.10.0: dependencies: buffer-crc32: 0.2.13 From 6fbc9b186a0c0f06d0914368b55a20133d0026ef Mon Sep 17 00:00:00 2001 From: sujitaw Date: Mon, 27 Jul 2026 16:59:33 +0530 Subject: [PATCH 25/57] feat/docker files for open bao Signed-off-by: sujitaw --- config.hcl | 10 ++++++++++ docker-compose.openbao.yml | 23 +++++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 config.hcl create mode 100644 docker-compose.openbao.yml diff --git a/config.hcl b/config.hcl new file mode 100644 index 000000000..db00e3ebb --- /dev/null +++ b/config.hcl @@ -0,0 +1,10 @@ +storage "file" { + path = "/bao/data" +} + +listener "tcp" { + address = "0.0.0.0:8200" + tls_disable = 1 # In true production, configure actual TLS certificates here +} + +ui = true diff --git a/docker-compose.openbao.yml b/docker-compose.openbao.yml new file mode 100644 index 000000000..a2b119a98 --- /dev/null +++ b/docker-compose.openbao.yml @@ -0,0 +1,23 @@ +version: '3.8' + +services: + openbao: + image: openbao/openbao:latest + container_name: openbao + # Runs as openbao user (UID 100) instead of root + user: "100:100" + ports: + - "8200:8200" + environment: + - BAO_ADDR=http://127.0.0.1:8200 + volumes: + - openbao_data:/bao/data + - ./config/bao.hcl:/bao/config/bao.hcl:ro + cap_add: + - IPC_LOCK + # Expressly point to the HCL config file + command: ["server", "-config=/bao/config/bao.hcl"] + restart: unless-stopped + +volumes: + openbao_data: \ No newline at end of file From f9ee32d174d0182b706b3a9fa41b75c95050d547 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Aug 2026 03:34:56 +0000 Subject: [PATCH 26/57] build: bump redis from 6.2-alpine to 8.0-alpine Bumps redis from 6.2-alpine to 8.0-alpine. --- updated-dependencies: - dependency-name: redis dependency-version: 8.0-alpine dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- docker-compose-dev.yml | 2 +- docker-compose.redis.yml | 2 +- docker-compose.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index 59d392828..2aa4b0478 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -12,7 +12,7 @@ services: # volumes: # - ./nats-server.conf:/nats-server.conf # Mount the config file redis: - image: redis:6.2-alpine + image: redis:8.0-alpine restart: always ports: - '6379:6379' diff --git a/docker-compose.redis.yml b/docker-compose.redis.yml index c17394ea1..9b2389a0d 100644 --- a/docker-compose.redis.yml +++ b/docker-compose.redis.yml @@ -1,7 +1,7 @@ version: '3' services: redis: - image: redis:6.2-alpine + image: redis:8.0-alpine restart: always ports: - '6379:6379' diff --git a/docker-compose.yml b/docker-compose.yml index 991af677e..38996d977 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,7 +13,7 @@ services: - ./nats-server.conf:/nats-server.conf:ro redis: container_name: credebl-redis - image: redis:6.2-alpine + image: redis:8.0-alpine restart: always ports: - '6379:6379' From 74ef5f1983e1f7e966512e590b412a109eb50596 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Aug 2026 12:52:15 +0000 Subject: [PATCH 27/57] build: bump protobufjs from 7.5.6 to 7.6.5 Bumps [protobufjs](https://github.com/protobufjs/protobuf.js) from 7.5.6 to 7.6.5. - [Release notes](https://github.com/protobufjs/protobuf.js/releases) - [Changelog](https://github.com/protobufjs/protobuf.js/blob/protobufjs-v7.6.5/CHANGELOG.md) - [Commits](https://github.com/protobufjs/protobuf.js/compare/protobufjs-v7.5.6...protobufjs-v7.6.5) --- updated-dependencies: - dependency-name: protobufjs dependency-version: 7.6.5 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- package.json | 2 +- pnpm-lock.yaml | 55 +++++++++++++++++++++++--------------------------- 2 files changed, 26 insertions(+), 31 deletions(-) diff --git a/package.json b/package.json index 697d33844..19c36a1dd 100644 --- a/package.json +++ b/package.json @@ -126,7 +126,7 @@ "winston": "^3.19.0", "winston-daily-rotate-file": "^5.0.0", "xml-js": "^1.6.11", - "protobufjs": "^7.5.6", + "protobufjs": "^7.6.5", "handlebars": "^4.7.9", "form-data": "^4.0.5" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c15e52875..98500dea4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -330,8 +330,8 @@ importers: specifier: ^8.18.0 version: 8.20.0 protobufjs: - specifier: ^7.5.6 - version: 7.5.6 + specifier: ^7.6.5 + version: 7.6.5 puppeteer: specifier: ^21.11.0 version: 21.11.0(typescript@5.9.3) @@ -1778,26 +1778,23 @@ packages: '@protobufjs/codegen@2.0.5': resolution: {integrity: sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==} - '@protobufjs/eventemitter@1.1.0': - resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} + '@protobufjs/eventemitter@1.1.1': + resolution: {integrity: sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==} - '@protobufjs/fetch@1.1.0': - resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==} + '@protobufjs/fetch@1.1.1': + resolution: {integrity: sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==} '@protobufjs/float@1.0.2': resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} - '@protobufjs/inquire@1.1.1': - resolution: {integrity: sha512-mnzgDV26ueAvk7rsbt9L7bE0SuAoqyuys/sMMrmVcN5x9VsxpcG3rqAUSgDyLp0UZlmNfIbQ4fHfCtreVBk8Ew==} - '@protobufjs/path@1.1.2': resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} '@protobufjs/pool@1.1.0': resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} - '@protobufjs/utf8@1.1.1': - resolution: {integrity: sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==} + '@protobufjs/utf8@1.1.2': + resolution: {integrity: sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==} '@puppeteer/browsers@1.5.0': resolution: {integrity: sha512-za318PweGINh5LnHSph7C4xhs0tmRjCD8EPpzcKlw4nzSPhnULj+LTG3+TGefZvW1ti5gjw2JkdQvQsivBeZlg==} @@ -3106,6 +3103,7 @@ packages: cron-parser@4.9.0: resolution: {integrity: sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==} engines: {node: '>=12.0.0'} + deprecated: v4 is no longer maintained, upgrade to v5 cron@4.4.0: resolution: {integrity: sha512-fkdfq+b+AHI4cKdhZlppHveI/mgz2qpiYxcm+t5E5TsxX7QrLS1VE0+7GENEk9z0EeGPcpSciGv6ez24duWhwQ==} @@ -3120,6 +3118,7 @@ packages: crypto-js@4.2.0: resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==} + deprecated: Active development of CryptoJS has been discontinued. This library is no longer maintained. crypto-random-string@5.0.0: resolution: {integrity: sha512-KWjTXWwxFd6a94m5CdRGW/t82Tr8DoBc9dNnPCAbFI1EBweN6v1tv8y4Y1m7ndkp/nkIBRxUxAzpaBnR2k3bcQ==} @@ -5405,8 +5404,8 @@ packages: resolution: {integrity: sha512-SAzp/O4Yh02jGdRc+uIrGoe87dkN/XtwxfZ4ZyafJHymd79ozp5VG5nyZ7ygqPM5+cpLDjjGnYFUkngonyDPOQ==} engines: {node: '>=14.0.0'} - protobufjs@7.5.6: - resolution: {integrity: sha512-M71sTMB146U3u0di3yup8iM+zv8yPRNQVr1KK4tyBitl3qFvEGucq/rGDRShD2rsJhtN02RJaJ7j5X5hmy8SJg==} + protobufjs@7.6.5: + resolution: {integrity: sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw==} engines: {node: '>=12.0.0'} proxy-addr@2.0.7: @@ -7358,7 +7357,7 @@ snapshots: fast-deep-equal: 3.1.3 functional-red-black-tree: 1.0.1 google-gax: 4.6.1 - protobufjs: 7.5.6 + protobufjs: 7.6.5 transitivePeerDependencies: - encoding - supports-color @@ -7407,7 +7406,7 @@ snapshots: dependencies: lodash.camelcase: 4.3.0 long: 5.3.2 - protobufjs: 7.5.6 + protobufjs: 7.6.5 yargs: 17.7.3 optional: true @@ -7415,7 +7414,7 @@ snapshots: dependencies: lodash.camelcase: 4.3.0 long: 5.3.2 - protobufjs: 7.5.6 + protobufjs: 7.6.5 yargs: 17.7.3 '@humanwhocodes/config-array@0.13.0': @@ -8241,7 +8240,7 @@ snapshots: '@opentelemetry/sdk-logs': 0.202.0(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-metrics': 2.0.1(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-trace-base': 2.0.1(@opentelemetry/api@1.9.1) - protobufjs: 7.5.6 + protobufjs: 7.6.5 '@opentelemetry/propagator-b3@2.0.1(@opentelemetry/api@1.9.1)': dependencies: @@ -8360,22 +8359,19 @@ snapshots: '@protobufjs/codegen@2.0.5': {} - '@protobufjs/eventemitter@1.1.0': {} + '@protobufjs/eventemitter@1.1.1': {} - '@protobufjs/fetch@1.1.0': + '@protobufjs/fetch@1.1.1': dependencies: '@protobufjs/aspromise': 1.1.2 - '@protobufjs/inquire': 1.1.1 '@protobufjs/float@1.0.2': {} - '@protobufjs/inquire@1.1.1': {} - '@protobufjs/path@1.1.2': {} '@protobufjs/pool@1.1.0': {} - '@protobufjs/utf8@1.1.1': {} + '@protobufjs/utf8@1.1.2': {} '@puppeteer/browsers@1.5.0': dependencies: @@ -11140,7 +11136,7 @@ snapshots: node-fetch: 2.7.0 object-hash: 3.0.0 proto3-json-serializer: 2.0.2 - protobufjs: 7.5.6 + protobufjs: 7.6.5 retry-request: 7.0.2 uuid: 9.0.1 transitivePeerDependencies: @@ -12765,21 +12761,20 @@ snapshots: proto3-json-serializer@2.0.2: dependencies: - protobufjs: 7.5.6 + protobufjs: 7.6.5 optional: true - protobufjs@7.5.6: + protobufjs@7.6.5: dependencies: '@protobufjs/aspromise': 1.1.2 '@protobufjs/base64': 1.1.2 '@protobufjs/codegen': 2.0.5 - '@protobufjs/eventemitter': 1.1.0 - '@protobufjs/fetch': 1.1.0 + '@protobufjs/eventemitter': 1.1.1 + '@protobufjs/fetch': 1.1.1 '@protobufjs/float': 1.0.2 - '@protobufjs/inquire': 1.1.1 '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 - '@protobufjs/utf8': 1.1.1 + '@protobufjs/utf8': 1.1.2 '@types/node': 20.19.39 long: 5.3.2 From f501cf5be342ccf8bc3ad446e767f6d871d12d93 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Aug 2026 12:52:30 +0000 Subject: [PATCH 28/57] build: bump axios from 1.16.0 to 1.18.0 Bumps [axios](https://github.com/axios/axios) from 1.16.0 to 1.18.0. - [Release notes](https://github.com/axios/axios/releases) - [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md) - [Commits](https://github.com/axios/axios/compare/v1.16.0...v1.18.0) --- updated-dependencies: - dependency-name: axios dependency-version: 1.18.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- package.json | 2 +- pnpm-lock.yaml | 43 +++++++++++++++++++++++++++---------------- 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index 697d33844..17c7cfb64 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "async-retry": "^1.3.3", "auth0-js": "^9.30.1", "aws-sdk": "^2.1693.0", - "axios": "^1.16.0", + "axios": "^1.18.0", "bcrypt": "^6.0.0", "blob-stream": "^0.1.3", "body-parser": "^1.20.6", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c15e52875..3d8071789 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -82,7 +82,7 @@ importers: version: 3.1044.0 '@nestjs/axios': specifier: 'catalog:' - version: 4.0.1(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(axios@1.16.0)(rxjs@7.8.2) + version: 4.0.1(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(axios@1.18.0)(rxjs@7.8.2) '@nestjs/bull': specifier: 'catalog:' version: 11.0.4(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(bull@4.16.5) @@ -186,8 +186,8 @@ importers: specifier: ^2.1693.0 version: 2.1693.0 axios: - specifier: ^1.16.0 - version: 1.16.0 + specifier: ^1.18.0 + version: 1.18.0 bcrypt: specifier: ^6.0.0 version: 6.0.0 @@ -488,7 +488,7 @@ importers: dependencies: '@nestjs/axios': specifier: 'catalog:' - version: 4.0.1(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(axios@1.16.0)(rxjs@7.8.2) + version: 4.0.1(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(axios@1.18.0)(rxjs@7.8.2) '@nestjs/cache-manager': specifier: 'catalog:' version: 3.1.2(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(cache-manager@5.7.6)(keyv@4.5.4)(rxjs@7.8.2) @@ -2616,8 +2616,8 @@ packages: axios@0.26.1: resolution: {integrity: sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==} - axios@1.16.0: - resolution: {integrity: sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==} + axios@1.18.0: + resolution: {integrity: sha512-E32NzpYKp++W7XRe52rHiXV2ehxmh3wbdgO7MHeFM+vqxLBYHzt0ElkiImtOBxtOmyp0yoC8C6uESVV84Y2/hw==} b4a@1.8.1: resolution: {integrity: sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==} @@ -3106,6 +3106,7 @@ packages: cron-parser@4.9.0: resolution: {integrity: sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==} engines: {node: '>=12.0.0'} + deprecated: v4 is no longer maintained, upgrade to v5 cron@4.4.0: resolution: {integrity: sha512-fkdfq+b+AHI4cKdhZlppHveI/mgz2qpiYxcm+t5E5TsxX7QrLS1VE0+7GENEk9z0EeGPcpSciGv6ez24duWhwQ==} @@ -3120,6 +3121,7 @@ packages: crypto-js@4.2.0: resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==} + deprecated: Active development of CryptoJS has been discontinued. This library is no longer maintained. crypto-random-string@5.0.0: resolution: {integrity: sha512-KWjTXWwxFd6a94m5CdRGW/t82Tr8DoBc9dNnPCAbFI1EBweN6v1tv8y4Y1m7ndkp/nkIBRxUxAzpaBnR2k3bcQ==} @@ -3773,8 +3775,8 @@ packages: resolution: {integrity: sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==} engines: {node: '>= 0.12'} - form-data@2.5.5: - resolution: {integrity: sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A==} + form-data@2.5.6: + resolution: {integrity: sha512-Ogz/E85h9tlfJzpI6TuFpGcHZFhLrb9Gw8wq9v40CxSCPnv7ahKr6Xgtkn0KYCDQJ8DNn5VoMO8EXr9V5PadyA==} engines: {node: '>= 0.12'} form-data@4.0.5: @@ -4031,6 +4033,10 @@ packages: resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} engines: {node: '>= 0.4'} + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + helmet@8.1.0: resolution: {integrity: sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==} engines: {node: '>=18.0.0'} @@ -7808,10 +7814,10 @@ snapshots: '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3': optional: true - '@nestjs/axios@4.0.1(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(axios@1.16.0)(rxjs@7.8.2)': + '@nestjs/axios@4.0.1(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(axios@1.18.0)(rxjs@7.8.2)': dependencies: '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) - axios: 1.16.0 + axios: 1.18.0 rxjs: 7.8.2 '@nestjs/bull-shared@11.0.4(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)': @@ -8949,7 +8955,7 @@ snapshots: '@types/caseless': 0.12.5 '@types/node': 20.19.39 '@types/tough-cookie': 4.0.5 - form-data: 2.5.5 + form-data: 2.5.6 optional: true '@types/retry@0.12.5': {} @@ -9251,7 +9257,6 @@ snapshots: debug: 4.4.3 transitivePeerDependencies: - supports-color - optional: true agent-base@7.1.4: {} @@ -9477,13 +9482,15 @@ snapshots: transitivePeerDependencies: - debug - axios@1.16.0: + axios@1.18.0: dependencies: follow-redirects: 1.16.0 form-data: 4.0.5 + https-proxy-agent: 5.0.1 proxy-from-env: 2.1.0 transitivePeerDependencies: - debug + - supports-color b4a@1.8.1: {} @@ -10872,12 +10879,12 @@ snapshots: mime-types: 2.1.35 optional: true - form-data@2.5.5: + form-data@2.5.6: dependencies: asynckit: 0.4.0 combined-stream: 1.0.8 es-set-tostringtag: 2.1.0 - hasown: 2.0.3 + hasown: 2.0.4 mime-types: 2.1.35 safe-buffer: 5.2.1 optional: true @@ -11223,6 +11230,11 @@ snapshots: dependencies: function-bind: 1.1.2 + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + optional: true + helmet@8.1.0: {} html-entities@2.6.0: @@ -11279,7 +11291,6 @@ snapshots: debug: 4.4.3 transitivePeerDependencies: - supports-color - optional: true https-proxy-agent@7.0.6: dependencies: From ab6ee20851b0077789ed89fea20724506d966bcd Mon Sep 17 00:00:00 2001 From: Ajay Jadhav Date: Fri, 7 Aug 2026 01:37:35 +0530 Subject: [PATCH 29/57] test/docs: add OpenBao tests, provisioning script, and docs - add unit tests for OpenBao provider, secrets loader, secret loader util, TTL cache, and email/S3 credential sourcing (50 tests) - add openbao-init.sh: idempotent provisioning (init/unseal, KV v2, AppRole, credebl role) - fix docker-compose.openbao.yml + config.hcl storage volume ownership - add ENABLE_BAO/SECRETS_PROVIDER/BAO_* vars to .env.demo - add README 'OpenBao Secret Storage' guide and openspec Signed-off-by: Ajay Jadhav --- .env.demo | 18 +- README.md | 48 ++++ config.hcl | 2 +- docker-compose.openbao.yml | 9 +- libs/common/src/resend-helper-file.spec.ts | 81 ++++++ libs/common/src/send-grid-helper-file.spec.ts | 73 ++++++ libs/common/src/smtp-helper-file.spec.ts | 122 +++++++++ .../src/utils/secretLoader.util.spec.ts | 86 +++++++ libs/common/src/utils/ttl-cache.util.spec.ts | 68 +++++ .../secret-storage/openbao.provider.spec.ts | 130 ++++++++++ .../src/secret-storage/secrets-loader.spec.ts | 104 ++++++++ .../src/providers/s3-storage.provider.spec.ts | 108 ++++++++ openbao-init.sh | 164 ++++++++++++ .../openbao-secret-storage.spec.md | 235 ++++++++++++++++++ package.json | 6 +- 15 files changed, 1240 insertions(+), 14 deletions(-) create mode 100644 libs/common/src/resend-helper-file.spec.ts create mode 100644 libs/common/src/send-grid-helper-file.spec.ts create mode 100644 libs/common/src/smtp-helper-file.spec.ts create mode 100644 libs/common/src/utils/secretLoader.util.spec.ts create mode 100644 libs/common/src/utils/ttl-cache.util.spec.ts create mode 100644 libs/config/src/secret-storage/openbao.provider.spec.ts create mode 100644 libs/config/src/secret-storage/secrets-loader.spec.ts create mode 100644 libs/storage/src/providers/s3-storage.provider.spec.ts create mode 100755 openbao-init.sh create mode 100644 openspec/specs/openbao-secret-storage/openbao-secret-storage.spec.md diff --git a/.env.demo b/.env.demo index 83d2fe9e4..102d61508 100644 --- a/.env.demo +++ b/.env.demo @@ -382,8 +382,18 @@ ADMIN_KEYCLOAK_SECRET= # Add in case the keycloak id of the old platform client is changed and want to update all the users in database with the new admin client PLATFORM_ADMIN_OLD_CLIENT_ID= -BAO_URL= -BAO_SECRET_PATH= -BAO_ROLE_ID= -BAO_SECRET_ID= +# --- OpenBao secret storage (PR #1679) --- +# Enable remote secrets fetching via OpenBao. Set to false to fall back to +# plain environment variables (default local behaviour). +ENABLE_BAO=true +# Secret provider: only 'openbao' is supported today +SECRETS_PROVIDER=openbao +# OpenBao server URL (docker-compose.openbao.yml maps 8200) +BAO_URL=http://127.0.0.1:8200 +# Default KV v2 path fetched at boot and injected into process.env +BAO_SECRET_PATH=secret/data/credebl_resend_api_key +# AppRole credentials. Generate your own via: ./openbao-init.sh +# (values below are for the local 'openbao' container created by docker-compose.openbao.yml) +BAO_ROLE_ID=ca2a350b-1871-0851-8557-42b36aa9c623 +BAO_SECRET_ID=3f0d8d45-c232-f5c1-2984-fe58e5144be8 HIDE_EXPERIMENTAL_OIDC_CONTROLLERS=false diff --git a/README.md b/README.md index 20f15ecff..acf763699 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,54 @@ The `docker-compose.yml` file is available in the root folder. docker-compose up ``` +## OpenBao Secret Storage (Optional) + +The platform can source the credentials used by email and file-storage integrations from an [OpenBao](https://openbao.org) server instead of plain environment variables. This is opt-in and disabled by default. + +### • Run OpenBao using Docker Compose + +```bash +docker compose -f docker-compose.openbao.yml up -d +``` + +This starts an OpenBao server on `http://127.0.0.1:8200` with file-backed storage persisted on a named volume. Server settings live in `config.hcl`. + +### • Provision the server + +OpenBao starts sealed and uninitialized. Run the one-shot provisioning script (idempotent — safe to re-run): + +```bash +./openbao-init.sh +``` + +The script initializes and unseals the server, enables the KV v2 secrets engine at `secret/`, enables the AppRole auth method, creates a `credebl` role scoped to the `credebl_*` secret paths, stores the secret values found in the environment (e.g. `RESEND_API_KEY`, `SMTP_HOST`, `AWS_ACCESS_KEY`), and prints the values to copy into your env file: + +```bash +BAO_URL=http://127.0.0.1:8200 +BAO_SECRET_PATH=secret/data/credebl_resend_api_key +BAO_ROLE_ID= +BAO_SECRET_ID= +``` + +Keep the printed `BAO_UNSEAL_KEY` and `BAO_ROOT_TOKEN` safe — they are shown only once. On later runs, pass `BAO_ROOT_TOKEN` (and `BAO_SECRET_ID` to re-print it). + +### • Configure the platform + +Add the following to your `.env` (values are already present as placeholders in `.env.demo`): + +```bash +ENABLE_BAO=true +SECRETS_PROVIDER=openbao +BAO_URL=http://127.0.0.1:8200 +BAO_SECRET_PATH=secret/data/credebl_resend_api_key +BAO_ROLE_ID= +BAO_SECRET_ID= +``` + +At startup every microservice authenticates to OpenBao via AppRole, fetches the secrets at `BAO_SECRET_PATH`, and injects them into `process.env` before the NATS listener starts. Email and S3 integrations re-fetch their own credential paths on demand (e.g. `secret/data/credebl_smtp_config`, `secret/data/credebl_aws_keys`) with a 10-minute TTL cache. + +Set `ENABLE_BAO=false` (or omit it) to fall back to local environment variables. + ## Run CREDEBL Microservices ### • Install Dependencies diff --git a/config.hcl b/config.hcl index db00e3ebb..4f0774dfd 100644 --- a/config.hcl +++ b/config.hcl @@ -1,5 +1,5 @@ storage "file" { - path = "/bao/data" + path = "/openbao/file" } listener "tcp" { diff --git a/docker-compose.openbao.yml b/docker-compose.openbao.yml index a2b119a98..f9e44b5f1 100644 --- a/docker-compose.openbao.yml +++ b/docker-compose.openbao.yml @@ -1,18 +1,15 @@ -version: '3.8' - services: openbao: image: openbao/openbao:latest container_name: openbao - # Runs as openbao user (UID 100) instead of root - user: "100:100" + # Entrypoint runs as root, chowns the data volume, then drops to openbao user ports: - "8200:8200" environment: - BAO_ADDR=http://127.0.0.1:8200 volumes: - - openbao_data:/bao/data - - ./config/bao.hcl:/bao/config/bao.hcl:ro + - openbao_data:/openbao/file + - ./config.hcl:/bao/config/bao.hcl:ro cap_add: - IPC_LOCK # Expressly point to the HCL config file diff --git a/libs/common/src/resend-helper-file.spec.ts b/libs/common/src/resend-helper-file.spec.ts new file mode 100644 index 000000000..0eb374dde --- /dev/null +++ b/libs/common/src/resend-helper-file.spec.ts @@ -0,0 +1,81 @@ +import { sendWithResend } from './resend-helper-file'; +import { fetchSecrets } from './utils/secretLoader.util'; + +jest.mock('resend', () => ({ + Resend: jest.fn() +})); +jest.mock('./utils/secretLoader.util', () => ({ + fetchSecrets: jest.fn() +})); + +import { Resend } from 'resend'; +import { EmailDto } from './dtos/email.dto'; + +const MockedResend = Resend as unknown as jest.Mock; +const mockedFetchSecrets = fetchSecrets as jest.MockedFunction; + +const emailDto: EmailDto = { + emailFrom: 'from@credebl.id', + emailTo: ['to@example.com'], + emailSubject: 'subject', + emailText: 'text', + emailHtml: '

html

' +}; + +describe('sendWithResend', () => { + const originalEnv = { ...process.env }; + let sendMock: jest.Mock; + + beforeEach(() => { + delete process.env.RESEND_API_KEY; + sendMock = jest.fn().mockResolvedValue({ data: { id: 'email-1' } }); + MockedResend.mockImplementation(() => ({ emails: { send: sendMock } })); + jest.clearAllMocks(); + }); + + afterEach(() => { + process.env = { ...originalEnv }; + }); + + it('sends an email using the provider-sourced API key', async () => { + mockedFetchSecrets.mockResolvedValue({ RESEND_API_KEY: 'resend-key' }); + + await expect(sendWithResend(emailDto)).resolves.toBe(true); + + expect(mockedFetchSecrets).toHaveBeenCalledWith('secret/data/credebl_resend_api_key'); + expect(MockedResend).toHaveBeenCalledWith('resend-key'); + expect(sendMock).toHaveBeenCalledWith( + expect.objectContaining({ to: emailDto.emailTo, subject: emailDto.emailSubject }) + ); + }); + + it('falls back to the environment variable when no secret is returned', async () => { + mockedFetchSecrets.mockResolvedValue({}); + process.env.RESEND_API_KEY = 'env-key'; + + await expect(sendWithResend(emailDto)).resolves.toBe(true); + + expect(MockedResend).toHaveBeenCalledWith('env-key'); + }); + + it('returns false when the API key is missing', async () => { + mockedFetchSecrets.mockResolvedValue({}); + + await expect(sendWithResend(emailDto)).resolves.toBe(false); + expect(MockedResend).not.toHaveBeenCalled(); + }); + + it('returns false when the send response has no id', async () => { + mockedFetchSecrets.mockResolvedValue({ RESEND_API_KEY: 'resend-key' }); + sendMock.mockResolvedValue({ data: {} }); + + await expect(sendWithResend(emailDto)).resolves.toBe(false); + }); + + it('returns false when the send rejects', async () => { + mockedFetchSecrets.mockResolvedValue({ RESEND_API_KEY: 'resend-key' }); + sendMock.mockRejectedValue(new Error('rate limited')); + + await expect(sendWithResend(emailDto)).resolves.toBe(false); + }); +}); diff --git a/libs/common/src/send-grid-helper-file.spec.ts b/libs/common/src/send-grid-helper-file.spec.ts new file mode 100644 index 000000000..cbfb52789 --- /dev/null +++ b/libs/common/src/send-grid-helper-file.spec.ts @@ -0,0 +1,73 @@ +import { sendWithSendGrid } from './send-grid-helper-file'; +import { fetchSecrets } from './utils/secretLoader.util'; + +jest.mock('@sendgrid/mail', () => ({ + setApiKey: jest.fn(), + send: jest.fn() +})); +jest.mock('./utils/secretLoader.util', () => ({ + fetchSecrets: jest.fn() +})); + +import * as sendgrid from '@sendgrid/mail'; +import { EmailDto } from './dtos/email.dto'; + +const mockedSetApiKey = sendgrid.setApiKey as jest.Mock; +const mockedSend = sendgrid.send as jest.Mock; +const mockedFetchSecrets = fetchSecrets as jest.MockedFunction; + +const emailDto: EmailDto = { + emailFrom: 'from@credebl.id', + emailTo: ['to@example.com'], + emailSubject: 'subject', + emailText: 'text', + emailHtml: '

html

' +}; + +describe('sendWithSendGrid', () => { + const originalEnv = { ...process.env }; + + beforeEach(() => { + delete process.env.SENDGRID_API_KEY; + jest.clearAllMocks(); + }); + + afterEach(() => { + process.env = { ...originalEnv }; + }); + + it('sends an email using the provider-sourced API key', async () => { + mockedFetchSecrets.mockResolvedValue({ SENDGRID_API_KEY: 'sg-key' }); + mockedSend.mockResolvedValue(true); + + await expect(sendWithSendGrid(emailDto)).resolves.toBe(true); + + expect(mockedFetchSecrets).toHaveBeenCalledWith('secret/data/credebl_sendgrid_api_key'); + expect(mockedSetApiKey).toHaveBeenCalledWith('sg-key'); + expect(mockedSend).toHaveBeenCalledWith(expect.objectContaining({ to: emailDto.emailTo })); + }); + + it('falls back to the environment variable when no secret is returned', async () => { + mockedFetchSecrets.mockResolvedValue({}); + process.env.SENDGRID_API_KEY = 'env-key'; + mockedSend.mockResolvedValue(true); + + await expect(sendWithSendGrid(emailDto)).resolves.toBe(true); + + expect(mockedSetApiKey).toHaveBeenCalledWith('env-key'); + }); + + it('returns false when the API key is missing', async () => { + mockedFetchSecrets.mockResolvedValue({}); + + await expect(sendWithSendGrid(emailDto)).resolves.toBe(false); + expect(mockedSetApiKey).not.toHaveBeenCalled(); + }); + + it('returns false when send rejects', async () => { + mockedFetchSecrets.mockResolvedValue({ SENDGRID_API_KEY: 'sg-key' }); + mockedSend.mockRejectedValue(new Error('rate limited')); + + await expect(sendWithSendGrid(emailDto)).resolves.toBe(false); + }); +}); diff --git a/libs/common/src/smtp-helper-file.spec.ts b/libs/common/src/smtp-helper-file.spec.ts new file mode 100644 index 000000000..a89b948d9 --- /dev/null +++ b/libs/common/src/smtp-helper-file.spec.ts @@ -0,0 +1,122 @@ +import { sendWithSMTP } from './smtp-helper-file'; +import { fetchSecrets } from './utils/secretLoader.util'; + +jest.mock('nodemailer', () => ({ + createTransport: jest.fn() +})); +jest.mock('./utils/secretLoader.util', () => ({ + fetchSecrets: jest.fn() +})); + +import * as nodemailer from 'nodemailer'; +import { EmailDto } from './dtos/email.dto'; + +const mockedCreateTransport = nodemailer.createTransport as jest.Mock; +const mockedFetchSecrets = fetchSecrets as jest.MockedFunction; + +const emailDto: EmailDto = { + emailFrom: 'from@credebl.id', + emailTo: ['to@example.com'], + emailSubject: 'subject', + emailText: 'text', + emailHtml: '

html

' +}; + +describe('sendWithSMTP', () => { + const originalEnv = { ...process.env }; + let sendMail: jest.Mock; + + beforeEach(() => { + delete process.env.SMTP_HOST; + delete process.env.SMTP_PORT; + delete process.env.SMTP_USER; + delete process.env.SMTP_PASS; + sendMail = jest.fn().mockResolvedValue({ messageId: 'm1' }); + mockedCreateTransport.mockReturnValue({ sendMail }); + jest.clearAllMocks(); + }); + + afterEach(() => { + process.env = { ...originalEnv }; + }); + + it('sends an email using provider-sourced SMTP configuration', async () => { + mockedFetchSecrets.mockResolvedValue({ + SMTP_HOST: 'smtp.example.com', + SMTP_PORT: '587', + SMTP_USER: 'user', + SMTP_PASS: 'pass' + }); + + await expect(sendWithSMTP(emailDto)).resolves.toBe(true); + + expect(mockedFetchSecrets).toHaveBeenCalledWith('secret/data/credebl_smtp_config'); + expect(mockedCreateTransport).toHaveBeenCalledWith( + expect.objectContaining({ + host: 'smtp.example.com', + port: 587, + secure: false, + requireTLS: true, + auth: { user: 'user', pass: 'pass' } + }) + ); + expect(sendMail).toHaveBeenCalledWith(expect.objectContaining({ from: emailDto.emailFrom })); + }); + + it('sets secure mode for port 465', async () => { + mockedFetchSecrets.mockResolvedValue({ + SMTP_HOST: 'smtp.example.com', + SMTP_PORT: '465', + SMTP_USER: 'user', + SMTP_PASS: 'pass' + }); + + await sendWithSMTP(emailDto); + + expect(mockedCreateTransport).toHaveBeenCalledWith(expect.objectContaining({ secure: true, requireTLS: false })); + }); + + it('falls back to environment variables when no secrets are returned', async () => { + mockedFetchSecrets.mockResolvedValue({}); + process.env.SMTP_HOST = 'smtp.env.com'; + process.env.SMTP_PORT = '587'; + process.env.SMTP_USER = 'env-user'; + process.env.SMTP_PASS = 'env-pass'; + + await expect(sendWithSMTP(emailDto)).resolves.toBe(true); + + expect(mockedCreateTransport).toHaveBeenCalledWith( + expect.objectContaining({ host: 'smtp.env.com', auth: { user: 'env-user', pass: 'env-pass' } }) + ); + }); + + it('returns false when required SMTP configuration is missing', async () => { + mockedFetchSecrets.mockResolvedValue({}); + + await expect(sendWithSMTP(emailDto)).resolves.toBe(false); + expect(sendMail).not.toHaveBeenCalled(); + }); + + it('returns false when the SMTP port is invalid', async () => { + mockedFetchSecrets.mockResolvedValue({ + SMTP_HOST: 'smtp.example.com', + SMTP_PORT: 'not-a-number', + SMTP_USER: 'user', + SMTP_PASS: 'pass' + }); + + await expect(sendWithSMTP(emailDto)).resolves.toBe(false); + }); + + it('returns false when sendMail rejects', async () => { + mockedFetchSecrets.mockResolvedValue({ + SMTP_HOST: 'smtp.example.com', + SMTP_PORT: '587', + SMTP_USER: 'user', + SMTP_PASS: 'pass' + }); + sendMail.mockRejectedValue(new Error('connection refused')); + + await expect(sendWithSMTP(emailDto)).resolves.toBe(false); + }); +}); diff --git a/libs/common/src/utils/secretLoader.util.spec.ts b/libs/common/src/utils/secretLoader.util.spec.ts new file mode 100644 index 000000000..7a9e0293c --- /dev/null +++ b/libs/common/src/utils/secretLoader.util.spec.ts @@ -0,0 +1,86 @@ +import { fetchSecrets } from './secretLoader.util'; +import { getSecretProvider } from 'libs/config/src/secret-storage/secrets-loader'; + +jest.mock('libs/config/src/secret-storage/secrets-loader', () => ({ + getSecretProvider: jest.fn() +})); + +const mockedGetSecretProvider = getSecretProvider as jest.MockedFunction; + +describe('fetchSecrets', () => { + const originalEnv = { ...process.env }; + + beforeEach(() => { + delete process.env.ENABLE_BAO; + delete process.env.SECRETS_PROVIDER; + jest.clearAllMocks(); + }); + + afterEach(() => { + process.env = { ...originalEnv }; + }); + + it('returns an empty object when OpenBao is disabled', async () => { + process.env.ENABLE_BAO = 'false'; + + await expect(fetchSecrets('secret/data/credebl_smtp_config')).resolves.toEqual({}); + expect(mockedGetSecretProvider).not.toHaveBeenCalled(); + }); + + it('returns an empty object when SECRETS_PROVIDER is not set', async () => { + process.env.ENABLE_BAO = 'true'; + delete process.env.SECRETS_PROVIDER; + + await expect(fetchSecrets('secret/data/credebl_smtp_config_2')).resolves.toEqual({}); + expect(mockedGetSecretProvider).not.toHaveBeenCalled(); + }); + + it('returns an empty object when the provider is unsupported', async () => { + process.env.ENABLE_BAO = 'true'; + process.env.SECRETS_PROVIDER = 'consul'; + mockedGetSecretProvider.mockReturnValue(null); + + await expect(fetchSecrets('secret/data/credebl_smtp_config_3')).resolves.toEqual({}); + expect(mockedGetSecretProvider).toHaveBeenCalledWith('consul'); + }); + + it('fetches the secrets for the requested path via the active provider', async () => { + process.env.ENABLE_BAO = 'true'; + process.env.SECRETS_PROVIDER = 'openbao'; + const loadSecrets = jest.fn().mockResolvedValue({ SMTP_HOST: 'smtp.example.com', SMTP_PORT: '587' }); + mockedGetSecretProvider.mockReturnValue({ name: 'OpenBao', loadSecrets }); + + await expect(fetchSecrets('secret/data/credebl_smtp_config_4')).resolves.toEqual({ + SMTP_HOST: 'smtp.example.com', + SMTP_PORT: '587' + }); + + expect(loadSecrets).toHaveBeenCalledWith({ customPath: 'secret/data/credebl_smtp_config_4' }); + }); + + it('caches the result per path and does not re-invoke the provider', async () => { + process.env.ENABLE_BAO = 'true'; + process.env.SECRETS_PROVIDER = 'openbao'; + const loadSecrets = jest.fn().mockResolvedValue({ RESEND_API_KEY: 'k' }); + mockedGetSecretProvider.mockReturnValue({ name: 'OpenBao', loadSecrets }); + + await fetchSecrets('secret/data/credebl_resend_api_key'); + await fetchSecrets('secret/data/credebl_resend_api_key'); + + expect(loadSecrets).toHaveBeenCalledTimes(1); + }); + + it('uses an independent cache per secret path', async () => { + process.env.ENABLE_BAO = 'true'; + process.env.SECRETS_PROVIDER = 'openbao'; + const loadSecrets = jest.fn().mockResolvedValue({ SOME_KEY: 'v' }); + mockedGetSecretProvider.mockReturnValue({ name: 'OpenBao', loadSecrets }); + + await fetchSecrets('secret/data/path_a'); + await fetchSecrets('secret/data/path_b'); + + expect(loadSecrets).toHaveBeenCalledTimes(2); + expect(loadSecrets).toHaveBeenCalledWith({ customPath: 'secret/data/path_a' }); + expect(loadSecrets).toHaveBeenCalledWith({ customPath: 'secret/data/path_b' }); + }); +}); diff --git a/libs/common/src/utils/ttl-cache.util.spec.ts b/libs/common/src/utils/ttl-cache.util.spec.ts new file mode 100644 index 000000000..055155740 --- /dev/null +++ b/libs/common/src/utils/ttl-cache.util.spec.ts @@ -0,0 +1,68 @@ +import { TtlCache } from './ttl-cache.util'; + +describe('TtlCache', () => { + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + it('calls the fetch function only once and returns the cached value on subsequent gets', async () => { + const cache = new TtlCache(1000); + const fetchFn = jest.fn().mockResolvedValue(42); + + await expect(cache.get(fetchFn)).resolves.toBe(42); + await expect(cache.get(fetchFn)).resolves.toBe(42); + + expect(fetchFn).toHaveBeenCalledTimes(1); + }); + + it('re-fetches after the ttl has elapsed', async () => { + const cache = new TtlCache(1000); + const fetchFn = jest.fn().mockResolvedValue(1); + + await cache.get(fetchFn); + expect(fetchFn).toHaveBeenCalledTimes(1); + + jest.advanceTimersByTime(1001); + fetchFn.mockResolvedValue(2); + + await expect(cache.get(fetchFn)).resolves.toBe(2); + expect(fetchFn).toHaveBeenCalledTimes(2); + }); + + it('treats the ttl as a sliding window while the value is read frequently', async () => { + const cache = new TtlCache(1000); + const fetchFn = jest.fn().mockResolvedValue(1); + + await cache.get(fetchFn); + + for (let i = 0; 5 > i; i += 1) { + jest.advanceTimersByTime(900); + await expect(cache.get(fetchFn)).resolves.toBe(1); + } + + expect(fetchFn).toHaveBeenCalledTimes(1); + }); + + it('clears the cached value and cancels the pending timer on clear()', async () => { + const cache = new TtlCache(1000); + const fetchFn = jest.fn().mockResolvedValue(1); + + await cache.get(fetchFn); + cache.clear(); + fetchFn.mockResolvedValue(2); + + await expect(cache.get(fetchFn)).resolves.toBe(2); + expect(fetchFn).toHaveBeenCalledTimes(2); + }); + + it('propagates a rejection from the fetch function', async () => { + const cache = new TtlCache(1000); + const fetchFn = jest.fn().mockRejectedValue(new Error('boom')); + + await expect(cache.get(fetchFn)).rejects.toThrow('boom'); + }); +}); diff --git a/libs/config/src/secret-storage/openbao.provider.spec.ts b/libs/config/src/secret-storage/openbao.provider.spec.ts new file mode 100644 index 000000000..1bd8f0daa --- /dev/null +++ b/libs/config/src/secret-storage/openbao.provider.spec.ts @@ -0,0 +1,130 @@ +import { OpenBaoProvider } from './openbao.provider'; + +describe('OpenBaoProvider', () => { + const originalEnv = { ...process.env }; + + let provider: OpenBaoProvider; + let fetchMock: jest.Mock; + + function jsonResponse(body: unknown, ok = true, status = 200): Response { + return { + ok, + status, + json: jest.fn().mockResolvedValue(body) + } as unknown as Response; + } + + beforeEach(() => { + provider = new OpenBaoProvider(); + process.env.BAO_URL = 'http://bao:8200'; + process.env.BAO_SECRET_PATH = 'secret/data/credebl_resend_api_key'; + process.env.BAO_ROLE_ID = 'role-id'; + process.env.BAO_SECRET_ID = 'secret-id'; + + fetchMock = jest.fn(); + global.fetch = fetchMock as unknown as typeof fetch; + }); + + afterEach(() => { + process.env = { ...originalEnv }; + jest.restoreAllMocks(); + }); + + it('rejects when any required environment variable is missing', async () => { + delete process.env.BAO_ROLE_ID; + + await expect(provider.loadSecrets()).rejects.toThrow( + 'BAO_URL, BAO_SECRET_PATH, BAO_ROLE_ID, and BAO_SECRET_ID must be set.' + ); + expect(fetchMock).not.toHaveBeenCalled(); + }); + + it('authenticates with AppRole and fetches secrets from the configured path', async () => { + fetchMock + .mockResolvedValueOnce(jsonResponse({ auth: { client_token: 'client-token' } })) + .mockResolvedValueOnce(jsonResponse({ data: { data: { RESEND_API_KEY: 'resend-key' } } })); + + await expect(provider.loadSecrets()).resolves.toEqual({ RESEND_API_KEY: 'resend-key' }); + + expect(fetchMock).toHaveBeenNthCalledWith( + 1, + 'http://bao:8200/v1/auth/approle/login', + expect.objectContaining({ + method: 'POST', + body: JSON.stringify({ role_id: 'role-id', secret_id: 'secret-id' }) + }) + ); + expect(fetchMock).toHaveBeenNthCalledWith( + 2, + 'http://bao:8200/v1/secret/data/credebl_resend_api_key', + expect.objectContaining({ + method: 'GET', + headers: expect.objectContaining({ 'X-Vault-Token': 'client-token' }) + }) + ); + }); + + it('fetches from a custom path when one is supplied', async () => { + fetchMock + .mockResolvedValueOnce(jsonResponse({ auth: { client_token: 'client-token' } })) + .mockResolvedValueOnce(jsonResponse({ data: { data: { KEY: 'value' } } })); + + await provider.loadSecrets({ customPath: 'secret/data/credebl_smtp_config' }); + + expect(fetchMock).toHaveBeenNthCalledWith( + 2, + 'http://bao:8200/v1/secret/data/credebl_smtp_config', + expect.anything() + ); + }); + + it('rejects when the AppRole login response is not ok', async () => { + fetchMock.mockResolvedValueOnce(jsonResponse({}, false, 500)); + + await expect(provider.loadSecrets()).rejects.toThrow('Authentication failed: Status 500'); + }); + + it('rejects when the login response carries no client token', async () => { + fetchMock.mockResolvedValueOnce(jsonResponse({ auth: {} })); + + await expect(provider.loadSecrets()).rejects.toThrow('Failed to retrieve client token from OpenBao.'); + }); + + it('rejects when the secrets fetch response is not ok', async () => { + fetchMock + .mockResolvedValueOnce(jsonResponse({ auth: { client_token: 'client-token' } })) + .mockResolvedValueOnce(jsonResponse({}, false, 404)); + + await expect(provider.loadSecrets()).rejects.toThrow('Fetch failed: Status 404'); + }); + + it('rejects when the payload has an unexpected structure', async () => { + fetchMock + .mockResolvedValueOnce(jsonResponse({ auth: { client_token: 'client-token' } })) + .mockResolvedValueOnce(jsonResponse({ data: {} })); + + await expect(provider.loadSecrets()).rejects.toThrow('Unexpected secrets payload structure.'); + }); + + it('rejects when the payload data is an array', async () => { + fetchMock + .mockResolvedValueOnce(jsonResponse({ auth: { client_token: 'client-token' } })) + .mockResolvedValueOnce(jsonResponse({ data: { data: [] } })); + + await expect(provider.loadSecrets()).rejects.toThrow('Unexpected secrets payload structure.'); + }); + + it('rejects with a timeout error when the login request is aborted', async () => { + fetchMock.mockRejectedValueOnce(new DOMException('The operation was aborted.', 'AbortError')); + + await expect(provider.loadSecrets()).rejects.toThrow('OpenBao authentication request timed out.'); + }); + + it('rejects with a timeout error when the secrets fetch is aborted', async () => { + fetchMock + .mockResolvedValueOnce(jsonResponse({ auth: { client_token: 'client-token' } })) + .mockRejectedValueOnce(new DOMException('The operation was aborted.', 'AbortError')); + + await expect(provider.loadSecrets()).rejects.toThrow('OpenBao secrets fetch request timed out.'); + }); +}); diff --git a/libs/config/src/secret-storage/secrets-loader.spec.ts b/libs/config/src/secret-storage/secrets-loader.spec.ts new file mode 100644 index 000000000..47c091210 --- /dev/null +++ b/libs/config/src/secret-storage/secrets-loader.spec.ts @@ -0,0 +1,104 @@ +import { OpenBaoProvider } from './openbao.provider'; +import { getSecretProvider, loadConfigSecrets } from './secrets-loader'; + +jest.mock('./openbao.provider', () => ({ + OpenBaoProvider: jest.fn() +})); + +const MockedOpenBaoProvider = OpenBaoProvider as unknown as jest.Mock; + +describe('getSecretProvider', () => { + it('returns an OpenBaoProvider for the "openbao" value', () => { + expect(getSecretProvider('openbao')).toBeInstanceOf(OpenBaoProvider); + }); + + it('matches the provider type case-insensitively', () => { + expect(getSecretProvider('OpenBao')).toBeInstanceOf(OpenBaoProvider); + expect(getSecretProvider('OPENBAO')).toBeInstanceOf(OpenBaoProvider); + }); + + it('returns null for unsupported provider types', () => { + expect(getSecretProvider('consul')).toBeNull(); + expect(getSecretProvider('aws')).toBeNull(); + expect(getSecretProvider('')).toBeNull(); + }); +}); + +describe('loadConfigSecrets', () => { + const originalEnv = { ...process.env }; + let loadSecrets: jest.Mock; + + beforeEach(() => { + delete process.env.ENABLE_BAO; + delete process.env.SECRETS_PROVIDER; + loadSecrets = jest.fn(); + MockedOpenBaoProvider.mockImplementation(() => ({ + name: 'OpenBao', + loadSecrets + })); + jest.clearAllMocks(); + }); + + afterEach(() => { + process.env = { ...originalEnv }; + }); + + it('skips remote fetching when ENABLE_BAO is not "true"', async () => { + process.env.ENABLE_BAO = 'false'; + process.env.SECRETS_PROVIDER = 'openbao'; + + await loadConfigSecrets(); + + expect(loadSecrets).not.toHaveBeenCalled(); + }); + + it('skips remote fetching when SECRETS_PROVIDER is not set', async () => { + process.env.ENABLE_BAO = 'true'; + delete process.env.SECRETS_PROVIDER; + + await loadConfigSecrets(); + + expect(loadSecrets).not.toHaveBeenCalled(); + }); + + it('logs a warning and continues for an unsupported provider', async () => { + process.env.ENABLE_BAO = 'true'; + process.env.SECRETS_PROVIDER = 'consul'; + + await expect(loadConfigSecrets()).resolves.toBeUndefined(); + + expect(loadSecrets).not.toHaveBeenCalled(); + }); + + it('injects fetched secrets into process.env', async () => { + process.env.ENABLE_BAO = 'true'; + process.env.SECRETS_PROVIDER = 'openbao'; + loadSecrets.mockResolvedValue({ SMTP_HOST: 'smtp.example.com', SMTP_PASS: 'secret' }); + + await loadConfigSecrets(); + + expect(process.env.SMTP_HOST).toBe('smtp.example.com'); + expect(process.env.SMTP_PASS).toBe('secret'); + }); + + it('skips forbidden prototype-pollution keys during injection', async () => { + process.env.ENABLE_BAO = 'true'; + process.env.SECRETS_PROVIDER = 'openbao'; + loadSecrets.mockResolvedValue({ __proto__: { polluted: true }, constructor: 'x', SAFE: 'y' }); + + await loadConfigSecrets(); + + expect((process.env as Record & { polluted?: boolean }).polluted).toBeUndefined(); + expect(process.env.SAFE).toBe('y'); + }); + + it('logs a critical boot failure and resolves when the provider rejects', async () => { + process.env.ENABLE_BAO = 'true'; + process.env.SECRETS_PROVIDER = 'openbao'; + loadSecrets.mockRejectedValue(new Error('BAO is down')); + + await expect(loadConfigSecrets()).resolves.toBeUndefined(); + + expect(process.env.SMTP_HOST).toBeUndefined(); + }); +}); diff --git a/libs/storage/src/providers/s3-storage.provider.spec.ts b/libs/storage/src/providers/s3-storage.provider.spec.ts new file mode 100644 index 000000000..5ff6c4c6c --- /dev/null +++ b/libs/storage/src/providers/s3-storage.provider.spec.ts @@ -0,0 +1,108 @@ +import { S3StorageService } from './s3-storage.provider'; +import { fetchSecrets } from '@credebl/common/utils/secretLoader.util'; + +jest.mock('aws-sdk', () => ({ + S3: jest.fn() +})); +jest.mock('@credebl/common/utils/secretLoader.util', () => ({ + fetchSecrets: jest.fn() +})); + +import { S3 } from 'aws-sdk'; + +const MockedS3 = S3 as unknown as jest.Mock; +const mockedFetchSecrets = fetchSecrets as jest.MockedFunction; + +type ClientGetter = () => Promise; + +describe('S3StorageService', () => { + const originalEnv = { ...process.env }; + let service: S3StorageService; + + beforeEach(() => { + delete process.env.AWS_REGION; + delete process.env.AWS_ACCESS_KEY; + delete process.env.AWS_SECRET_KEY; + delete process.env.AWS_PUBLIC_REGION; + delete process.env.AWS_PUBLIC_ACCESS_KEY; + delete process.env.AWS_PUBLIC_SECRET_KEY; + delete process.env.AWS_S3_STOREOBJECT_REGION; + delete process.env.AWS_S3_STOREOBJECT_ACCESS_KEY; + delete process.env.AWS_S3_STOREOBJECT_SECRET_KEY; + service = new S3StorageService(); + jest.clearAllMocks(); + }); + + afterEach(() => { + process.env = { ...originalEnv }; + }); + + it('builds the internal S3 client from provider-sourced credentials', async () => { + mockedFetchSecrets.mockResolvedValue({ AWS_ACCESS_KEY: 'secret-key', AWS_SECRET_KEY: 'secret-secret' }); + process.env.AWS_REGION = 'us-east-1'; + + await (service as unknown as { getS3Client: ClientGetter }).getS3Client(); + + expect(mockedFetchSecrets).toHaveBeenCalledWith('secret/data/credebl_aws_keys'); + expect(MockedS3).toHaveBeenCalledWith({ + accessKeyId: 'secret-key', + secretAccessKey: 'secret-secret', + region: 'us-east-1' + }); + }); + + it('falls back to environment variables for the internal client', async () => { + mockedFetchSecrets.mockResolvedValue({}); + process.env.AWS_REGION = 'us-east-1'; + process.env.AWS_ACCESS_KEY = 'env-key'; + process.env.AWS_SECRET_KEY = 'env-secret'; + + await (service as unknown as { getS3Client: ClientGetter }).getS3Client(); + + expect(MockedS3).toHaveBeenCalledWith({ + accessKeyId: 'env-key', + secretAccessKey: 'env-secret', + region: 'us-east-1' + }); + }); + + it('builds the public S3 client from the public credential pair', async () => { + mockedFetchSecrets.mockResolvedValue({ + AWS_PUBLIC_ACCESS_KEY: 'public-key', + AWS_PUBLIC_SECRET_KEY: 'public-secret' + }); + process.env.AWS_PUBLIC_REGION = 'ap-south-1'; + + await (service as unknown as { getPublicS3Client: ClientGetter }).getPublicS3Client(); + + expect(MockedS3).toHaveBeenCalledWith({ + accessKeyId: 'public-key', + secretAccessKey: 'public-secret', + region: 'ap-south-1' + }); + }); + + it('builds the store-object S3 client from the store-object credential pair', async () => { + mockedFetchSecrets.mockResolvedValue({ + AWS_S3_STOREOBJECT_ACCESS_KEY: 'store-key', + AWS_S3_STOREOBJECT_SECRET_KEY: 'store-secret' + }); + process.env.AWS_S3_STOREOBJECT_REGION = 'eu-west-1'; + + await (service as unknown as { getStoreObjectS3Client: ClientGetter }).getStoreObjectS3Client(); + + expect(MockedS3).toHaveBeenCalledWith({ + accessKeyId: 'store-key', + secretAccessKey: 'store-secret', + region: 'eu-west-1' + }); + }); + + it('builds the public URL from the bucket and public region', () => { + process.env.AWS_PUBLIC_REGION = 'ap-south-1'; + + expect(service.getPublicUrl('my-bucket', 'logos/logo.png')).toBe( + 'https://my-bucket.s3.ap-south-1.amazonaws.com/logos/logo.png' + ); + }); +}); diff --git a/openbao-init.sh b/openbao-init.sh new file mode 100755 index 000000000..3e823bd77 --- /dev/null +++ b/openbao-init.sh @@ -0,0 +1,164 @@ +#!/usr/bin/env bash +# +# Bootstrap OpenBao (docker-compose.openbao.yml) for the CREDEBL platform demo. +# +# Provisions, idempotently: +# - OpenBao initialization + unseal (if not already initialized) +# - KV v2 secrets engine at secret/ +# - AppRole auth method +# - a "credebl" AppRole role + policy scoped to the credebl_* secret paths +# - the platform's secret data, sourced from the matching environment variables +# +# Usage: +# ./openbao-init.sh +# +# On the first run the script prints the unseal key, root token, role_id and +# secret_id. Copy BAO_ROLE_ID and BAO_SECRET_ID into .env.demo. +# On later runs it is idempotent: it re-prints role_id and re-uses an existing +# secret_id instead of generating a new one. +# +# Optional inputs (read from the environment): +# BAO_UNSEAL_KEY - unseal key from a previous run (needed if already +# initialized but sealed) +# BAO_ROOT_TOKEN - root token from a previous run (avoids re-printing) +# CONTAINER_NAME - docker container name (default: openbao) +# RESEND_API_KEY, SENDGRID_API_KEY, SMTP_HOST/PORT/USER/PASS, +# AWS_* - secret values to store; only the ones that are set +# (non-empty) are written +# +# NOTE: for demo only. The unseal key and root token are secrets; do not commit +# them to the repository. + +set -euo pipefail + +CONTAINER_NAME="${CONTAINER_NAME:-openbao}" +BAO_ADDR_IN_CONTAINER="${BAO_ADDR_IN_CONTAINER:-http://127.0.0.1:8200}" + +run_bao() { + docker exec -e BAO_TOKEN="${BAO_TOKEN:-}" "$CONTAINER_NAME" bao "$@" +} + +echo_step() { + printf '\n==> %s\n' "$1" +} + +echo "Connecting to OpenBao in container '${CONTAINER_NAME}' (${BAO_ADDR_IN_CONTAINER})..." + +status="$(docker exec "$CONTAINER_NAME" bao status 2>&1 || true)" +initialized="$(grep -E '^Initialized' <<<"$status" | awk '{print $2}')" +sealed="$(grep -E '^Sealed' <<<"$status" | awk '{print $2}')" + +if [[ "$initialized" != "true" ]]; then + echo_step "OpenBao is not initialized. Running 'bao operator init' (1 unseal key)..." + init_out="$(docker exec "$CONTAINER_NAME" bao operator init -key-shares=1 -key-threshold=1)" + BAO_UNSEAL_KEY="${BAO_UNSEAL_KEY:-$(grep 'Unseal Key 1:' <<<"$init_out" | awk '{print $NF}')}" + BAO_ROOT_TOKEN="${BAO_ROOT_TOKEN:-$(grep 'Initial Root Token:' <<<"$init_out" | awk '{print $NF}')}" +else + BAO_UNSEAL_KEY="${BAO_UNSEAL_KEY:-}" + BAO_ROOT_TOKEN="${BAO_ROOT_TOKEN:-}" +fi + +if [[ "$sealed" == "true" ]]; then + if [[ -z "$BAO_UNSEAL_KEY" ]]; then + echo "ERROR: OpenBao is sealed. Re-run with BAO_UNSEAL_KEY= (from the first run)." >&2 + exit 1 + fi + echo_step "Unsealing OpenBao..." + docker exec "$CONTAINER_NAME" bao operator unseal "$BAO_UNSEAL_KEY" >/dev/null +fi + +if [[ -z "$BAO_ROOT_TOKEN" ]]; then + echo "ERROR: no root token available. Re-run with BAO_ROOT_TOKEN= (from the first run)." >&2 + exit 1 +fi +BAO_TOKEN="$BAO_ROOT_TOKEN" + +if ! run_bao secrets list 2>/dev/null | grep -q '^secret/'; then + echo_step "Enabling KV v2 secrets engine at secret/..." + run_bao secrets enable -path=secret kv-v2 +fi + +if ! run_bao auth list 2>/dev/null | grep -q '^approle/'; then + echo_step "Enabling AppRole auth method..." + run_bao auth enable approle +fi + +if ! run_bao policy list 2>/dev/null | grep -qx 'credebl-secrets'; then + echo_step "Writing 'credebl-secrets' policy..." + docker exec -i -e BAO_TOKEN="$BAO_TOKEN" "$CONTAINER_NAME" bao policy write credebl-secrets - <<'POLICY' +path "secret/data/credebl_*" { + capabilities = ["read"] +} +path "secret/metadata/credebl_*" { + capabilities = ["read", "list"] +} +POLICY +fi + +role_exists="false" +if run_bao read auth/approle/role/credebl >/dev/null 2>&1; then + role_exists="true" +fi + +if [[ "$role_exists" == "false" ]]; then + echo_step "Creating AppRole role 'credebl' (non-expiring secret_id, read-only policy)..." + run_bao write auth/approle/role/credebl \ + token_policies=credebl-secrets \ + secret_id_ttl=0 \ + token_ttl=30m \ + token_max_ttl=1h +fi + +role_id="$(run_bao read -field=role_id auth/approle/role/credebl/role-id)" + +if [[ "$role_exists" == "false" ]]; then + echo_step "Generating secret_id for the 'credebl' role..." + secret_id="$(run_bao write -f -field=secret_id auth/approle/role/credebl/secret-id)" +else + secret_id="${BAO_SECRET_ID:-}" +fi + +echo_step "Storing platform secrets..." + +kv_put() { + local path="$1" + shift + if [[ "$#" -gt 0 ]]; then + docker exec -e BAO_TOKEN="$BAO_TOKEN" "$CONTAINER_NAME" bao kv put "$path" "$@" >/dev/null + echo " wrote ${path}" + else + echo " skipped ${path} (no matching environment variables set)" + fi +} + +kv_put secret/credebl_resend_api_key \ + $( [[ -n "${RESEND_API_KEY:-}" ]] && printf 'RESEND_API_KEY=%s' "$RESEND_API_KEY" ) + +kv_put secret/credebl_sendgrid_api_key \ + $( [[ -n "${SENDGRID_API_KEY:-}" ]] && printf 'SENDGRID_API_KEY=%s' "$SENDGRID_API_KEY" ) + +kv_put secret/credebl_smtp_config \ + $( [[ -n "${SMTP_HOST:-}" ]] && printf 'SMTP_HOST=%s' "$SMTP_HOST" ) \ + $( [[ -n "${SMTP_PORT:-}" ]] && printf 'SMTP_PORT=%s' "$SMTP_PORT" ) \ + $( [[ -n "${SMTP_USER:-}" ]] && printf 'SMTP_USER=%s' "$SMTP_USER" ) \ + $( [[ -n "${SMTP_PASS:-}" ]] && printf 'SMTP_PASS=%s' "$SMTP_PASS" ) + +kv_put secret/credebl_aws_keys \ + $( [[ -n "${AWS_ACCESS_KEY:-}" ]] && printf 'AWS_ACCESS_KEY=%s' "$AWS_ACCESS_KEY" ) \ + $( [[ -n "${AWS_SECRET_KEY:-}" ]] && printf 'AWS_SECRET_KEY=%s' "$AWS_SECRET_KEY" ) \ + $( [[ -n "${AWS_PUBLIC_ACCESS_KEY:-}" ]] && printf 'AWS_PUBLIC_ACCESS_KEY=%s' "$AWS_PUBLIC_ACCESS_KEY" ) \ + $( [[ -n "${AWS_PUBLIC_SECRET_KEY:-}" ]] && printf 'AWS_PUBLIC_SECRET_KEY=%s' "$AWS_PUBLIC_SECRET_KEY" ) \ + $( [[ -n "${AWS_S3_STOREOBJECT_ACCESS_KEY:-}" ]] && printf 'AWS_S3_STOREOBJECT_ACCESS_KEY=%s' "$AWS_S3_STOREOBJECT_ACCESS_KEY" ) \ + $( [[ -n "${AWS_S3_STOREOBJECT_SECRET_KEY:-}" ]] && printf 'AWS_S3_STOREOBJECT_SECRET_KEY=%s' "$AWS_S3_STOREOBJECT_SECRET_KEY" ) + +echo_step "Done. Add the following to .env.demo:" +cat <)` method returning a flat `Record` of key/value secrets. +- **Must** expose a `getSecretProvider(providerType)` factory that returns a provider for the value `openbao` (case-insensitive) and `null` for any other value. +- **Will** support future providers (e.g. AWS Secrets Manager) via the same factory without call-site changes. + +#### Scenario: unknown provider value + +- **When** `getSecretProvider` is called with a value other than `openbao` +- **Then** it returns `null`. + +#### Scenario: case-insensitive provider lookup + +- **When** `getSecretProvider` is called with `OpenBao` or `OPENBAO` +- **Then** it returns an `OpenBaoProvider` instance. + +### openbao-provider-configuration + +The OpenBao provider reads its connection settings from the process environment. + +- **Must** read `BAO_URL`, `BAO_SECRET_PATH`, `BAO_ROLE_ID`, and `BAO_SECRET_ID` from `process.env`. +- **Must** throw an error listing all required variables when any of them is missing or empty. +- **Must** allow the secret path to be overridden per call via a `customPath` option passed to `loadSecrets`. +- **Must** enforce a request timeout of `CommonConstants.OPENBAO_REQUEST_TIMEOUT` (10 seconds) on every OpenBao HTTP request. +- **Will** treat a request aborted by the timeout as a failure and throw a `...timed out` error. + +#### Scenario: missing environment configuration + +- **When** any of `BAO_URL`, `BAO_SECRET_PATH`, `BAO_ROLE_ID`, `BAO_SECRET_ID` is unset +- **Then** `loadSecrets` rejects with an error naming the missing variables. + +#### Scenario: custom secret path + +- **When** `loadSecrets` is called with `{ customPath: 'secret/data/custom' }` +- **Then** the provider fetches `BAO_URL/v1/secret/data/custom` instead of the `BAO_SECRET_PATH` value. + +### openbao-approle-authentication + +The provider authenticates to OpenBao using the AppRole auth method and exchanges role credentials for a client token. + +- **Must** POST `{ role_id, secret_id }` as a JSON body to `{BAO_URL}/v1/auth/approle/login`. +- **Must** throw an `Authentication failed: Status ` error when the login response is not ok. +- **Must** throw a `Failed to retrieve client token from OpenBao.` error when the login succeeds but the response contains no `auth.client_token`. +- **Must** send the obtained client token on subsequent secret requests via the `X-Vault-Token` header. +- **Will** reject with `OpenBao authentication request timed out.` when the login request is aborted by the timeout. + +#### Scenario: rejected AppRole credentials + +- **When** OpenBao answers the login request with a non-2xx status +- **Then** the provider throws `Authentication failed: Status `. + +#### Scenario: login response without a token + +- **When** the login response is ok but carries no `auth.client_token` +- **Then** the provider throws `Failed to retrieve client token from OpenBao.`. + +### openbao-kv-secret-retrieval + +The provider reads a KV secrets engine path using the authenticated token. + +- **Must** GET `{BAO_URL}/v1/{secretPath}` with the `X-Vault-Token` header. +- **Must** throw `Fetch failed: Status ` when the read response is not ok. +- **Must** read the secret data from the KV v2 payload shape `result.data.data`. +- **Must** throw `Unexpected secrets payload structure.` when `data.data` is missing, not an object, or an array. +- **Must** return the secret map as a flat `Record`. +- **Will** reject with `OpenBao secrets fetch request timed out.` when the read request is aborted by the timeout. + +#### Scenario: unexpected payload shape + +- **When** OpenBao returns a payload without a `data.data` object +- **Then** the provider throws `Unexpected secrets payload structure.`. + +#### Scenario: successful secret retrieval + +- **When** authentication succeeds and the KV path returns `{ data: { data: { RESEND_API_KEY: 'x' } } }` +- **Then** `loadSecrets` resolves to `{ RESEND_API_KEY: 'x' }`. + +### startup-secret-injection + +Every microservice loads remote secrets into `process.env` before its NATS microservice is created and starts listening. + +- **Must** gate startup injection on `ENABLE_BAO` being exactly `true` after trimming and lowercasing. +- **Must** skip injection without error when `ENABLE_BAO` is not `true`. +- **Must** skip injection without error when `SECRETS_PROVIDER` is not set. +- **Must** log a warning and continue with local environment variables when `SECRETS_PROVIDER` names an unsupported provider. +- **Must** inject every fetched secret into `process.env` as an enumerable, configurable, writable string property. +- **Must** skip keys named `__proto__`, `constructor`, or `prototype` during injection. +- **Must** log a `Critical Lifecycle Boot Failure` error and continue startup when the provider throws, rather than crashing the bootstrap. +- **Must** run `await loadConfigSecrets()` before `NestFactory.createMicroservice(...)` in every `apps/*/src/main.ts` bootstrap. +- **Will** be invoked through the `@credebl/config` package root export. + +#### Scenario: secrets management disabled + +- **When** `ENABLE_BAO` is unset, `false`, or `FALSE` +- **Then** `loadConfigSecrets` returns without reading from any provider. + +#### Scenario: unsupported provider value + +- **When** `SECRETS_PROVIDER=consul` +- **Then** `loadConfigSecrets` logs a warning and returns without throwing. + +#### Scenario: malicious secret keys are filtered + +- **When** the provider returns a secret whose key is `__proto__` +- **Then** that entry is skipped and `process.env` is not polluted. + +#### Scenario: provider failure does not block boot + +- **When** the provider rejects while loading secrets +- **Then** the error is logged and `loadConfigSecrets` resolves instead of rejecting. + +### runtime-secret-loading-with-cache + +Email and storage integrations fetch their credential sets on demand from the configured provider, cached per secret path. + +- **Must** expose a `fetchSecrets(secretPath)` helper in `libs/common/src/utils/secretLoader.util.ts`. +- **Must** return an empty object without calling the provider when `ENABLE_BAO` is not `true`. +- **Must** return an empty object when `SECRETS_PROVIDER` is unset or names an unsupported provider. +- **Must** call `provider.loadSecrets({ customPath: secretPath })` when a provider is active. +- **Must** cache the result per secret path for a TTL of 10 minutes. +- **Must** share the cache module-wide across all consumers (email + storage) for a given path. + +#### Scenario: runtime fetch for a specific path + +- **When** `fetchSecrets('secret/data/credebl_smtp_config')` is called with OpenBao enabled +- **Then** the provider is invoked with `customPath` set to that path and its result is returned. + +#### Scenario: subsequent reads are served from cache + +- **When** `fetchSecrets` is called twice with the same path while the entry is unexpired +- **Then** the provider is only invoked once. + +### ttl-cache-semantics + +The TTL cache preserves a fetched value for a duration and re-fetches once it lapses. + +- **Must** invoke the fetch function on first read and store the result. +- **Must** return the stored value on subsequent reads without re-invoking the fetch function. +- **Must** treat the TTL as sliding: any read refreshes the expiry timer. +- **Must** clear the stored value once `ttlMs` of inactivity elapses. +- **Must** provide a `clear()` method that drops the stored value and cancels the pending expiry timer. +- **Will** schedule expiry so that an idle process is not kept alive by the pending timer. + +#### Scenario: expiry triggers a re-fetch + +- **When** a value is read again after `ttlMs` of inactivity has elapsed +- **Then** the fetch function runs again and the fresh value is returned. + +#### Scenario: sliding window keeps a hot entry alive + +- **When** a value is read repeatedly at intervals shorter than `ttlMs` +- **Then** the fetch function is never re-invoked and the same value is returned. + +### email-credential-sourcing + +Email providers resolve their credentials at runtime, preferring provider-sourced secrets over environment variables. + +- **Must** resolve the Resend API key from `secret/data/credebl_resend_api_key` (`RESEND_API_KEY`) or fall back to `process.env.RESEND_API_KEY`. +- **Must** resolve the SendGrid API key from `secret/data/credebl_sendgrid_api_key` (`SENDGRID_API_KEY`) or fall back to `process.env.SENDGRID_API_KEY`. +- **Must** resolve SMTP settings (`SMTP_HOST`, `SMTP_PORT`, `SMTP_USER`, `SMTP_PASS`) from `secret/data/credebl_smtp_config` or fall back to the matching environment variables. +- **Must** treat a missing/invalid SMTP port as a failure. +- **Must** return `false` and log the error when a required credential is missing or a send fails, rather than throwing to the caller. +- **Must** configure the SMTP transport with `secure: true` for port 465 and `requireTLS: true` for port 587. + +#### Scenario: missing email credential + +- **When** the provider returns no Resend key and `RESEND_API_KEY` is unset +- **Then** `sendWithResend` logs an error and returns `false`. + +#### Scenario: invalid SMTP port + +- **When** `SMTP_PORT` is not a positive integer +- **Then** `sendWithSMTP` logs an error and returns `false`. + +#### Scenario: provider secrets take precedence + +- **When** the provider returns `RESEND_API_KEY` and the same env var is set to a different value +- **Then** the client is constructed with the provider-sourced key. + +### storage-credential-sourcing + +S3 storage clients resolve their AWS credentials at runtime from the shared `secret/data/credebl_aws_keys` path. + +- **Must** resolve internal S3 credentials from `AWS_ACCESS_KEY`/`AWS_SECRET_KEY` or fall back to the matching env vars. +- **Must** resolve public (logo/URL) S3 credentials from `AWS_PUBLIC_ACCESS_KEY`/`AWS_PUBLIC_SECRET_KEY` or fall back to the matching env vars. +- **Must** resolve store-object S3 credentials from `AWS_S3_STOREOBJECT_ACCESS_KEY`/`AWS_S3_STOREOBJECT_SECRET_KEY` or fall back to the matching env vars. +- **Must** read the region for each client from `AWS_REGION`, `AWS_PUBLIC_REGION`, and `AWS_S3_STOREOBJECT_REGION` respectively. +- **Will** leave the RustFS/local storage providers reading credentials exclusively from the environment. + +#### Scenario: provider-sourced AWS keys + +- **When** OpenBao returns `AWS_ACCESS_KEY`/`AWS_SECRET_KEY` at the AWS key path +- **Then** the internal S3 client is constructed with those values. + +### deployment-artifacts + +The repository ships demo configuration and container artifacts for running OpenBao. + +- **Must** document the `ENABLE_BAO` and `SECRETS_PROVIDER` gating variables and the `BAO_URL`, `BAO_SECRET_PATH`, `BAO_ROLE_ID`, and `BAO_SECRET_ID` placeholders in `.env.demo`. +- **Must** provide a `docker-compose.openbao.yml` that runs an `openbao/openbao` server exposing port 8200 with file-backed storage persisted on a named volume. +- **Must** provide an HCL server configuration (`config.hcl`) declaring file storage at `/openbao/file`, a TCP listener on `0.0.0.0:8200` with TLS disabled, and the UI enabled. +- **Must** provide a `openbao-init.sh` script that idempotently initializes and unseals the server, enables KV v2 at `secret/`, enables AppRole auth, creates the `credebl` role scoped to the `credebl_*` paths, generates the `role_id`/`secret_id`, and stores the secret data from the matching environment variables. +- **Will** require the operator to run `openbao-init.sh` once and copy the printed `BAO_ROLE_ID`/`BAO_SECRET_ID` into `.env.demo` before the platform can use the provider. + +#### Scenario: demo environment placeholders + +- **When** an operator follows the demo setup +- **Then** `.env.demo` exposes the two gating variables and the four `BAO_*` variables to configure the provider. + +#### Scenario: local OpenBao server + +- **When** `docker compose -f docker-compose.openbao.yml up` is run +- **Then** an OpenBao server is available on `127.0.0.1:8200` and its data survives restarts via the named volume. + +#### Scenario: one-shot provisioning via the init script + +- **When** `./openbao-init.sh` is run against a freshly started server +- **Then** the server is initialized and unsealed, KV v2 and AppRole are enabled, the `credebl` role exists, and the script prints `BAO_ROLE_ID`/`BAO_SECRET_ID` ready for `.env.demo`. +- **And** re-running the script does not re-initialize the server or regenerate the `role_id`/`secret_id`. diff --git a/package.json b/package.json index 79f2dbe33..348906944 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "express-useragent": "^1.0.15", "firebase-admin": "^13.6.1", "fs": "0.0.1-security", - "generate-password": "^1.7.1", + "generate-password": "^1.7.1", "helmet": "^8.1.0", "html-pdf": "^3.0.1", "html-to-image": "^1.11.13", @@ -201,11 +201,11 @@ "^@credebl/user-org-roles(|/.*)$": "/libs/user-org-roles/src/$1", "^y/user-activity(|/.*)$": "/libs/user-activity/src/$1", "^@app/supabase(|/.*)$": "/libs/supabase/src/$1", - "^credebl/utility(|/.*)$": "/libs/utility/src/$1", "^@credebl/config(|/.*)$": "/libs/config/src/$1", "^@credebl/context(|/.*)$": "/libs/context/src/$1", - "^@credebl/logger(|/.*)$": "/libs/logger/src/$1" + "^@credebl/logger(|/.*)$": "/libs/logger/src/$1", + "^libs/(.*)$": "/libs/$1" } }, "packageManager": "pnpm@9.15.3+sha512.1f79bc245a66eb0b07c5d4d83131240774642caaa86ef7d0434ab47c0d16f66b04e21e0c086eb61e62c77efc4d7f7ec071afad3796af64892fae66509173893a", From cf3f979d6904fbb6d66835a40468b1e4112f99d8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Aug 2026 05:06:49 +0000 Subject: [PATCH 30/57] build: bump nanoid from 4.0.2 to 5.1.16 Bumps [nanoid](https://github.com/ai/nanoid) from 4.0.2 to 5.1.16. - [Release notes](https://github.com/ai/nanoid/releases) - [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md) - [Commits](https://github.com/ai/nanoid/compare/4.0.2...5.1.16) --- updated-dependencies: - dependency-name: nanoid dependency-version: 5.1.16 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- package.json | 2 +- pnpm-lock.yaml | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 697d33844..42930f5c2 100644 --- a/package.json +++ b/package.json @@ -91,7 +91,7 @@ "moment": "^2.30.1", "morgan": "^1.10.1", "multer": "^2.0.2", - "nanoid": "^4.0.2", + "nanoid": "^5.1.16", "nats": "^2.29.3", "nestjs-cls": "catalog:", "nestjs-rate-limiter": "catalog:", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c15e52875..01e538fdb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -279,8 +279,8 @@ importers: specifier: ^2.0.2 version: 2.1.1 nanoid: - specifier: ^4.0.2 - version: 4.0.2 + specifier: ^5.1.16 + version: 5.1.16 nats: specifier: ^2.29.3 version: 2.29.3 @@ -3106,6 +3106,7 @@ packages: cron-parser@4.9.0: resolution: {integrity: sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==} engines: {node: '>=12.0.0'} + deprecated: v4 is no longer maintained, upgrade to v5 cron@4.4.0: resolution: {integrity: sha512-fkdfq+b+AHI4cKdhZlppHveI/mgz2qpiYxcm+t5E5TsxX7QrLS1VE0+7GENEk9z0EeGPcpSciGv6ez24duWhwQ==} @@ -3120,6 +3121,7 @@ packages: crypto-js@4.2.0: resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==} + deprecated: Active development of CryptoJS has been discontinued. This library is no longer maintained. crypto-random-string@5.0.0: resolution: {integrity: sha512-KWjTXWwxFd6a94m5CdRGW/t82Tr8DoBc9dNnPCAbFI1EBweN6v1tv8y4Y1m7ndkp/nkIBRxUxAzpaBnR2k3bcQ==} @@ -4920,9 +4922,9 @@ packages: resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} engines: {node: ^18.17.0 || >=20.5.0} - nanoid@4.0.2: - resolution: {integrity: sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==} - engines: {node: ^14 || ^16 || >=18} + nanoid@5.1.16: + resolution: {integrity: sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ==} + engines: {node: ^18 || >=20} hasBin: true nats@2.29.3: @@ -12301,7 +12303,7 @@ snapshots: mute-stream@2.0.0: {} - nanoid@4.0.2: {} + nanoid@5.1.16: {} nats@2.29.3: dependencies: From f026ceba5faa1bcd63ff4ce5cc4b0e2da283b9aa Mon Sep 17 00:00:00 2001 From: Ajay Jadhav Date: Thu, 13 Aug 2026 18:12:59 +0530 Subject: [PATCH 31/57] fix: address SonarCloud code smells - assign positional parameter to local var in openbao-init.sh (shelldre:S7679) - replace switch with if in getSecretProvider (typescript:S1301) Signed-off-by: Ajay Jadhav --- libs/config/src/secret-storage/secrets-loader.ts | 10 ++++------ openbao-init.sh | 3 ++- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/libs/config/src/secret-storage/secrets-loader.ts b/libs/config/src/secret-storage/secrets-loader.ts index 8c7d6c23b..1d9c546ad 100644 --- a/libs/config/src/secret-storage/secrets-loader.ts +++ b/libs/config/src/secret-storage/secrets-loader.ts @@ -9,13 +9,11 @@ const logger = new Logger('SecretsLoader'); * Factory function to get the requested secret provider */ export function getSecretProvider(providerType: string): SecretProvider | null { - switch (providerType.toLowerCase()) { - case 'openbao': - return new OpenBaoProvider(); - - default: - return null; + if ('openbao' === providerType.toLowerCase()) { + return new OpenBaoProvider(); } + + return null; } export async function loadConfigSecrets(): Promise { diff --git a/openbao-init.sh b/openbao-init.sh index 3e823bd77..7ab376dad 100755 --- a/openbao-init.sh +++ b/openbao-init.sh @@ -39,7 +39,8 @@ run_bao() { } echo_step() { - printf '\n==> %s\n' "$1" + local message="$1" + printf '\n==> %s\n' "$message" } echo "Connecting to OpenBao in container '${CONTAINER_NAME}' (${BAO_ADDR_IN_CONTAINER})..." From 860d462dc69eec8ea1023daec0855c47ae47bbde Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 14 Aug 2026 06:23:07 +0000 Subject: [PATCH 32/57] build: bump qs from 6.15.1 to 6.15.2 Bumps [qs](https://github.com/ljharb/qs) from 6.15.1 to 6.15.2. - [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md) - [Commits](https://github.com/ljharb/qs/compare/v6.15.1...v6.15.2) --- updated-dependencies: - dependency-name: qs dependency-version: 6.15.2 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- package.json | 2 +- pnpm-lock.yaml | 41 ++++++++++------------------------------- 2 files changed, 11 insertions(+), 32 deletions(-) diff --git a/package.json b/package.json index 47ea3c9c0..7104e0451 100644 --- a/package.json +++ b/package.json @@ -110,7 +110,7 @@ "pg": "^8.18.0", "puppeteer": "^21.11.0", "qrcode": "^1.5.4", - "qs": "^6.15.0", + "qs": "^6.15.2", "reflect-metadata": "^0.1.14", "resend": "^6.9.2", "rimraf": "^3.0.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7b866d285..0e9df95ba 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -339,8 +339,8 @@ importers: specifier: ^1.5.4 version: 1.5.4 qs: - specifier: ^6.15.0 - version: 6.15.1 + specifier: ^6.15.2 + version: 6.15.2 reflect-metadata: specifier: ^0.1.14 version: 0.1.14 @@ -5476,12 +5476,8 @@ packages: engines: {node: '>=10.13.0'} hasBin: true - qs@6.15.1: - resolution: {integrity: sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==} - engines: {node: '>=0.6'} - - qs@6.15.3: - resolution: {integrity: sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==} + qs@6.15.2: + resolution: {integrity: sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==} engines: {node: '>=0.6'} qs@6.5.5: @@ -5761,10 +5757,6 @@ packages: resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} engines: {node: '>= 0.4'} - side-channel@1.1.0: - resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} - engines: {node: '>= 0.4'} - side-channel@1.1.1: resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} engines: {node: '>= 0.4'} @@ -9440,7 +9432,7 @@ snapshots: idtoken-verifier: 2.2.4 js-cookie: 2.2.1 minimist: 1.2.8 - qs: 6.15.1 + qs: 6.15.2 superagent: 10.3.0 url-join: 4.0.1 winchan: 0.2.2 @@ -9631,7 +9623,7 @@ snapshots: http-errors: 2.0.1 iconv-lite: 0.4.24 on-finished: 2.4.1 - qs: 6.15.3 + qs: 6.15.2 raw-body: 2.5.3 type-is: 1.6.18 unpipe: 1.0.0 @@ -9646,7 +9638,7 @@ snapshots: http-errors: 2.0.1 iconv-lite: 0.7.3 on-finished: 2.4.1 - qs: 6.15.3 + qs: 6.15.2 raw-body: 3.0.2 type-is: 2.1.0 transitivePeerDependencies: @@ -10648,7 +10640,7 @@ snapshots: once: 1.4.0 parseurl: 1.3.3 proxy-addr: 2.0.7 - qs: 6.15.1 + qs: 6.15.2 range-parser: 1.2.1 router: 2.2.0 send: 1.2.1 @@ -12914,13 +12906,8 @@ snapshots: pngjs: 5.0.0 yargs: 15.4.1 - qs@6.15.1: + qs@6.15.2: dependencies: - side-channel: 1.1.0 - - qs@6.15.3: - dependencies: - es-define-property: 1.0.1 side-channel: 1.1.1 qs@6.5.5: @@ -13265,14 +13252,6 @@ snapshots: object-inspect: 1.13.4 side-channel-map: 1.0.1 - side-channel@1.1.0: - dependencies: - es-errors: 1.3.0 - object-inspect: 1.13.4 - side-channel-list: 1.0.1 - side-channel-map: 1.0.1 - side-channel-weakmap: 1.0.2 - side-channel@1.1.1: dependencies: es-errors: 1.3.0 @@ -13511,7 +13490,7 @@ snapshots: formidable: 3.5.4 methods: 1.1.2 mime: 2.6.0 - qs: 6.15.1 + qs: 6.15.2 transitivePeerDependencies: - supports-color From 588653ffeef7f6ae838f14199b036d97faf968ce Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 14 Aug 2026 06:23:09 +0000 Subject: [PATCH 33/57] build: bump form-data from 4.0.5 to 4.0.6 Bumps [form-data](https://github.com/form-data/form-data) from 4.0.5 to 4.0.6. - [Changelog](https://github.com/form-data/form-data/blob/master/CHANGELOG.md) - [Commits](https://github.com/form-data/form-data/compare/v4.0.5...v4.0.6) --- updated-dependencies: - dependency-name: form-data dependency-version: 4.0.6 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- package.json | 2 +- pnpm-lock.yaml | 45 ++++++++++++++++++++++++++------------------- 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/package.json b/package.json index 47ea3c9c0..0cd103130 100644 --- a/package.json +++ b/package.json @@ -128,7 +128,7 @@ "xml-js": "^1.6.11", "protobufjs": "^7.6.5", "handlebars": "^4.7.9", - "form-data": "^4.0.5" + "form-data": "^4.0.6" }, "devDependencies": { "@nestjs/cli": "catalog:", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7b866d285..ced4ef6f3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -237,8 +237,8 @@ importers: specifier: ^13.6.1 version: 13.8.0 form-data: - specifier: ^4.0.5 - version: 4.0.5 + specifier: ^4.0.6 + version: 4.0.6 fs: specifier: 0.0.1-security version: 0.0.1-security @@ -3383,6 +3383,10 @@ packages: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} + engines: {node: '>= 0.4'} + es-set-tostringtag@2.1.0: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} @@ -3776,8 +3780,8 @@ packages: resolution: {integrity: sha512-Ogz/E85h9tlfJzpI6TuFpGcHZFhLrb9Gw8wq9v40CxSCPnv7ahKr6Xgtkn0KYCDQJ8DNn5VoMO8EXr9V5PadyA==} engines: {node: '>= 0.12'} - form-data@4.0.5: - resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} + form-data@4.0.6: + resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==} engines: {node: '>= 6'} formdata-polyfill@4.0.10: @@ -8978,7 +8982,7 @@ snapshots: '@types/cookiejar': 2.1.5 '@types/methods': 1.1.4 '@types/node': 20.19.39 - form-data: 4.0.5 + form-data: 4.0.6 '@types/supertest@7.2.0': dependencies: @@ -9479,7 +9483,7 @@ snapshots: axios@1.18.0: dependencies: follow-redirects: 1.16.0 - form-data: 4.0.5 + form-data: 4.0.6 https-proxy-agent: 5.0.1 proxy-from-env: 2.1.0 transitivePeerDependencies: @@ -10292,7 +10296,7 @@ snapshots: has-property-descriptors: 1.0.2 has-proto: 1.2.0 has-symbols: 1.1.0 - hasown: 2.0.3 + hasown: 2.0.4 internal-slot: 1.1.0 is-array-buffer: 3.0.5 is-callable: 1.2.7 @@ -10347,16 +10351,20 @@ snapshots: dependencies: es-errors: 1.3.0 + es-object-atoms@1.1.2: + dependencies: + es-errors: 1.3.0 + es-set-tostringtag@2.1.0: dependencies: es-errors: 1.3.0 get-intrinsic: 1.3.0 has-tostringtag: 1.0.2 - hasown: 2.0.3 + hasown: 2.0.4 es-shim-unscopables@1.1.0: dependencies: - hasown: 2.0.3 + hasown: 2.0.4 es-to-primitive@1.3.0: dependencies: @@ -10883,12 +10891,12 @@ snapshots: safe-buffer: 5.2.1 optional: true - form-data@4.0.5: + form-data@4.0.6: dependencies: asynckit: 0.4.0 combined-stream: 1.0.8 es-set-tostringtag: 2.1.0 - hasown: 2.0.3 + hasown: 2.0.4 mime-types: 2.1.35 formdata-polyfill@4.0.10: @@ -10943,7 +10951,7 @@ snapshots: call-bound: 1.0.4 define-properties: 1.2.1 functions-have-names: 1.2.3 - hasown: 2.0.3 + hasown: 2.0.4 is-callable: 1.2.7 functional-red-black-tree@1.0.1: @@ -11002,12 +11010,12 @@ snapshots: call-bind-apply-helpers: 1.0.2 es-define-property: 1.0.1 es-errors: 1.3.0 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 function-bind: 1.1.2 get-proto: 1.0.1 gopd: 1.2.0 has-symbols: 1.1.0 - hasown: 2.0.3 + hasown: 2.0.4 math-intrinsics: 1.1.0 get-package-type@0.1.0: {} @@ -11227,7 +11235,6 @@ snapshots: hasown@2.0.4: dependencies: function-bind: 1.1.2 - optional: true helmet@8.1.0: {} @@ -11355,7 +11362,7 @@ snapshots: internal-slot@1.1.0: dependencies: es-errors: 1.3.0 - hasown: 2.0.3 + hasown: 2.0.4 side-channel: 1.1.1 ioredis@5.10.1: @@ -11469,7 +11476,7 @@ snapshots: call-bound: 1.0.4 gopd: 1.2.0 has-tostringtag: 1.0.2 - hasown: 2.0.3 + hasown: 2.0.4 is-set@2.0.3: {} @@ -12442,7 +12449,7 @@ snapshots: call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 object.fromentries@2.0.8: dependencies: @@ -13507,7 +13514,7 @@ snapshots: cookiejar: 2.1.4 debug: 4.4.3 fast-safe-stringify: 2.1.1 - form-data: 4.0.5 + form-data: 4.0.6 formidable: 3.5.4 methods: 1.1.2 mime: 2.6.0 From 4a000e38fc0985c6671ee3106dad1747f2fabfa3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 14 Aug 2026 06:23:09 +0000 Subject: [PATCH 34/57] build: bump @opentelemetry/sdk-node from 0.202.0 to 0.217.0 Bumps [@opentelemetry/sdk-node](https://github.com/open-telemetry/opentelemetry-js) from 0.202.0 to 0.217.0. - [Release notes](https://github.com/open-telemetry/opentelemetry-js/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-js/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-js/compare/experimental/v0.202.0...experimental/v0.217.0) --- updated-dependencies: - dependency-name: "@opentelemetry/sdk-node" dependency-version: 0.217.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- package.json | 2 +- pnpm-lock.yaml | 490 +++++++++++++++++++++++++++++++++++-------------- 2 files changed, 357 insertions(+), 135 deletions(-) diff --git a/package.json b/package.json index 47ea3c9c0..ae0773658 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "@opentelemetry/instrumentation-nestjs-core": "^0.48.1", "@opentelemetry/resources": "^2.5.1", "@opentelemetry/sdk-logs": "^0.202.0", - "@opentelemetry/sdk-node": "^0.202.0", + "@opentelemetry/sdk-node": "^0.217.0", "@opentelemetry/semantic-conventions": "^1.39.0", "@prisma/client": "^5.22.0", "@sendgrid/mail": "^7.7.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7b866d285..fd2886367 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -103,7 +103,7 @@ importers: version: 11.0.2(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2)) '@nestjs/microservices': specifier: 'catalog:' - version: 11.1.19(@grpc/grpc-js@1.14.3)(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/websockets@11.1.19)(cache-manager@5.7.6)(ioredis@5.10.1)(nats@2.29.3)(reflect-metadata@0.1.14)(rxjs@7.8.2) + version: 11.1.19(@grpc/grpc-js@1.14.4)(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/websockets@11.1.19)(cache-manager@5.7.6)(ioredis@5.10.1)(nats@2.29.3)(reflect-metadata@0.1.14)(rxjs@7.8.2) '@nestjs/passport': specifier: 'catalog:' version: 11.0.5(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(passport@0.6.0) @@ -150,8 +150,8 @@ importers: specifier: ^0.202.0 version: 0.202.0(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-node': - specifier: ^0.202.0 - version: 0.202.0(@opentelemetry/api@1.9.1) + specifier: ^0.217.0 + version: 0.217.0(@opentelemetry/api@1.9.1) '@opentelemetry/semantic-conventions': specifier: ^1.39.0 version: 1.40.0 @@ -500,7 +500,7 @@ importers: version: 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@11.1.19)(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.1.14)(rxjs@7.8.2) '@nestjs/microservices': specifier: 'catalog:' - version: 11.1.19(@grpc/grpc-js@1.14.3)(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/websockets@11.1.19)(cache-manager@5.7.6)(ioredis@5.10.1)(nats@2.29.3)(reflect-metadata@0.1.14)(rxjs@7.8.2) + version: 11.1.19(@grpc/grpc-js@1.14.4)(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/websockets@11.1.19)(cache-manager@5.7.6)(ioredis@5.10.1)(nats@2.29.3)(reflect-metadata@0.1.14)(rxjs@7.8.2) '@nestjs/swagger': specifier: 'catalog:' version: 11.4.2(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14) @@ -555,7 +555,7 @@ importers: version: 11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) '@nestjs/microservices': specifier: 'catalog:' - version: 11.1.19(@grpc/grpc-js@1.14.3)(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/websockets@11.1.19)(cache-manager@5.7.6)(ioredis@5.10.1)(nats@2.29.3)(reflect-metadata@0.1.14)(rxjs@7.8.2) + version: 11.1.19(@grpc/grpc-js@1.14.4)(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/websockets@11.1.19)(cache-manager@5.7.6)(ioredis@5.10.1)(nats@2.29.3)(reflect-metadata@0.1.14)(rxjs@7.8.2) '@nestjs/testing': specifier: 'catalog:' version: 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/microservices@11.1.19)(@nestjs/platform-express@11.1.19) @@ -991,6 +991,10 @@ packages: resolution: {integrity: sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==} engines: {node: '>=12.10.0'} + '@grpc/grpc-js@1.14.4': + resolution: {integrity: sha512-k9Dj3DV/itK9D06Y8f190Qgop7/Ui+D0njFV3LHMPwPT75DpXLQohE9Wmz0QElrJnzsjB7KPWiKJbOl7IPDArQ==} + engines: {node: '>=12.10.0'} + '@grpc/proto-loader@0.7.15': resolution: {integrity: sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==} engines: {node: '>=6'} @@ -1001,6 +1005,11 @@ packages: engines: {node: '>=6'} hasBin: true + '@grpc/proto-loader@0.8.1': + resolution: {integrity: sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg==} + engines: {node: '>=6'} + hasBin: true + '@humanwhocodes/config-array@0.13.0': resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} engines: {node: '>=10.10.0'} @@ -1550,12 +1559,22 @@ packages: resolution: {integrity: sha512-fTBjMqKCfotFWfLzaKyhjLvyEyq5vDKTTFfBmx21btv3gvy8Lq6N5Dh2OzqeuN4DjtpSvNT1uNVfg08eD2Rfxw==} engines: {node: '>=8.0.0'} + '@opentelemetry/api-logs@0.217.0': + resolution: {integrity: sha512-Cdq0jW2lknrNfrAm92MyEAvpe2cRsKjdnQLHUL6xRA4IVUnsWx6P65E7NcUO0Y+L4w1Aee5iV8FvjSwd+lrs9A==} + engines: {node: '>=8.0.0'} + '@opentelemetry/api@1.9.1': resolution: {integrity: sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==} engines: {node: '>=8.0.0'} - '@opentelemetry/context-async-hooks@2.0.1': - resolution: {integrity: sha512-XuY23lSI3d4PEqKA+7SLtAgwqIfc6E/E9eAQWLN1vlpC53ybO3o6jW4BsXo1xvz9lYyyWItfQDDLzezER01mCw==} + '@opentelemetry/configuration@0.217.0': + resolution: {integrity: sha512-xCtrYOhBqdy6ZOMfe0Oa73ZKF+2LMhoOv4L5vmwAHVvOXUg+V3fvKuEIr9ZyD0Ow+vxllEjWO6PV1wd0DOtyvw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.9.0 + + '@opentelemetry/context-async-hooks@2.7.1': + resolution: {integrity: sha512-OPFBYuXEn1E4ja3Y6eeA7O+ZnLBNcXTV5Cgsn1VaqBZ6hC5FnpZPLBNme1LJY8ZtF4aOujPKFoeWN4ik487KuQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' @@ -1572,8 +1591,8 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/exporter-logs-otlp-grpc@0.202.0': - resolution: {integrity: sha512-Y84L8Yja/A2qjGEzC/To0yrMUXHrtwJzHtZ2za1/ulZplRe5QFsLNyHixIS42ZYUKuNyWMDgOFhnN2Pz5uThtg==} + '@opentelemetry/exporter-logs-otlp-grpc@0.217.0': + resolution: {integrity: sha512-vC5S0Dc+noxD86CVtNu1+awCHPA5Kewi1Sg23ps+9lh4YifwsKXh3pe4XTNEKtUJiAcjpJ5dqStGakLbrSE+YQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 @@ -1584,38 +1603,44 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-logs-otlp-proto@0.202.0': - resolution: {integrity: sha512-qYwbmNWPkP7AbzX8o4DRu5bb/a0TWYNcpZc1NEAOhuV7pgBpAUPEClxRWPN94ulIia+PfQjzFGMaRwmLGmNP6g==} + '@opentelemetry/exporter-logs-otlp-http@0.217.0': + resolution: {integrity: sha512-KfLAdt1uilVE+3FxbgVnp2ZrzqbIawzcesnRoi+Kh9ckB5Ld5D8btUgoBvwTbdmuNx1j6b132Wsh72azq+pPNQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-metrics-otlp-grpc@0.202.0': - resolution: {integrity: sha512-/dq/rf4KCkTYoP+NyPXTE+5wjvfhAHSqK62vRsJ/IalG61VPQvwaL18yWcavbI+44ImQwtMeZxfIJSox7oQL0w==} + '@opentelemetry/exporter-logs-otlp-proto@0.217.0': + resolution: {integrity: sha512-Se0GG/ZO24mQTlQj7zprR4pNI0nKe4lPDPBsuJmi6508b9TlZEuUd3EfyuHk6oJxzL7fGyDFYAbxNigQvRP2ZQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-metrics-otlp-http@0.202.0': - resolution: {integrity: sha512-ooYcrf/m9ZuVGpQnER7WRH+JZbDPD389HG7VS/EnvIEF5WpNYEqf+NdmtaAcs51d81QrytTYAubc5bVWi//28w==} + '@opentelemetry/exporter-metrics-otlp-grpc@0.217.0': + resolution: {integrity: sha512-0GpJKnCoVaVA1rKBMVPHziznfOQlXgH72S9ktjBAF1AnAVPzX7vVEBGrhwiSxxHDAiefXk+J8znApsMb/K6Z3w==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-metrics-otlp-proto@0.202.0': - resolution: {integrity: sha512-X0RpPpPjyCAmIq9tySZm0Hk3Ltw8KWsqeNq5I7gS9AR9RzbVHb/l+eiMI1CqSRvW9R47HXcUu/epmEzY8ebFAg==} + '@opentelemetry/exporter-metrics-otlp-http@0.217.0': + resolution: {integrity: sha512-1zkMzzhiNJdVmLxuwkltqWGw4fOOam47bqRxmuQNjyKJe/9NmY5cIrZ4kiQV7sVGxoOgT0ZvGUfLcjvtpC/b9Q==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-prometheus@0.202.0': - resolution: {integrity: sha512-6RvQqZHAPFiwL1OKRJe4ta6SgJx/g8or41B+OovVVEie3HeCDhDGL9S1VJNkBozUz6wTY8a47fQwdMrCOUdMhQ==} + '@opentelemetry/exporter-metrics-otlp-proto@0.217.0': + resolution: {integrity: sha512-nfxt/KxVGFkjkO/M+58y1ugHu/dwPtxG4eYq0KApcQ7xk5CHzhdn+IuLZfDSvNDrJ3Uy5q++Fj/wbK7i8yryfQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-trace-otlp-grpc@0.202.0': - resolution: {integrity: sha512-d5wLdbNA3ahpSeD0I34vbDFMTh4vPsXemH0bKDXLeCVULCAjOJXuZmEiuRammiDgVvvX7CAb/IGLDz8d2QHvoA==} + '@opentelemetry/exporter-prometheus@0.217.0': + resolution: {integrity: sha512-U9MCXxJu0sBCh5aEkylYRR4xVIL8D1CW6dGwvYXbfFr0qveSorfD0XJchCAWoW6QfAAIcY/yxjf4Dj8OgkHBPw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-trace-otlp-grpc@0.217.0': + resolution: {integrity: sha512-fPZs2fw7veLH3pEKu8vSepUa2fQpAE2P7al6qU10aH9GrEJJ8YaPgsd5xON7by5rbcEVS71FOU2aWyK6nzB7VQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 @@ -1626,14 +1651,20 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-trace-otlp-proto@0.202.0': - resolution: {integrity: sha512-z3vzdMclCETGIn8uUBgpz7w651ftCiH2qh3cewhBk+rF0EYPNQ3mJvyxktLnKIBZ/ci0zUknAzzYC7LIIZmggQ==} + '@opentelemetry/exporter-trace-otlp-http@0.217.0': + resolution: {integrity: sha512-38YQoqtYjglz2GV94LGUN/djLvxtvGIQO68o6qAFPVshjmwSdX1F2i0c7vn3lEl1L5B/YqjB/bgKXaVx7KO+RQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-zipkin@2.0.1': - resolution: {integrity: sha512-a9eeyHIipfdxzCfc2XPrE+/TI3wmrZUDFtG2RRXHSbZZULAny7SyybSvaDvS77a7iib5MPiAvluwVvbGTsHxsw==} + '@opentelemetry/exporter-trace-otlp-proto@0.217.0': + resolution: {integrity: sha512-nPV8gKHUiSuTZpQcnZU3/pBlK7crSyEGpZuh5MtWySB0vv6NNG0QvvfKitQt+Fc2Mc6qfyU54KlZcurwoTbrVg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-zipkin@2.7.1': + resolution: {integrity: sha512-mfsD9bKAxcKrh5+y08TPodvClBO0CznBE3p79YAGnO81WI4LrdsGA65T53e4iTSbCalW4WaUpkbeJcbpyIUHfg==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.0.0 @@ -1662,14 +1693,26 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation@0.217.0': + resolution: {integrity: sha512-24ucQMjz7Y34Kw3trbxL2ZrssbtgWnR+Clpaa+YdeWuuyH3Cvk23Q03PcQvqiZrDvt8AmQmjgg9v6Y9PHoxG7w==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/otlp-exporter-base@0.202.0': resolution: {integrity: sha512-nMEOzel+pUFYuBJg2znGmHJWbmvMbdX5/RhoKNKowguMbURhz0fwik5tUKplLcUtl8wKPL1y9zPnPxeBn65N0Q==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/otlp-grpc-exporter-base@0.202.0': - resolution: {integrity: sha512-yIEHVxFA5dmYif7lZbbB66qulLLhrklj6mI2X3cuGW5hYPyUErztEmbroM+6teu/XobBi9bLHid2VT4NIaRuGg==} + '@opentelemetry/otlp-exporter-base@0.217.0': + resolution: {integrity: sha512-eYfqnB3UhKu/5frhd1R6+FprKygbhkomuaceMXDyzxbfXB9tKgZOVmjaJ02CkLA6Tdzumxl+e2H+vo2a8jiMPQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-grpc-exporter-base@0.217.0': + resolution: {integrity: sha512-7RTAdZuOsCDnsyqTCG4+bDzrfnsWdzkRs7z0AVi/V3tEQx0oKeyc+OuRWYxnRsmaJXgxcmB8vb/lfxn58Dj6Ag==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 @@ -1680,14 +1723,20 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/propagator-b3@2.0.1': - resolution: {integrity: sha512-Hc09CaQ8Tf5AGLmf449H726uRoBNGPBL4bjr7AnnUpzWMvhdn61F78z9qb6IqB737TffBsokGAK1XykFEZ1igw==} + '@opentelemetry/otlp-transformer@0.217.0': + resolution: {integrity: sha512-MKK8UHKFUOGAvbZRWh90MhwHG+Fxm6OROBdjKPCF+HQobjuJ/Kuf8Chs8CR45X1aqotxrMj7OxTdsXe8sXuGVA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/propagator-b3@2.7.1': + resolution: {integrity: sha512-RJid6E2CKyeGfKBzXKF21ejabGMHypFkPAh3qZ+NvI+SGjuIye79t3PmiqcDgtRzdKH6ynXzbfslQ8DfpRUg2A==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/propagator-jaeger@2.0.1': - resolution: {integrity: sha512-7PMdPBmGVH2eQNb/AtSJizQNgeNTfh6jQFqys6lfhd6P4r+m/nTh3gKPPpaCXVdRQ+z93vfKk+4UGty390283w==} + '@opentelemetry/propagator-jaeger@2.7.1': + resolution: {integrity: sha512-KMjVBHzP4N60bOzxja76M1F1hZZ43lGPga5ix+mkv9+kk1nx9SbkxSvJsMbuVUxdPQmsPTqGShmhN8ulrMOg6Q==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' @@ -1710,14 +1759,26 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.4.0 <1.10.0' + '@opentelemetry/sdk-logs@0.217.0': + resolution: {integrity: sha512-BB+PcHItcZDL63dPMW+mJvwN9rk37wuIDjRxbVlg6pPDvDR/7GL7UJHbGsllgoggOoTimsKgENaWPoGch/oE1A==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.4.0 <1.10.0' + '@opentelemetry/sdk-metrics@2.0.1': resolution: {integrity: sha512-wf8OaJoSnujMAHWR3g+/hGvNcsC16rf9s1So4JlMiFaFHiE4HpIA3oUh+uWZQ7CNuK8gVW/pQSkgoa5HkkOl0g==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.9.0 <1.10.0' - '@opentelemetry/sdk-node@0.202.0': - resolution: {integrity: sha512-SF9vXWVd9I5CZ69mW3GfwfLI2SHgyvEqntcg0en5y8kRp5+2PPoa3Mkgj0WzFLrbSgTw4PsXn7c7H6eSdrtV0w==} + '@opentelemetry/sdk-metrics@2.7.1': + resolution: {integrity: sha512-MpDJdkiFDs3Pm1RHO3KByuZbuBdJEXEAkiC0+yJdsZGVCdf1RpHR6n+LHDcS7ffmfrt5kVCzJSCfm4z2C7v0uQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.9.0 <1.10.0' + + '@opentelemetry/sdk-node@0.217.0': + resolution: {integrity: sha512-K/60pSv42+NQiZKy1pAH18nYDkxltsDV4O3SJ233J0E9raU1ksyL9gsKuS8p30bYBb4AMPCfDuutHQaHYpcv0Q==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.3.0 <1.10.0' @@ -1728,8 +1789,14 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.3.0 <1.10.0' - '@opentelemetry/sdk-trace-node@2.0.1': - resolution: {integrity: sha512-UhdbPF19pMpBtCWYP5lHbTogLWx9N0EBxtdagvkn5YtsAnCBZzL7SjktG+ZmupRgifsHMjwUaCCaVmqGfSADmA==} + '@opentelemetry/sdk-trace-base@2.7.1': + resolution: {integrity: sha512-NAYIlsF8MPUsKqJMiDQJTMPOmlbawC1Iz/omMLygZ1C9am8fTKYjTaI+OZM+WTY3t3Glo0wnOg/6/pac6RGPPw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-trace-node@2.7.1': + resolution: {integrity: sha512-pCpQxU68lV+I9s9svqMyVu5iHdDDUnqUpSxqwyCU8A9ejEsSnMPCbearwsUO4yk08ZJzAIUCFuReMdVQvHrdvg==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' @@ -1787,6 +1854,9 @@ packages: '@protobufjs/float@1.0.2': resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + '@protobufjs/inquire@1.1.2': + resolution: {integrity: sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw==} + '@protobufjs/path@1.1.2': resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} @@ -2904,6 +2974,9 @@ packages: cjs-module-lexer@1.4.3: resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==} + cjs-module-lexer@2.2.1: + resolution: {integrity: sha512-Ca8swihM+/4yKecYHY52kgJd300hi2lADU/a1RxNTRe+RJ9jvqQlESpbz9DnG9mowez8qwXHB8qYdIUw9e+F5Q==} + class-transformer@0.5.1: resolution: {integrity: sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==} @@ -3379,6 +3452,9 @@ packages: es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + es-module-lexer@2.3.1: + resolution: {integrity: sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==} + es-object-atoms@1.1.1: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} @@ -4129,6 +4205,10 @@ packages: import-in-the-middle@1.15.0: resolution: {integrity: sha512-bpQy+CrsRmYmoPMAE/0G33iwRqwW4ouqdRg8jgbH3aKuCtOc8lxgmYXg2dMM92CRiGP660EtBcymH/eVUpCSaA==} + import-in-the-middle@3.3.3: + resolution: {integrity: sha512-AiohS3H80sXO6owEltjGX+glb7qXaDhBoJb9XcQVH4UI207xu/bDLUcadVKp7Qe576reg9yr/PXZjV5qx8gfbA==} + engines: {node: '>=18'} + import-local@3.2.0: resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==} engines: {node: '>=8'} @@ -5412,6 +5492,10 @@ packages: resolution: {integrity: sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw==} engines: {node: '>=12.0.0'} + protobufjs@8.0.1: + resolution: {integrity: sha512-NWWCCscLjs+cOKF/s/XVNFRW7Yih0fdH+9brffR5NZCy8k42yRdl5KlWKMVXuI1vfCoy4o1z80XR/W/QUb3V3w==} + engines: {node: '>=12.0.0'} + proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -5571,6 +5655,10 @@ packages: resolution: {integrity: sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ==} engines: {node: '>=8.6.0'} + require-in-the-middle@8.0.1: + resolution: {integrity: sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==} + engines: {node: '>=9.3.0 || >=8.10.0 <9.0.0'} + require-main-filename@2.0.0: resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} @@ -6624,6 +6712,11 @@ packages: resolution: {integrity: sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==} engines: {node: '>= 14'} + yaml@2.9.0: + resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} + engines: {node: '>= 14.6'} + hasBin: true + yargs-parser@18.1.3: resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} engines: {node: '>=6'} @@ -7405,6 +7498,12 @@ snapshots: dependencies: '@grpc/proto-loader': 0.8.0 '@js-sdsl/ordered-map': 4.4.2 + optional: true + + '@grpc/grpc-js@1.14.4': + dependencies: + '@grpc/proto-loader': 0.8.1 + '@js-sdsl/ordered-map': 4.4.2 '@grpc/proto-loader@0.7.15': dependencies: @@ -7420,6 +7519,14 @@ snapshots: long: 5.3.2 protobufjs: 7.6.5 yargs: 17.7.3 + optional: true + + '@grpc/proto-loader@0.8.1': + dependencies: + lodash.camelcase: 4.3.0 + long: 5.3.2 + protobufjs: 7.6.5 + yargs: 17.7.3 '@humanwhocodes/config-array@0.13.0': dependencies: @@ -7902,7 +8009,7 @@ snapshots: tslib: 2.8.1 uid: 2.0.2 optionalDependencies: - '@nestjs/microservices': 11.1.19(@grpc/grpc-js@1.14.3)(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/websockets@11.1.19)(cache-manager@5.7.6)(ioredis@5.10.1)(nats@2.29.3)(reflect-metadata@0.1.14)(rxjs@7.8.2) + '@nestjs/microservices': 11.1.19(@grpc/grpc-js@1.14.4)(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/websockets@11.1.19)(cache-manager@5.7.6)(ioredis@5.10.1)(nats@2.29.3)(reflect-metadata@0.1.14)(rxjs@7.8.2) '@nestjs/platform-express': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19) '@nestjs/websockets': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/platform-socket.io@11.1.19)(reflect-metadata@0.1.14)(rxjs@7.8.2) @@ -7920,7 +8027,7 @@ snapshots: class-transformer: 0.5.1 class-validator: 0.14.4 - '@nestjs/microservices@11.1.19(@grpc/grpc-js@1.14.3)(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/websockets@11.1.19)(cache-manager@5.7.6)(ioredis@5.10.1)(nats@2.29.3)(reflect-metadata@0.1.14)(rxjs@7.8.2)': + '@nestjs/microservices@11.1.19(@grpc/grpc-js@1.14.4)(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/websockets@11.1.19)(cache-manager@5.7.6)(ioredis@5.10.1)(nats@2.29.3)(reflect-metadata@0.1.14)(rxjs@7.8.2)': dependencies: '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@11.1.19)(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.1.14)(rxjs@7.8.2) @@ -7929,7 +8036,7 @@ snapshots: rxjs: 7.8.2 tslib: 2.8.1 optionalDependencies: - '@grpc/grpc-js': 1.14.3 + '@grpc/grpc-js': 1.14.4 '@nestjs/websockets': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/platform-socket.io@11.1.19)(reflect-metadata@0.1.14)(rxjs@7.8.2) cache-manager: 5.7.6 ioredis: 5.10.1 @@ -8017,7 +8124,7 @@ snapshots: '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@11.1.19)(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.1.14)(rxjs@7.8.2) tslib: 2.8.1 optionalDependencies: - '@nestjs/microservices': 11.1.19(@grpc/grpc-js@1.14.3)(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/websockets@11.1.19)(cache-manager@5.7.6)(ioredis@5.10.1)(nats@2.29.3)(reflect-metadata@0.1.14)(rxjs@7.8.2) + '@nestjs/microservices': 11.1.19(@grpc/grpc-js@1.14.4)(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/websockets@11.1.19)(cache-manager@5.7.6)(ioredis@5.10.1)(nats@2.29.3)(reflect-metadata@0.1.14)(rxjs@7.8.2) '@nestjs/platform-express': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19) '@nestjs/typeorm@11.0.1(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(reflect-metadata@0.1.14)(rxjs@7.8.2)(typeorm@0.3.31(ioredis@5.10.1)(pg@8.20.0)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)))': @@ -8064,9 +8171,19 @@ snapshots: dependencies: '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs@0.217.0': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api@1.9.1': {} - '@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.1)': + '@opentelemetry/configuration@0.217.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + yaml: 2.9.0 + + '@opentelemetry/context-async-hooks@2.7.1(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 @@ -8080,15 +8197,15 @@ snapshots: '@opentelemetry/api': 1.9.1 '@opentelemetry/semantic-conventions': 1.40.0 - '@opentelemetry/exporter-logs-otlp-grpc@0.202.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/exporter-logs-otlp-grpc@0.217.0(@opentelemetry/api@1.9.1)': dependencies: - '@grpc/grpc-js': 1.14.3 + '@grpc/grpc-js': 1.14.4 '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base': 0.202.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-grpc-exporter-base': 0.202.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer': 0.202.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-logs': 0.202.0(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-grpc-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.217.0(@opentelemetry/api@1.9.1) '@opentelemetry/exporter-logs-otlp-http@0.202.0(@opentelemetry/api@1.9.1)': dependencies: @@ -8099,65 +8216,75 @@ snapshots: '@opentelemetry/otlp-transformer': 0.202.0(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-logs': 0.202.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-logs-otlp-proto@0.202.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/exporter-logs-otlp-http@0.217.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/api-logs': 0.202.0 - '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base': 0.202.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer': 0.202.0(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-logs': 0.202.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-trace-base': 2.0.1(@opentelemetry/api@1.9.1) + '@opentelemetry/api-logs': 0.217.0 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.217.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-metrics-otlp-grpc@0.202.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/exporter-logs-otlp-proto@0.217.0(@opentelemetry/api@1.9.1)': dependencies: - '@grpc/grpc-js': 1.14.3 '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-metrics-otlp-http': 0.202.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base': 0.202.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-grpc-exporter-base': 0.202.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer': 0.202.0(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-metrics': 2.0.1(@opentelemetry/api@1.9.1) + '@opentelemetry/api-logs': 0.217.0 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-metrics-otlp-http@0.202.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/exporter-metrics-otlp-grpc@0.217.0(@opentelemetry/api@1.9.1)': dependencies: + '@grpc/grpc-js': 1.14.4 '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base': 0.202.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer': 0.202.0(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-metrics': 2.0.1(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-metrics-otlp-http': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-grpc-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-metrics-otlp-proto@0.202.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/exporter-metrics-otlp-http@0.217.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-metrics-otlp-http': 0.202.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base': 0.202.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer': 0.202.0(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-metrics': 2.0.1(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-prometheus@0.202.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/exporter-metrics-otlp-proto@0.217.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-metrics': 2.0.1(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-metrics-otlp-http': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-trace-otlp-grpc@0.202.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/exporter-prometheus@0.217.0(@opentelemetry/api@1.9.1)': dependencies: - '@grpc/grpc-js': 1.14.3 '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base': 0.202.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-grpc-exporter-base': 0.202.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer': 0.202.0(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-trace-base': 2.0.1(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + + '@opentelemetry/exporter-trace-otlp-grpc@0.217.0(@opentelemetry/api@1.9.1)': + dependencies: + '@grpc/grpc-js': 1.14.4 + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-grpc-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/exporter-trace-otlp-http@0.202.0(@opentelemetry/api@1.9.1)': dependencies: @@ -8168,21 +8295,30 @@ snapshots: '@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-trace-base': 2.0.1(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-trace-otlp-proto@0.202.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/exporter-trace-otlp-http@0.217.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base': 0.202.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer': 0.202.0(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-trace-base': 2.0.1(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-zipkin@2.0.1(@opentelemetry/api@1.9.1)': + '@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-trace-base': 2.0.1(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) + + '@opentelemetry/exporter-zipkin@2.7.1(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/semantic-conventions': 1.40.0 '@opentelemetry/instrumentation-express@0.51.1(@opentelemetry/api@1.9.1)': @@ -8221,19 +8357,34 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation@0.217.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.217.0 + import-in-the-middle: 3.3.3 + require-in-the-middle: 8.0.1 + transitivePeerDependencies: + - supports-color + '@opentelemetry/otlp-exporter-base@0.202.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.1) '@opentelemetry/otlp-transformer': 0.202.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-grpc-exporter-base@0.202.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/otlp-exporter-base@0.217.0(@opentelemetry/api@1.9.1)': dependencies: - '@grpc/grpc-js': 1.14.3 '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base': 0.202.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer': 0.202.0(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.1) + + '@opentelemetry/otlp-grpc-exporter-base@0.217.0(@opentelemetry/api@1.9.1)': + dependencies: + '@grpc/grpc-js': 1.14.4 + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.1) '@opentelemetry/otlp-transformer@0.202.0(@opentelemetry/api@1.9.1)': dependencies: @@ -8246,15 +8397,26 @@ snapshots: '@opentelemetry/sdk-trace-base': 2.0.1(@opentelemetry/api@1.9.1) protobufjs: 7.6.5 - '@opentelemetry/propagator-b3@2.0.1(@opentelemetry/api@1.9.1)': + '@opentelemetry/otlp-transformer@0.217.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.1) + '@opentelemetry/api-logs': 0.217.0 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) + protobufjs: 8.0.1 - '@opentelemetry/propagator-jaeger@2.0.1(@opentelemetry/api@1.9.1)': + '@opentelemetry/propagator-b3@2.7.1(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + + '@opentelemetry/propagator-jaeger@2.7.1(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/resources@2.0.1(@opentelemetry/api@1.9.1)': dependencies: @@ -8275,36 +8437,53 @@ snapshots: '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.1) '@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs@0.217.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.217.0 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + '@opentelemetry/sdk-metrics@2.0.1(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.1) '@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-node@0.202.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/sdk-metrics@2.7.1(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/api-logs': 0.202.0 - '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-logs-otlp-grpc': 0.202.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-logs-otlp-http': 0.202.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-logs-otlp-proto': 0.202.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-metrics-otlp-grpc': 0.202.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-metrics-otlp-http': 0.202.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-metrics-otlp-proto': 0.202.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-prometheus': 0.202.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-trace-otlp-grpc': 0.202.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-trace-otlp-http': 0.202.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-trace-otlp-proto': 0.202.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-zipkin': 2.0.1(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation': 0.202.0(@opentelemetry/api@1.9.1) - '@opentelemetry/propagator-b3': 2.0.1(@opentelemetry/api@1.9.1) - '@opentelemetry/propagator-jaeger': 2.0.1(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-logs': 0.202.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-metrics': 2.0.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-trace-base': 2.0.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-trace-node': 2.0.1(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + + '@opentelemetry/sdk-node@0.217.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.217.0 + '@opentelemetry/configuration': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/context-async-hooks': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-logs-otlp-grpc': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-logs-otlp-http': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-logs-otlp-proto': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-metrics-otlp-grpc': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-metrics-otlp-http': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-metrics-otlp-proto': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-prometheus': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-trace-otlp-grpc': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-trace-otlp-http': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-trace-otlp-proto': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-zipkin': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/propagator-b3': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/propagator-jaeger': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-node': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/semantic-conventions': 1.40.0 transitivePeerDependencies: - supports-color @@ -8316,12 +8495,19 @@ snapshots: '@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.1) '@opentelemetry/semantic-conventions': 1.40.0 - '@opentelemetry/sdk-trace-node@2.0.1(@opentelemetry/api@1.9.1)': + '@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/context-async-hooks': 2.0.1(@opentelemetry/api@1.9.1) - '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-trace-base': 2.0.1(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + + '@opentelemetry/sdk-trace-node@2.7.1(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/context-async-hooks': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/semantic-conventions@1.40.0': {} @@ -8371,6 +8557,8 @@ snapshots: '@protobufjs/float@1.0.2': {} + '@protobufjs/inquire@1.1.2': {} + '@protobufjs/path@1.1.2': {} '@protobufjs/pool@1.1.0': {} @@ -9818,6 +10006,8 @@ snapshots: cjs-module-lexer@1.4.3: {} + cjs-module-lexer@2.2.1: {} + class-transformer@0.5.1: {} class-validator@0.14.4: @@ -10343,6 +10533,8 @@ snapshots: es-module-lexer@1.7.0: {} + es-module-lexer@2.3.1: {} + es-object-atoms@1.1.1: dependencies: es-errors: 1.3.0 @@ -11336,6 +11528,12 @@ snapshots: cjs-module-lexer: 1.4.3 module-details-from-path: 1.0.4 + import-in-the-middle@3.3.3: + dependencies: + cjs-module-lexer: 2.2.1 + es-module-lexer: 2.3.1 + module-details-from-path: 1.0.4 + import-local@3.2.0: dependencies: pkg-dir: 4.2.0 @@ -12787,6 +12985,21 @@ snapshots: '@types/node': 20.19.39 long: 5.3.2 + protobufjs@8.0.1: + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.5 + '@protobufjs/eventemitter': 1.1.1 + '@protobufjs/fetch': 1.1.1 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.2 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.2 + '@types/node': 20.19.39 + long: 5.3.2 + proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 @@ -13041,6 +13254,13 @@ snapshots: transitivePeerDependencies: - supports-color + require-in-the-middle@8.0.1: + dependencies: + debug: 4.4.3 + module-details-from-path: 1.0.4 + transitivePeerDependencies: + - supports-color + require-main-filename@2.0.0: {} resend@6.12.3: @@ -14188,6 +14408,8 @@ snapshots: yaml@2.3.1: {} + yaml@2.9.0: {} + yargs-parser@18.1.3: dependencies: camelcase: 5.3.1 From 333944c91dc959a27c338a3320eb72bce0fca368 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 14 Aug 2026 13:26:28 +0530 Subject: [PATCH 35/57] build: bump protobufjs from 8.0.1 to 8.7.2 (#1714) Bumps [protobufjs](https://github.com/protobufjs/protobuf.js) from 8.0.1 to 8.7.2. - [Release notes](https://github.com/protobufjs/protobuf.js/releases) - [Changelog](https://github.com/protobufjs/protobuf.js/blob/master/CHANGELOG.md) - [Commits](https://github.com/protobufjs/protobuf.js/compare/protobufjs-v8.0.1...protobufjs-v8.7.2) --- updated-dependencies: - dependency-name: protobufjs dependency-version: 8.7.2 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pnpm-lock.yaml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ba1b13de9..1ce203204 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -331,7 +331,7 @@ importers: version: 8.20.0 protobufjs: specifier: ^7.6.5 - version: 7.6.5 + version: 8.7.2 puppeteer: specifier: ^21.11.0 version: 21.11.0(typescript@5.9.3) @@ -5500,6 +5500,10 @@ packages: resolution: {integrity: sha512-NWWCCscLjs+cOKF/s/XVNFRW7Yih0fdH+9brffR5NZCy8k42yRdl5KlWKMVXuI1vfCoy4o1z80XR/W/QUb3V3w==} engines: {node: '>=12.0.0'} + protobufjs@8.7.2: + resolution: {integrity: sha512-oTVHV+oelUBtiu5iTuTNNZ0eLYsXSMxry4cgr30mayNkgIZL6qZ0IOQVPuSWGcyAaXKl/XgqwWHIC3a0khYVBA==} + engines: {node: '>=12.0.0'} + proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -12999,6 +13003,10 @@ snapshots: '@types/node': 20.19.39 long: 5.3.2 + protobufjs@8.7.2: + dependencies: + long: 5.3.2 + proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 From fb541d7ca6c3a270ec011d32cde77b464d16bef8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 14 Aug 2026 13:26:48 +0530 Subject: [PATCH 36/57] chore(deps): bump morgan from 1.10.1 to 1.11.0 (#1697) Bumps [morgan](https://github.com/expressjs/morgan) from 1.10.1 to 1.11.0. - [Release notes](https://github.com/expressjs/morgan/releases) - [Changelog](https://github.com/expressjs/morgan/blob/master/HISTORY.md) - [Commits](https://github.com/expressjs/morgan/compare/1.10.1...1.11.0) --- updated-dependencies: - dependency-name: morgan dependency-version: 1.11.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- pnpm-lock.yaml | 20 ++++++-------------- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index 82fcc75c9..09e7838a2 100644 --- a/package.json +++ b/package.json @@ -89,7 +89,7 @@ "jwks-rsa": "^3.2.2", "linebyline": "^1.3.0", "moment": "^2.30.1", - "morgan": "^1.10.1", + "morgan": "^1.11.0", "multer": "^2.0.2", "nanoid": "^5.1.16", "nats": "^2.29.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1ce203204..889c37ee5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -273,8 +273,8 @@ importers: specifier: ^2.30.1 version: 2.30.1 morgan: - specifier: ^1.10.1 - version: 1.10.1 + specifier: ^1.11.0 + version: 1.11.0 multer: specifier: ^2.0.2 version: 2.1.1 @@ -4979,8 +4979,8 @@ packages: moment@2.30.1: resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} - morgan@1.10.1: - resolution: {integrity: sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==} + morgan@1.11.0: + resolution: {integrity: sha512-zSkVu3t18r39pw4ixfBKvfZi3y2UOqr7d4WYwcj3m8nXpEQK4rPO6GLzs/CExoRgmX3y9EjmmcXqv6jq0SK46g==} engines: {node: '>= 0.8.0'} ms@2.0.0: @@ -5176,10 +5176,6 @@ packages: resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} engines: {node: '>= 0.4'} - on-finished@2.3.0: - resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==} - engines: {node: '>= 0.8'} - on-finished@2.4.1: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} engines: {node: '>= 0.8'} @@ -12466,12 +12462,12 @@ snapshots: moment@2.30.1: {} - morgan@1.10.1: + morgan@1.11.0: dependencies: basic-auth: 2.0.1 debug: 2.6.9 depd: 2.0.0 - on-finished: 2.3.0 + on-finished: 2.4.1 on-headers: 1.1.0 transitivePeerDependencies: - supports-color @@ -12665,10 +12661,6 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 - on-finished@2.3.0: - dependencies: - ee-first: 1.1.1 - on-finished@2.4.1: dependencies: ee-first: 1.1.1 From 9eae1f2f4409c0668157d447f24537906068c653 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 14 Aug 2026 13:27:37 +0530 Subject: [PATCH 37/57] ci: bump ossf/scorecard-action (#1662) Bumps the minor-actions group with 1 update in the / directory: [ossf/scorecard-action](https://github.com/ossf/scorecard-action). Updates `ossf/scorecard-action` from 2.3.1 to 2.4.4 - [Release notes](https://github.com/ossf/scorecard-action/releases) - [Changelog](https://github.com/ossf/scorecard-action/blob/main/RELEASE.md) - [Commits](https://github.com/ossf/scorecard-action/compare/0864cf19026789058feabb7e87baa5f140aac736...2d1146689b8cda280b9bc96326124645441f03bc) --- updated-dependencies: - dependency-name: ossf/scorecard-action dependency-version: 2.4.3 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/scorecard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 971dc081f..35a432884 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -37,7 +37,7 @@ jobs: persist-credentials: false - name: "Run analysis" - uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1 + uses: ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc # v2.4.4 with: results_file: results.sarif results_format: sarif From f6630318a94a9ef64b08fe75183b198d2bf34ab9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 14 Aug 2026 13:27:51 +0530 Subject: [PATCH 38/57] build: bump redis in the minor-docker-compose group across 1 directory (#1709) Bumps the minor-docker-compose group with 1 update in the / directory: redis. Updates `redis` from 8.0-alpine to 8.10-alpine --- updated-dependencies: - dependency-name: redis dependency-version: 8.10-alpine dependency-type: direct:production dependency-group: minor-docker-compose ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docker-compose-dev.yml | 2 +- docker-compose.redis.yml | 2 +- docker-compose.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index 2aa4b0478..b4ef82954 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -12,7 +12,7 @@ services: # volumes: # - ./nats-server.conf:/nats-server.conf # Mount the config file redis: - image: redis:8.0-alpine + image: redis:8.10-alpine restart: always ports: - '6379:6379' diff --git a/docker-compose.redis.yml b/docker-compose.redis.yml index 9b2389a0d..f37765bb3 100644 --- a/docker-compose.redis.yml +++ b/docker-compose.redis.yml @@ -1,7 +1,7 @@ version: '3' services: redis: - image: redis:8.0-alpine + image: redis:8.10-alpine restart: always ports: - '6379:6379' diff --git a/docker-compose.yml b/docker-compose.yml index 38996d977..1a7358709 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,7 +13,7 @@ services: - ./nats-server.conf:/nats-server.conf:ro redis: container_name: credebl-redis - image: redis:8.0-alpine + image: redis:8.10-alpine restart: always ports: - '6379:6379' From 85139dc674e2f2e8a273b9a40fa3a1e0fb527bdf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 14 Aug 2026 13:28:10 +0530 Subject: [PATCH 39/57] chore(deps): bump uuid from 9.0.1 to 14.0.0 (#1658) Bumps [uuid](https://github.com/uuidjs/uuid) from 9.0.1 to 14.0.0. - [Release notes](https://github.com/uuidjs/uuid/releases) - [Changelog](https://github.com/uuidjs/uuid/blob/main/CHANGELOG.md) - [Commits](https://github.com/uuidjs/uuid/compare/v9.0.1...v14.0.0) --- updated-dependencies: - dependency-name: uuid dependency-version: 14.0.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- libs/common/package.json | 2 +- package.json | 2 +- pnpm-lock.yaml | 17 ++++++++++++----- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/libs/common/package.json b/libs/common/package.json index 7d396649a..3737b9f13 100644 --- a/libs/common/package.json +++ b/libs/common/package.json @@ -28,7 +28,7 @@ "dotenv": "^16.0.3", "nats": "^2.15.1", "rxjs": "^7.8.1", - "uuid": "^9.0.0" + "uuid": "^14.0.0" }, "devDependencies": { "@types/crypto-js": "^4.1.1", diff --git a/package.json b/package.json index 09e7838a2..7e6b9f730 100644 --- a/package.json +++ b/package.json @@ -120,7 +120,7 @@ "swagger-ui-express": "^5.0.1", "typeorm": "^0.3.31", "unzipper": "^0.12.3", - "uuid": "^9.0.1", + "uuid": "^14.0.0", "validator": "^13.15.35", "web-push": "^3.6.7", "winston": "^3.19.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 889c37ee5..0cd244812 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -369,8 +369,8 @@ importers: specifier: ^0.12.3 version: 0.12.3 uuid: - specifier: ^9.0.1 - version: 9.0.1 + specifier: ^14.0.0 + version: 14.0.0 validator: specifier: ^13.15.35 version: 13.15.35 @@ -532,8 +532,8 @@ importers: specifier: ^7.8.1 version: 7.8.2 uuid: - specifier: ^9.0.0 - version: 9.0.1 + specifier: ^14.0.0 + version: 14.0.0 devDependencies: '@types/crypto-js': specifier: ^4.1.1 @@ -6462,6 +6462,10 @@ packages: resolution: {integrity: sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==} hasBin: true + uuid@14.0.0: + resolution: {integrity: sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==} + hasBin: true + uuid@3.4.0: resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==} deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). @@ -14146,6 +14150,8 @@ snapshots: uuid@11.1.1: {} + uuid@14.0.0: {} + uuid@3.4.0: optional: true @@ -14153,7 +14159,8 @@ snapshots: uuid@8.3.2: {} - uuid@9.0.1: {} + uuid@9.0.1: + optional: true v8-compile-cache-lib@3.0.1: {} From e1a0112eed532a7c03c567d83ed7f4ca4ad458a5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 14 Aug 2026 13:32:06 +0530 Subject: [PATCH 40/57] chore(deps): bump multer from 2.1.1 to 2.2.0 (#1686) Bumps [multer](https://github.com/expressjs/multer) from 2.1.1 to 2.2.0. - [Release notes](https://github.com/expressjs/multer/releases) - [Changelog](https://github.com/expressjs/multer/blob/main/CHANGELOG.md) - [Commits](https://github.com/expressjs/multer/compare/v2.1.1...v2.2.0) --- updated-dependencies: - dependency-name: multer dependency-version: 2.2.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Signed-off-by: Ajay Jadhav Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- pnpm-lock.yaml | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 7e6b9f730..c25e3a4f1 100644 --- a/package.json +++ b/package.json @@ -90,7 +90,7 @@ "linebyline": "^1.3.0", "moment": "^2.30.1", "morgan": "^1.11.0", - "multer": "^2.0.2", + "multer": "^2.2.0", "nanoid": "^5.1.16", "nats": "^2.29.3", "nestjs-cls": "catalog:", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0cd244812..5c5db3a28 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -276,8 +276,8 @@ importers: specifier: ^1.11.0 version: 1.11.0 multer: - specifier: ^2.0.2 - version: 2.1.1 + specifier: ^2.2.0 + version: 2.2.0 nanoid: specifier: ^5.1.16 version: 5.1.16 @@ -5003,6 +5003,10 @@ packages: resolution: {integrity: sha512-mo+QTzKlx8R7E5ylSXxWzGoXoZbOsRMpyitcht8By2KHvMbf3tjwosZ/Mu/XYU6UuJ3VZnODIrak5ZrPiPyB6A==} engines: {node: '>= 10.16.0'} + multer@2.2.0: + resolution: {integrity: sha512-6rdyFg2kLrMh9Jee7/BMPuV9lEAd7lLW2YUpF9/YxR7njyoUwwQ0ZPh3TaIY50Sw6vlyD2HW3wGOkTS4P79xrQ==} + engines: {node: '>= 10.16.0'} + mute-stream@2.0.0: resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} engines: {node: ^18.17.0 || >=20.5.0} @@ -12505,6 +12509,13 @@ snapshots: concat-stream: 2.0.0 type-is: 1.6.18 + multer@2.2.0: + dependencies: + append-field: 1.0.0 + busboy: 1.6.0 + concat-stream: 2.0.0 + type-is: 1.6.18 + mute-stream@2.0.0: {} nanoid@5.1.16: {} From 7dcc5d4fff22fbdc64a4cd25cd598dce23247240 Mon Sep 17 00:00:00 2001 From: Ajay Jadhav Date: Fri, 14 Aug 2026 14:10:16 +0530 Subject: [PATCH 41/57] revert: bump uuid from 9.0.1 to 14.0.0 (#1658) (#1716) uuid 14 is ESM-only and cannot be loaded by the repo's CommonJS jest runtime (SyntaxError: Unexpected token 'export'), breaking 8+ suites that import uuid transitively via NATSClient. The bump carried no security fix (uuid 9.0.1 has no advisories), so revert to keep the test suite green while the ESM migration is handled separately. Verified: test results on this revert match the pre-bump baseline exactly (19 pre-existing broken suites, 5 failed tests, 56 passed). Signed-off-by: Ajay Jadhav --- libs/common/package.json | 2 +- package.json | 2 +- pnpm-lock.yaml | 17 +++++------------ 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/libs/common/package.json b/libs/common/package.json index 3737b9f13..7d396649a 100644 --- a/libs/common/package.json +++ b/libs/common/package.json @@ -28,7 +28,7 @@ "dotenv": "^16.0.3", "nats": "^2.15.1", "rxjs": "^7.8.1", - "uuid": "^14.0.0" + "uuid": "^9.0.0" }, "devDependencies": { "@types/crypto-js": "^4.1.1", diff --git a/package.json b/package.json index c25e3a4f1..f13223a5c 100644 --- a/package.json +++ b/package.json @@ -120,7 +120,7 @@ "swagger-ui-express": "^5.0.1", "typeorm": "^0.3.31", "unzipper": "^0.12.3", - "uuid": "^14.0.0", + "uuid": "^9.0.1", "validator": "^13.15.35", "web-push": "^3.6.7", "winston": "^3.19.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5c5db3a28..9ec07afb6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -369,8 +369,8 @@ importers: specifier: ^0.12.3 version: 0.12.3 uuid: - specifier: ^14.0.0 - version: 14.0.0 + specifier: ^9.0.1 + version: 9.0.1 validator: specifier: ^13.15.35 version: 13.15.35 @@ -532,8 +532,8 @@ importers: specifier: ^7.8.1 version: 7.8.2 uuid: - specifier: ^14.0.0 - version: 14.0.0 + specifier: ^9.0.0 + version: 9.0.1 devDependencies: '@types/crypto-js': specifier: ^4.1.1 @@ -6466,10 +6466,6 @@ packages: resolution: {integrity: sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==} hasBin: true - uuid@14.0.0: - resolution: {integrity: sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==} - hasBin: true - uuid@3.4.0: resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==} deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). @@ -14161,8 +14157,6 @@ snapshots: uuid@11.1.1: {} - uuid@14.0.0: {} - uuid@3.4.0: optional: true @@ -14170,8 +14164,7 @@ snapshots: uuid@8.3.2: {} - uuid@9.0.1: - optional: true + uuid@9.0.1: {} v8-compile-cache-lib@3.0.1: {} From fec49105afaed5cf5f54cc78f5b255d0e07f3e61 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 14 Aug 2026 14:47:52 +0530 Subject: [PATCH 42/57] chore(deps): bump the patch-updates group across 1 directory with 44 updates (#1718) Bumps the patch-updates group with 20 updates in the / directory: | Package | From | To | | --- | --- | --- | | [nodemailer](https://github.com/nodemailer/nodemailer) | `9.0.1` | `9.0.5` | | [papaparse](https://github.com/mholt/PapaParse) | `5.5.3` | `5.5.4` | | [qs](https://github.com/ljharb/qs) | `6.15.2` | `6.15.3` | | [unzipper](https://github.com/ZJONSSON/node-unzipper) | `0.12.3` | `0.12.5` | | [uuid](https://github.com/uuidjs/uuid) | `14.0.0` | `14.0.1` | | [@types/supertest](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/supertest) | `7.2.0` | `7.2.1` | | [ts-jest](https://github.com/kulshekhar/ts-jest) | `29.4.9` | `29.4.12` | | [@nestjs/bull](https://github.com/nestjs/bull) | `11.0.4` | `11.0.5` | | [@nestjs/cache-manager](https://github.com/nestjs/cache-manager) | `3.1.2` | `3.1.3` | | [@nestjs/cli](https://github.com/nestjs/nest-cli) | `11.0.10` | `11.0.24` | | [@nestjs/common](https://github.com/nestjs/nest/tree/HEAD/packages/common) | `11.1.19` | `11.1.29` | | [@nestjs/core](https://github.com/nestjs/nest/tree/HEAD/packages/core) | `11.1.19` | `11.1.29` | | [@nestjs/microservices](https://github.com/nestjs/nest/tree/HEAD/packages/microservices) | `11.1.19` | `11.1.29` | | [@nestjs/platform-express](https://github.com/nestjs/nest/tree/HEAD/packages/platform-express) | `11.1.19` | `11.1.29` | | [@nestjs/platform-socket.io](https://github.com/nestjs/nest/tree/HEAD/packages/platform-socket.io) | `11.1.19` | `11.1.29` | | [@nestjs/swagger](https://github.com/nestjs/swagger) | `11.4.2` | `11.4.6` | | [@nestjs/testing](https://github.com/nestjs/nest/tree/HEAD/packages/testing) | `11.1.19` | `11.1.29` | | [@nestjs/typeorm](https://github.com/nestjs/typeorm) | `11.0.1` | `11.0.3` | | [@nestjs/websockets](https://github.com/nestjs/nest/tree/HEAD/packages/websockets) | `11.1.19` | `11.1.29` | | [nestjs-cls](https://github.com/Papooch/nestjs-cls) | `6.2.0` | `6.2.1` | Updates `nodemailer` from 9.0.1 to 9.0.5 - [Release notes](https://github.com/nodemailer/nodemailer/releases) - [Changelog](https://github.com/nodemailer/nodemailer/blob/master/CHANGELOG.md) - [Commits](https://github.com/nodemailer/nodemailer/compare/v9.0.1...v9.0.5) Updates `papaparse` from 5.5.3 to 5.5.4 - [Release notes](https://github.com/mholt/PapaParse/releases) - [Changelog](https://github.com/mholt/PapaParse/blob/master/CHANGELOG.md) - [Commits](https://github.com/mholt/PapaParse/compare/5.5.3...5.5.4) Updates `qs` from 6.15.2 to 6.15.3 - [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md) - [Commits](https://github.com/ljharb/qs/compare/v6.15.2...v6.15.3) Updates `unzipper` from 0.12.3 to 0.12.5 - [Release notes](https://github.com/ZJONSSON/node-unzipper/releases) - [Commits](https://github.com/ZJONSSON/node-unzipper/commits) Updates `uuid` from 14.0.0 to 14.0.1 - [Release notes](https://github.com/uuidjs/uuid/releases) - [Changelog](https://github.com/uuidjs/uuid/blob/main/CHANGELOG.md) - [Commits](https://github.com/uuidjs/uuid/compare/v14.0.0...v14.0.1) Updates `protobufjs` from 8.7.2 to 7.6.5 - [Release notes](https://github.com/protobufjs/protobuf.js/releases) - [Changelog](https://github.com/protobufjs/protobuf.js/blob/protobufjs-v7.6.5/CHANGELOG.md) - [Commits](https://github.com/protobufjs/protobuf.js/compare/protobufjs-v8.7.2...protobufjs-v7.6.5) Updates `@types/supertest` from 7.2.0 to 7.2.1 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/supertest) Updates `ts-jest` from 29.4.9 to 29.4.12 - [Release notes](https://github.com/kulshekhar/ts-jest/releases) - [Changelog](https://github.com/kulshekhar/ts-jest/blob/main/CHANGELOG.md) - [Commits](https://github.com/kulshekhar/ts-jest/compare/v29.4.9...v29.4.12) Updates `@nestjs/bull` from 11.0.4 to 11.0.5 - [Release notes](https://github.com/nestjs/bull/releases) - [Commits](https://github.com/nestjs/bull/compare/@nestjs/bull@11.0.4...@nestjs/bull@11.0.5) Updates `@nestjs/cache-manager` from 3.1.2 to 3.1.3 - [Release notes](https://github.com/nestjs/cache-manager/releases) - [Commits](https://github.com/nestjs/cache-manager/compare/3.1.2...3.1.3) Updates `@nestjs/cli` from 11.0.10 to 11.0.24 - [Release notes](https://github.com/nestjs/nest-cli/releases) - [Commits](https://github.com/nestjs/nest-cli/compare/11.0.10...11.0.24) Updates `@nestjs/common` from 11.1.19 to 11.1.29 - [Release notes](https://github.com/nestjs/nest/releases) - [Commits](https://github.com/nestjs/nest/commits/v11.1.29/packages/common) Updates `@nestjs/core` from 11.1.19 to 11.1.29 - [Release notes](https://github.com/nestjs/nest/releases) - [Commits](https://github.com/nestjs/nest/commits/v11.1.29/packages/core) Updates `@nestjs/microservices` from 11.1.19 to 11.1.29 - [Release notes](https://github.com/nestjs/nest/releases) - [Commits](https://github.com/nestjs/nest/commits/v11.1.29/packages/microservices) Updates `@nestjs/platform-express` from 11.1.19 to 11.1.29 - [Release notes](https://github.com/nestjs/nest/releases) - [Commits](https://github.com/nestjs/nest/commits/v11.1.29/packages/platform-express) Updates `@nestjs/platform-socket.io` from 11.1.19 to 11.1.29 - [Release notes](https://github.com/nestjs/nest/releases) - [Commits](https://github.com/nestjs/nest/commits/v11.1.29/packages/platform-socket.io) Updates `@nestjs/swagger` from 11.4.2 to 11.4.6 - [Release notes](https://github.com/nestjs/swagger/releases) - [Commits](https://github.com/nestjs/swagger/compare/11.4.2...11.4.6) Updates `@nestjs/testing` from 11.1.19 to 11.1.29 - [Release notes](https://github.com/nestjs/nest/releases) - [Commits](https://github.com/nestjs/nest/commits/v11.1.29/packages/testing) Updates `@nestjs/typeorm` from 11.0.1 to 11.0.3 - [Release notes](https://github.com/nestjs/typeorm/releases) - [Commits](https://github.com/nestjs/typeorm/compare/11.0.1...11.0.3) Updates `@nestjs/websockets` from 11.1.19 to 11.1.29 - [Release notes](https://github.com/nestjs/nest/releases) - [Commits](https://github.com/nestjs/nest/commits/v11.1.29/packages/websockets) Updates `nestjs-cls` from 6.2.0 to 6.2.1 - [Release notes](https://github.com/Papooch/nestjs-cls/releases) - [Commits](https://github.com/Papooch/nestjs-cls/compare/nestjs-cls@6.2.0...nestjs-cls@6.2.1) Updates `@angular-devkit/core` from 19.2.15 to 19.2.24 - [Release notes](https://github.com/angular/angular-cli/releases) - [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular-cli/compare/19.2.15...v19.2.24) Updates `@angular-devkit/schematics-cli` from 19.2.15 to 19.2.27 - [Release notes](https://github.com/angular/angular-cli/releases) - [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular-cli/compare/19.2.15...v19.2.27) Updates `@angular-devkit/schematics` from 19.2.15 to 19.2.24 - [Release notes](https://github.com/angular/angular-cli/releases) - [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular-cli/compare/19.2.15...v19.2.24) Updates `@nestjs/bull-shared` from 11.0.4 to 11.0.5 - [Release notes](https://github.com/nestjs/bull/releases) - [Commits](https://github.com/nestjs/bull/compare/@nestjs/bull-shared@11.0.4...@nestjs/bull-shared@11.0.5) Updates `@types/superagent` from 8.1.9 to 8.1.11 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/superagent) Updates `ansis` from 4.1.0 to 4.2.0 - [Release notes](https://github.com/webdiscus/ansis/releases) - [Changelog](https://github.com/webdiscus/ansis/blob/master/CHANGELOG.md) - [Commits](https://github.com/webdiscus/ansis/compare/v4.1.0...v4.2.0) Updates `baseline-browser-mapping` from 2.10.27 to 2.11.14 - [Release notes](https://github.com/web-platform-dx/baseline-browser-mapping/releases) - [Commits](https://github.com/web-platform-dx/baseline-browser-mapping/compare/v2.10.27...v2.11.14) Updates `browserslist` from 4.28.2 to 4.28.8 - [Release notes](https://github.com/browserslist/browserslist/releases) - [Changelog](https://github.com/browserslist/browserslist/blob/main/CHANGELOG.md) - [Commits](https://github.com/browserslist/browserslist/compare/4.28.2...4.28.8) Updates `caniuse-lite` from 1.0.30001792 to 1.0.30001809 - [Commits](https://github.com/browserslist/caniuse-lite/compare/1.0.30001792...1.0.30001809) Updates `chardet` from 2.1.1 to 2.2.0 - [Release notes](https://github.com/runk/node-chardet/releases) - [Commits](https://github.com/runk/node-chardet/compare/v2.1.1...v2.2.0) Updates `electron-to-chromium` from 1.5.352 to 1.5.406 - [Changelog](https://github.com/Kilian/electron-to-chromium/blob/main/CHANGELOG.md) - [Commits](https://github.com/Kilian/electron-to-chromium/compare/v1.5.352...v1.5.406) Updates `engine.io` from 6.6.7 to 6.6.9 - [Release notes](https://github.com/socketio/socket.io/releases) - [Changelog](https://github.com/socketio/socket.io/blob/main/CHANGELOG.md) - [Commits](https://github.com/socketio/socket.io/compare/engine.io@6.6.7...engine.io@6.6.9) Updates `es-module-lexer` from 1.7.0 to 2.3.1 - [Release notes](https://github.com/guybedford/es-module-lexer/releases) - [Commits](https://github.com/guybedford/es-module-lexer/compare/1.7.0...2.3.1) Updates `js-yaml` from 3.14.2 to 3.15.1 - [Changelog](https://github.com/nodeca/js-yaml/blob/3.15.1/CHANGELOG.md) - [Commits](https://github.com/nodeca/js-yaml/compare/3.14.2...3.15.1) Updates `node-releases` from 2.0.38 to 2.0.53 - [Commits](https://github.com/chicoxyzzy/node-releases/compare/v2.0.38...v2.0.53) Updates `socket.io-adapter` from 2.5.6 to 2.5.8 - [Release notes](https://github.com/socketio/socket.io/releases) - [Changelog](https://github.com/socketio/socket.io/blob/main/CHANGELOG.md) - [Commits](https://github.com/socketio/socket.io/compare/socket.io-adapter@2.5.6...socket.io-adapter@2.5.8) Updates `swagger-ui-dist` from 5.32.4 to 5.32.5 - [Release notes](https://github.com/swagger-api/swagger-ui/releases) - [Commits](https://github.com/swagger-api/swagger-ui/compare/v5.32.4...v5.32.5) Updates `terser-webpack-plugin` from 5.5.0 to 5.6.1 - [Release notes](https://github.com/webpack/minimizer-webpack-plugin/releases) - [Changelog](https://github.com/webpack/minimizer-webpack-plugin/blob/main/CHANGELOG.md) - [Commits](https://github.com/webpack/minimizer-webpack-plugin/compare/v5.5.0...v5.6.1) Updates `terser` from 5.47.1 to 5.50.0 - [Changelog](https://github.com/terser/terser/blob/master/CHANGELOG.md) - [Commits](https://github.com/terser/terser/compare/v5.47.1...v5.50.0) Updates `update-browserslist-db` from 1.2.3 to 1.3.1 - [Release notes](https://github.com/browserslist/update-db/releases) - [Changelog](https://github.com/browserslist/update-db/blob/main/CHANGELOG.md) - [Commits](https://github.com/browserslist/update-db/compare/1.2.3...1.3.1) Updates `watchpack` from 2.5.1 to 2.5.2 - [Release notes](https://github.com/webpack/watchpack/releases) - [Changelog](https://github.com/webpack/watchpack/blob/main/CHANGELOG.md) - [Commits](https://github.com/webpack/watchpack/compare/v2.5.1...v2.5.2) Updates `webpack-sources` from 3.4.1 to 3.5.1 - [Release notes](https://github.com/webpack/webpack-sources/releases) - [Changelog](https://github.com/webpack/webpack-sources/blob/main/CHANGELOG.md) - [Commits](https://github.com/webpack/webpack-sources/compare/v3.4.1...v3.5.1) Updates `webpack` from 5.100.2 to 5.106.2 - [Release notes](https://github.com/webpack/webpack/releases) - [Changelog](https://github.com/webpack/webpack/blob/main/CHANGELOG.md) - [Commits](https://github.com/webpack/webpack/compare/v5.100.2...v5.106.2) --- updated-dependencies: - dependency-name: nodemailer dependency-version: 9.0.5 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: patch-updates - dependency-name: papaparse dependency-version: 5.5.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: patch-updates - dependency-name: qs dependency-version: 6.15.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: patch-updates - dependency-name: unzipper dependency-version: 0.12.5 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: patch-updates - dependency-name: uuid dependency-version: 14.0.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: patch-updates - dependency-name: protobufjs dependency-version: 7.6.5 dependency-type: direct:production update-type: version-update:semver-major dependency-group: patch-updates - dependency-name: "@types/supertest" dependency-version: 7.2.1 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: patch-updates - dependency-name: ts-jest dependency-version: 29.4.12 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: patch-updates - dependency-name: "@nestjs/bull" dependency-version: 11.0.5 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: patch-updates - dependency-name: "@nestjs/cache-manager" dependency-version: 3.1.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: patch-updates - dependency-name: "@nestjs/cli" dependency-version: 11.0.24 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: patch-updates - dependency-name: "@nestjs/common" dependency-version: 11.1.29 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: patch-updates - dependency-name: "@nestjs/core" dependency-version: 11.1.29 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: patch-updates - dependency-name: "@nestjs/microservices" dependency-version: 11.1.29 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: patch-updates - dependency-name: "@nestjs/platform-express" dependency-version: 11.1.29 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: patch-updates - dependency-name: "@nestjs/platform-socket.io" dependency-version: 11.1.29 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: patch-updates - dependency-name: "@nestjs/swagger" dependency-version: 11.4.6 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: patch-updates - dependency-name: "@nestjs/testing" dependency-version: 11.1.29 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: patch-updates - dependency-name: "@nestjs/typeorm" dependency-version: 11.0.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: patch-updates - dependency-name: "@nestjs/websockets" dependency-version: 11.1.29 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: patch-updates - dependency-name: nestjs-cls dependency-version: 6.2.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: patch-updates - dependency-name: "@angular-devkit/core" dependency-version: 19.2.24 dependency-type: indirect update-type: version-update:semver-patch dependency-group: patch-updates - dependency-name: "@angular-devkit/schematics-cli" dependency-version: 19.2.27 dependency-type: indirect update-type: version-update:semver-patch dependency-group: patch-updates - dependency-name: "@angular-devkit/schematics" dependency-version: 19.2.24 dependency-type: indirect update-type: version-update:semver-patch dependency-group: patch-updates - dependency-name: "@nestjs/bull-shared" dependency-version: 11.0.5 dependency-type: indirect update-type: version-update:semver-patch dependency-group: patch-updates - dependency-name: "@types/superagent" dependency-version: 8.1.11 dependency-type: indirect update-type: version-update:semver-patch dependency-group: patch-updates - dependency-name: ansis dependency-version: 4.2.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: patch-updates - dependency-name: baseline-browser-mapping dependency-version: 2.11.14 dependency-type: indirect update-type: version-update:semver-minor dependency-group: patch-updates - dependency-name: browserslist dependency-version: 4.28.8 dependency-type: indirect update-type: version-update:semver-patch dependency-group: patch-updates - dependency-name: caniuse-lite dependency-version: 1.0.30001809 dependency-type: indirect update-type: version-update:semver-patch dependency-group: patch-updates - dependency-name: chardet dependency-version: 2.2.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: patch-updates - dependency-name: electron-to-chromium dependency-version: 1.5.406 dependency-type: indirect update-type: version-update:semver-patch dependency-group: patch-updates - dependency-name: engine.io dependency-version: 6.6.9 dependency-type: indirect update-type: version-update:semver-patch dependency-group: patch-updates - dependency-name: es-module-lexer dependency-version: 2.3.1 dependency-type: indirect update-type: version-update:semver-major dependency-group: patch-updates - dependency-name: js-yaml dependency-version: 3.15.1 dependency-type: indirect update-type: version-update:semver-minor dependency-group: patch-updates - dependency-name: node-releases dependency-version: 2.0.53 dependency-type: indirect update-type: version-update:semver-patch dependency-group: patch-updates - dependency-name: socket.io-adapter dependency-version: 2.5.8 dependency-type: indirect update-type: version-update:semver-patch dependency-group: patch-updates - dependency-name: swagger-ui-dist dependency-version: 5.32.5 dependency-type: indirect update-type: version-update:semver-patch dependency-group: patch-updates - dependency-name: terser-webpack-plugin dependency-version: 5.6.1 dependency-type: indirect update-type: version-update:semver-minor dependency-group: patch-updates - dependency-name: terser dependency-version: 5.50.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: patch-updates - dependency-name: update-browserslist-db dependency-version: 1.3.1 dependency-type: indirect update-type: version-update:semver-minor dependency-group: patch-updates - dependency-name: watchpack dependency-version: 2.5.2 dependency-type: indirect update-type: version-update:semver-patch dependency-group: patch-updates - dependency-name: webpack-sources dependency-version: 3.5.1 dependency-type: indirect update-type: version-update:semver-minor dependency-group: patch-updates - dependency-name: webpack dependency-version: 5.106.2 dependency-type: indirect update-type: version-update:semver-minor dependency-group: patch-updates ... Signed-off-by: dependabot[bot] Signed-off-by: Ajay Jadhav Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 12 +- pnpm-lock.yaml | 978 +++++++++++++++++++++++--------------------- pnpm-workspace.yaml | 26 +- 3 files changed, 520 insertions(+), 496 deletions(-) diff --git a/package.json b/package.json index f13223a5c..a627a5cc3 100644 --- a/package.json +++ b/package.json @@ -99,9 +99,9 @@ "nestjs-typeorm-paginate": "catalog:", "node-html-to-image": "^4.0.0", "node-qpdf2": "^2.0.0", - "nodemailer": "^9.0.1", + "nodemailer": "^9.0.5", "p-limit": "^3.1.0", - "papaparse": "^5.5.3", + "papaparse": "^5.5.4", "passport": "^0.6.0", "passport-jwt": "^4.0.1", "passport-local": "^1.0.0", @@ -110,7 +110,7 @@ "pg": "^8.18.0", "puppeteer": "^21.11.0", "qrcode": "^1.5.4", - "qs": "^6.15.2", + "qs": "^6.15.3", "reflect-metadata": "^0.1.14", "resend": "^6.9.2", "rimraf": "^3.0.2", @@ -119,7 +119,7 @@ "socket.io-client": "^4.8.3", "swagger-ui-express": "^5.0.1", "typeorm": "^0.3.31", - "unzipper": "^0.12.3", + "unzipper": "^0.12.5", "uuid": "^9.0.1", "validator": "^13.15.35", "web-push": "^3.6.7", @@ -140,7 +140,7 @@ "@types/node": "^20.19.33", "@types/passport-jwt": "3.0.9", "@types/passport-local": "^1.0.38", - "@types/supertest": "^7.2.0", + "@types/supertest": "^7.2.1", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", "eslint": "^8.57.1", @@ -156,7 +156,7 @@ "prettier": "^3.8.1", "prisma": "^5.22.0", "supertest": "^7.2.2", - "ts-jest": "^29.4.6", + "ts-jest": "^29.4.12", "ts-loader": "^9.5.4", "ts-node": "^10.9.2", "tsconfig-paths": "^4.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9ec07afb6..409d365ca 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,38 +10,38 @@ catalogs: specifier: ^4.0.1 version: 4.0.1 '@nestjs/bull': - specifier: ^11.0.3 - version: 11.0.4 + specifier: ^11.0.5 + version: 11.0.5 '@nestjs/cache-manager': - specifier: ^3.0.1 - version: 3.1.2 + specifier: ^3.1.3 + version: 3.1.3 '@nestjs/cli': - specifier: 11.0.10 - version: 11.0.10 + specifier: 11.0.24 + version: 11.0.24 '@nestjs/common': - specifier: ^11.1.6 - version: 11.1.19 + specifier: ^11.1.29 + version: 11.1.29 '@nestjs/config': specifier: ^4.0.2 version: 4.0.4 '@nestjs/core': - specifier: ^11.1.6 - version: 11.1.19 + specifier: ^11.1.29 + version: 11.1.29 '@nestjs/jwt': specifier: ^11.0.0 version: 11.0.2 '@nestjs/microservices': - specifier: ^11.1.6 - version: 11.1.19 + specifier: ^11.1.29 + version: 11.1.29 '@nestjs/passport': specifier: ^11.0.5 version: 11.0.5 '@nestjs/platform-express': - specifier: ^11.1.6 - version: 11.1.19 + specifier: ^11.1.29 + version: 11.1.29 '@nestjs/platform-socket.io': - specifier: ^11.1.6 - version: 11.1.19 + specifier: ^11.1.29 + version: 11.1.29 '@nestjs/schedule': specifier: ^6.0.1 version: 6.1.3 @@ -49,20 +49,20 @@ catalogs: specifier: ^11.0.7 version: 11.1.0 '@nestjs/swagger': - specifier: ^11.2.0 - version: 11.4.2 + specifier: ^11.4.6 + version: 11.4.6 '@nestjs/testing': - specifier: ^11.1.6 - version: 11.1.19 + specifier: ^11.1.29 + version: 11.1.29 '@nestjs/typeorm': - specifier: ^11.0.0 - version: 11.0.1 + specifier: ^11.0.3 + version: 11.0.3 '@nestjs/websockets': - specifier: ^11.1.6 - version: 11.1.19 + specifier: ^11.1.29 + version: 11.1.29 nestjs-cls: - specifier: ^6.0.1 - version: 6.2.0 + specifier: ^6.2.1 + version: 6.2.1 nestjs-rate-limiter: specifier: ^3.1.0 version: 3.1.0 @@ -82,49 +82,49 @@ importers: version: 3.1044.0 '@nestjs/axios': specifier: 'catalog:' - version: 4.0.1(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(axios@1.18.0)(rxjs@7.8.2) + version: 4.0.1(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(axios@1.18.0)(rxjs@7.8.2) '@nestjs/bull': specifier: 'catalog:' - version: 11.0.4(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(bull@4.16.5) + version: 11.0.5(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.29)(bull@4.16.5) '@nestjs/cache-manager': specifier: 'catalog:' - version: 3.1.2(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(cache-manager@5.7.6)(keyv@4.5.4)(rxjs@7.8.2) + version: 3.1.3(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.29)(cache-manager@5.7.6)(keyv@4.5.4)(rxjs@7.8.2) '@nestjs/common': specifier: 'catalog:' - version: 11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) + version: 11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) '@nestjs/config': specifier: 'catalog:' - version: 4.0.4(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(rxjs@7.8.2) + version: 4.0.4(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(rxjs@7.8.2) '@nestjs/core': specifier: 'catalog:' - version: 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@11.1.19)(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.1.14)(rxjs@7.8.2) + version: 11.1.29(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@11.1.29)(@nestjs/platform-express@11.1.29)(@nestjs/websockets@11.1.29)(reflect-metadata@0.1.14)(rxjs@7.8.2) '@nestjs/jwt': specifier: 'catalog:' - version: 11.0.2(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2)) + version: 11.0.2(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2)) '@nestjs/microservices': specifier: 'catalog:' - version: 11.1.19(@grpc/grpc-js@1.14.4)(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/websockets@11.1.19)(cache-manager@5.7.6)(ioredis@5.10.1)(nats@2.29.3)(reflect-metadata@0.1.14)(rxjs@7.8.2) + version: 11.1.29(@grpc/grpc-js@1.14.4)(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.29)(@nestjs/websockets@11.1.29)(cache-manager@5.7.6)(ioredis@5.10.1)(nats@2.29.3)(reflect-metadata@0.1.14)(rxjs@7.8.2) '@nestjs/passport': specifier: 'catalog:' - version: 11.0.5(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(passport@0.6.0) + version: 11.0.5(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(passport@0.6.0) '@nestjs/platform-express': specifier: 'catalog:' - version: 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19) + version: 11.1.29(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.29) '@nestjs/platform-socket.io': specifier: 'catalog:' - version: 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/websockets@11.1.19)(rxjs@7.8.2) + version: 11.1.29(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/websockets@11.1.29)(rxjs@7.8.2) '@nestjs/schedule': specifier: 'catalog:' - version: 6.1.3(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19) + version: 6.1.3(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.29) '@nestjs/swagger': specifier: 'catalog:' - version: 11.4.2(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14) + version: 11.4.6(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.29)(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14) '@nestjs/typeorm': specifier: 'catalog:' - version: 11.0.1(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(reflect-metadata@0.1.14)(rxjs@7.8.2)(typeorm@0.3.31(ioredis@5.10.1)(pg@8.20.0)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3))) + version: 11.0.3(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.29)(reflect-metadata@0.1.14)(rxjs@7.8.2)(typeorm@0.3.31(ioredis@5.10.1)(pg@8.20.0)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3))) '@nestjs/websockets': specifier: 'catalog:' - version: 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/platform-socket.io@11.1.19)(reflect-metadata@0.1.14)(rxjs@7.8.2) + version: 11.1.29(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.29)(@nestjs/platform-socket.io@11.1.29)(reflect-metadata@0.1.14)(rxjs@7.8.2) '@opentelemetry/api': specifier: ^1.9.0 version: 1.9.1 @@ -286,7 +286,7 @@ importers: version: 2.29.3 nestjs-cls: specifier: 'catalog:' - version: 6.2.0(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(reflect-metadata@0.1.14)(rxjs@7.8.2) + version: 6.2.1(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.29)(reflect-metadata@0.1.14)(rxjs@7.8.2) nestjs-rate-limiter: specifier: 'catalog:' version: 3.1.0 @@ -295,7 +295,7 @@ importers: version: 1.0.9 nestjs-typeorm-paginate: specifier: 'catalog:' - version: 4.1.0(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(typeorm@0.3.31(ioredis@5.10.1)(pg@8.20.0)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3))) + version: 4.1.0(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(typeorm@0.3.31(ioredis@5.10.1)(pg@8.20.0)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3))) node-html-to-image: specifier: ^4.0.0 version: 4.0.0 @@ -303,14 +303,14 @@ importers: specifier: ^2.0.0 version: 2.0.0 nodemailer: - specifier: ^9.0.1 - version: 9.0.1 + specifier: ^9.0.5 + version: 9.0.5 p-limit: specifier: ^3.1.0 version: 3.1.0 papaparse: - specifier: ^5.5.3 - version: 5.5.3 + specifier: ^5.5.4 + version: 5.5.4 passport: specifier: ^0.6.0 version: 0.6.0 @@ -331,7 +331,7 @@ importers: version: 8.20.0 protobufjs: specifier: ^7.6.5 - version: 8.7.2 + version: 7.6.5 puppeteer: specifier: ^21.11.0 version: 21.11.0(typescript@5.9.3) @@ -339,8 +339,8 @@ importers: specifier: ^1.5.4 version: 1.5.4 qs: - specifier: ^6.15.2 - version: 6.15.2 + specifier: ^6.15.3 + version: 6.15.3 reflect-metadata: specifier: ^0.1.14 version: 0.1.14 @@ -366,8 +366,8 @@ importers: specifier: ^0.3.31 version: 0.3.31(ioredis@5.10.1)(pg@8.20.0)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)) unzipper: - specifier: ^0.12.3 - version: 0.12.3 + specifier: ^0.12.5 + version: 0.12.5 uuid: specifier: ^9.0.1 version: 9.0.1 @@ -389,13 +389,13 @@ importers: devDependencies: '@nestjs/cli': specifier: 'catalog:' - version: 11.0.10(@types/node@20.19.39)(prettier@3.8.3) + version: 11.0.24(@types/node@20.19.39)(prettier@3.8.3) '@nestjs/schematics': specifier: 'catalog:' version: 11.1.0(chokidar@4.0.3)(prettier@3.8.3)(typescript@5.9.3) '@nestjs/testing': specifier: 'catalog:' - version: 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/microservices@11.1.19)(@nestjs/platform-express@11.1.19) + version: 11.1.29(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.29)(@nestjs/microservices@11.1.29)(@nestjs/platform-express@11.1.29) '@types/express': specifier: ^4.17.25 version: 4.17.25 @@ -415,8 +415,8 @@ importers: specifier: ^1.0.38 version: 1.0.38 '@types/supertest': - specifier: ^7.2.0 - version: 7.2.0 + specifier: ^7.2.1 + version: 7.2.1 '@typescript-eslint/eslint-plugin': specifier: ^6.21.0 version: 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3) @@ -463,11 +463,11 @@ importers: specifier: ^7.2.2 version: 7.2.2 ts-jest: - specifier: ^29.4.6 - version: 29.4.9(@babel/core@7.29.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.0))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)))(typescript@5.9.3) + specifier: ^29.4.12 + version: 29.4.12(@babel/core@7.29.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.0))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)))(typescript@5.9.3) ts-loader: specifier: ^9.5.4 - version: 9.5.7(typescript@5.9.3)(webpack@5.100.2) + version: 9.5.7(typescript@5.9.3)(webpack@5.106.2) ts-node: specifier: ^10.9.2 version: 10.9.2(@types/node@20.19.39)(typescript@5.9.3) @@ -488,25 +488,25 @@ importers: dependencies: '@nestjs/axios': specifier: 'catalog:' - version: 4.0.1(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(axios@1.18.0)(rxjs@7.8.2) + version: 4.0.1(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(axios@1.18.0)(rxjs@7.8.2) '@nestjs/cache-manager': specifier: 'catalog:' - version: 3.1.2(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(cache-manager@5.7.6)(keyv@4.5.4)(rxjs@7.8.2) + version: 3.1.3(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.29)(cache-manager@5.7.6)(keyv@4.5.4)(rxjs@7.8.2) '@nestjs/common': specifier: 'catalog:' - version: 11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) + version: 11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) '@nestjs/core': specifier: 'catalog:' - version: 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@11.1.19)(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.1.14)(rxjs@7.8.2) + version: 11.1.29(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@11.1.29)(@nestjs/platform-express@11.1.29)(@nestjs/websockets@11.1.29)(reflect-metadata@0.1.14)(rxjs@7.8.2) '@nestjs/microservices': specifier: 'catalog:' - version: 11.1.19(@grpc/grpc-js@1.14.4)(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/websockets@11.1.19)(cache-manager@5.7.6)(ioredis@5.10.1)(nats@2.29.3)(reflect-metadata@0.1.14)(rxjs@7.8.2) + version: 11.1.29(@grpc/grpc-js@1.14.4)(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.29)(@nestjs/websockets@11.1.29)(cache-manager@5.7.6)(ioredis@5.10.1)(nats@2.29.3)(reflect-metadata@0.1.14)(rxjs@7.8.2) '@nestjs/swagger': specifier: 'catalog:' - version: 11.4.2(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14) + version: 11.4.6(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.29)(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14) '@nestjs/testing': specifier: 'catalog:' - version: 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/microservices@11.1.19)(@nestjs/platform-express@11.1.19) + version: 11.1.29(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.29)(@nestjs/microservices@11.1.29)(@nestjs/platform-express@11.1.29) '@prisma/client': specifier: ^5.1.1 version: 5.22.0(prisma@5.22.0) @@ -552,13 +552,13 @@ importers: dependencies: '@nestjs/common': specifier: 'catalog:' - version: 11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) + version: 11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) '@nestjs/microservices': specifier: 'catalog:' - version: 11.1.19(@grpc/grpc-js@1.14.4)(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/websockets@11.1.19)(cache-manager@5.7.6)(ioredis@5.10.1)(nats@2.29.3)(reflect-metadata@0.1.14)(rxjs@7.8.2) + version: 11.1.29(@grpc/grpc-js@1.14.4)(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.29)(@nestjs/websockets@11.1.29)(cache-manager@5.7.6)(ioredis@5.10.1)(nats@2.29.3)(reflect-metadata@0.1.14)(rxjs@7.8.2) '@nestjs/testing': specifier: 'catalog:' - version: 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/microservices@11.1.19)(@nestjs/platform-express@11.1.19) + version: 11.1.29(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.29)(@nestjs/microservices@11.1.29)(@nestjs/platform-express@11.1.29) aws-sdk: specifier: ^2.1510.0 version: 2.1693.0 @@ -575,8 +575,8 @@ importers: packages: - '@angular-devkit/core@19.2.15': - resolution: {integrity: sha512-pU2RZYX6vhd7uLSdLwPnuBcr0mXJSjp3EgOXKsrlQFQZevc+Qs+2JdXgIElnOT/aDqtRtriDmLlSbtdE8n3ZbA==} + '@angular-devkit/core@19.2.24': + resolution: {integrity: sha512-Kd49warf6U/EyWe5BszF/eebN3zQ3bk7tgfEljAw8q/rX95UUtriJubWvp6pgzHfzBA4jwq8f+QiNZB8eBEXPA==} engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} peerDependencies: chokidar: ^4.0.0 @@ -584,8 +584,8 @@ packages: chokidar: optional: true - '@angular-devkit/core@19.2.24': - resolution: {integrity: sha512-Kd49warf6U/EyWe5BszF/eebN3zQ3bk7tgfEljAw8q/rX95UUtriJubWvp6pgzHfzBA4jwq8f+QiNZB8eBEXPA==} + '@angular-devkit/core@19.2.27': + resolution: {integrity: sha512-3amNzoCVSKd7ah6l6lBQL4onwwJvqvam7FMoQBILrxtW5LB5ezh8gMSPuA4zJjKjoRzf9uoWdlzqv/84I52xZA==} engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} peerDependencies: chokidar: ^4.0.0 @@ -593,19 +593,19 @@ packages: chokidar: optional: true - '@angular-devkit/schematics-cli@19.2.15': - resolution: {integrity: sha512-1ESFmFGMpGQmalDB3t2EtmWDGv6gOFYBMxmHO2f1KI/UDl8UmZnCGL4mD3EWo8Hv0YIsZ9wOH9Q7ZHNYjeSpzg==} + '@angular-devkit/schematics-cli@19.2.27': + resolution: {integrity: sha512-wHYH6SVXVykhLzovUHtYor3Nl4SpIiITi7r9DQDaKYUD4hpRBx25W6N9eGuakT9Vd5tV/x6wmvQFWQZQwFB7eA==} engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} hasBin: true - '@angular-devkit/schematics@19.2.15': - resolution: {integrity: sha512-kNOJ+3vekJJCQKWihNmxBkarJzNW09kP5a9E1SRNiQVNOUEeSwcRR0qYotM65nx821gNzjjhJXnAZ8OazWldrg==} - engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} - '@angular-devkit/schematics@19.2.24': resolution: {integrity: sha512-lnw+ZM1Io+cJAkReC0NPDjqObL8NtKzKIkdgEEKC8CUmkhurYhedbicN8Y8NYHgG1uLd2GozW3+/QqPRZaN+Lw==} engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + '@angular-devkit/schematics@19.2.27': + resolution: {integrity: sha512-/PZmyAlb2NGWPikRRuiWLdfHQd8Wrx6lX4HqvTcaDhlU43M3T0ud4PH2T3QDp7BzHYY92xtD8iPxX2asg67G1A==} + engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + '@aws-crypto/sha256-browser@5.2.0': resolution: {integrity: sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==} @@ -735,6 +735,10 @@ packages: resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} engines: {node: '>=6.9.0'} + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + '@babel/compat-data@7.29.3': resolution: {integrity: sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==} engines: {node: '>=6.9.0'} @@ -777,6 +781,10 @@ packages: resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.27.1': resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} engines: {node: '>=6.9.0'} @@ -1112,8 +1120,8 @@ packages: '@types/node': optional: true - '@inquirer/prompts@7.3.2': - resolution: {integrity: sha512-G1ytyOoHh5BphmEBxSwALin3n1KGNYB6yImbICcRQdzXfOGbuJ9Jske/Of5Sebk339NSGGNfUshnzK8YWkTPsQ==} + '@inquirer/prompts@7.10.1': + resolution: {integrity: sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -1121,8 +1129,8 @@ packages: '@types/node': optional: true - '@inquirer/prompts@7.8.0': - resolution: {integrity: sha512-JHwGbQ6wjf1dxxnalDYpZwZxUEosT+6CPGD9Zh4sm9WXdtUp9XODCQD3NjSTmu+0OAyxWXNOqf0spjIymJa2Tw==} + '@inquirer/prompts@7.3.2': + resolution: {integrity: sha512-G1ytyOoHh5BphmEBxSwALin3n1KGNYB6yImbICcRQdzXfOGbuJ9Jske/Of5Sebk339NSGGNfUshnzK8YWkTPsQ==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -1173,10 +1181,6 @@ packages: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} - '@isaacs/cliui@9.0.0': - resolution: {integrity: sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==} - engines: {node: '>=18'} - '@istanbuljs/load-nyc-config@1.1.0': resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} engines: {node: '>=8'} @@ -1320,21 +1324,21 @@ packages: axios: ^1.3.1 rxjs: ^7.0.0 - '@nestjs/bull-shared@11.0.4': - resolution: {integrity: sha512-VBJcDHSAzxQnpcDfA0kt9MTGUD1XZzfByV70su0W0eDCQ9aqIEBlzWRW21tv9FG9dIut22ysgDidshdjlnczLw==} + '@nestjs/bull-shared@11.0.5': + resolution: {integrity: sha512-QI7GHPk4oePBwszAZn/Kz5hgB79KijB1RDoo+E/BrSt33sHUrvRZXVyO0+knZleyloRYy0LT2VwsqSP/aQvg4Q==} peerDependencies: '@nestjs/common': ^10.0.0 || ^11.0.0 '@nestjs/core': ^10.0.0 || ^11.0.0 - '@nestjs/bull@11.0.4': - resolution: {integrity: sha512-QVz2PR/rJF/isy7otVnMTSqLf/O71p9Ka7lBZt9Gm+NQFv8fcH2L11GL7TA0whyCcw/kAX5iRepUXz/wed4JoA==} + '@nestjs/bull@11.0.5': + resolution: {integrity: sha512-z1TKz7NHlsZv5ss27Lp32jzPe8ynw/COWoywMu32k8VXWsDSAy7mvDSBEX3Go0ekO7CzhAzqPWrl//GU3QuMFA==} peerDependencies: '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 '@nestjs/core': ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 bull: ^3.3 || ^4.0.0 - '@nestjs/cache-manager@3.1.2': - resolution: {integrity: sha512-Eglt8lUzC3Q3OZ2hFt4vLZ190M94YSJXUiKo67K/zlUgZQGtvxL0AYeKbG96x8+1gJTF7QhFpYw/RkQ28416Mw==} + '@nestjs/cache-manager@3.1.3': + resolution: {integrity: sha512-HMtiOfHz75NZX7mJn1VnZGLSachVI04TnUc5wvEogIaKwk5BDQHgtP5htxreizjv7oxKalJbuTyxtiF6bE+bgQ==} peerDependencies: '@nestjs/common': ^9.0.0 || ^10.0.0 || ^11.0.0 '@nestjs/core': ^9.0.0 || ^10.0.0 || ^11.0.0 @@ -1342,12 +1346,12 @@ packages: keyv: '>=5' rxjs: ^7.8.1 - '@nestjs/cli@11.0.10': - resolution: {integrity: sha512-4waDT0yGWANg0pKz4E47+nUrqIJv/UqrZ5wLPkCqc7oMGRMWKAaw1NDZ9rKsaqhqvxb2LfI5+uXOWr4yi94DOQ==} + '@nestjs/cli@11.0.24': + resolution: {integrity: sha512-aIHxQLSYtXShifA3zwWIeznEsZnNa3Iz2QRykFj+sl9IcbERBHr5nH87FRgywM+He3NxoF5WazHfR8FsmVeWxw==} engines: {node: '>= 20.11'} hasBin: true peerDependencies: - '@swc/cli': ^0.1.62 || ^0.3.0 || ^0.4.0 || ^0.5.0 || ^0.6.0 || ^0.7.0 + '@swc/cli': ^0.1.62 || ^0.3.0 || ^0.4.0 || ^0.5.0 || ^0.6.0 || ^0.7.0 || ^0.8.0 '@swc/core': ^1.3.62 peerDependenciesMeta: '@swc/cli': @@ -1355,8 +1359,8 @@ packages: '@swc/core': optional: true - '@nestjs/common@11.1.19': - resolution: {integrity: sha512-qeiTt2tv+e5QyDKqG8HlVZb2wx64FEaSGFJouqTSRs+kG44iTfl3xlz1XqVped+rihx4hmjWgL5gkhtdK3E6+Q==} + '@nestjs/common@11.1.29': + resolution: {integrity: sha512-zkeNRlfiQIH/044r5zphjNzKYxBRC4O00onTdCsH2qq0R30Ixo0gOS9so5TadEbJJCuGNm0Vx3PBE2FG2vkBdA==} peerDependencies: class-transformer: '>=0.4.1' class-validator: '>=0.13.2' @@ -1374,8 +1378,8 @@ packages: '@nestjs/common': ^10.0.0 || ^11.0.0 rxjs: ^7.1.0 - '@nestjs/core@11.1.19': - resolution: {integrity: sha512-6nJkWa2efrYi+XlU686J9y5L7OvxpLVjT0T/sxRKE7Jvpffiihelup4WSvLvRhdHDjj/5SuoWEwqReXAaaeHmw==} + '@nestjs/core@11.1.29': + resolution: {integrity: sha512-ANXnZxirMNAY+JuRHCIYIdTfbhX3rizeSbJloub/4EYwpIfCuUoaz9woOofWmywYYc3gsVkLxVZV03gqntI8vA==} engines: {node: '>= 20'} peerDependencies: '@nestjs/common': ^11.0.0 @@ -1410,8 +1414,8 @@ packages: class-validator: optional: true - '@nestjs/microservices@11.1.19': - resolution: {integrity: sha512-3Oja56ydTlSaui19/i7gYM0MMqz/w4UR2aqZeL4K8B+Fq0Ztg3zHb8et76atToJGpSCevJLEsoEMOMaGgzRwfg==} + '@nestjs/microservices@11.1.29': + resolution: {integrity: sha512-Z97VBDwgarzj08ZoC5ItJDIkUiDsAXm3kx2lWc0YtpnOCtQxmGe6tM2eI1tlKJWXDIWBi6iNxtOCJU76F+Y/cA==} peerDependencies: '@grpc/grpc-js': '*' '@nestjs/common': ^11.0.0 @@ -1452,14 +1456,14 @@ packages: '@nestjs/common': ^10.0.0 || ^11.0.0 passport: ^0.5.0 || ^0.6.0 || ^0.7.0 - '@nestjs/platform-express@11.1.19': - resolution: {integrity: sha512-Vpdv8jyCQdThfoTx+UTn+DRYr6H6X02YUqcpZ3qP6G3ZUwtVp7eS+hoQPGd4UuCnlnFG8Wqr2J9bGEzQdi1rIg==} + '@nestjs/platform-express@11.1.29': + resolution: {integrity: sha512-OngsAjdzM+SbbmP1XojKfBgl0Lj7u8b7IK5duwpFymzNP5EmjS8ujvqck9M8h0L14I91aRIFSjIpVfHj8WVtqw==} peerDependencies: '@nestjs/common': ^11.0.0 '@nestjs/core': ^11.0.0 - '@nestjs/platform-socket.io@11.1.19': - resolution: {integrity: sha512-gu1nPIEaP5Qjjg/Cl8wXyvwGpdZGzgbtK4KcH65YRAA+GTKUkIHb4BNpLJ27Ymq/wqLJKNEbCjajfzD0BEjMGA==} + '@nestjs/platform-socket.io@11.1.29': + resolution: {integrity: sha512-1p2KJpH6CYYpxPrtqpP65VdMgUqobGBPmx8vwOsF84/ki9YWiylG2+1q6kaUqYMtBdjreJDHgabuR5VazXav2Q==} peerDependencies: '@nestjs/common': ^11.0.0 '@nestjs/websockets': ^11.0.0 @@ -1480,10 +1484,10 @@ packages: prettier: optional: true - '@nestjs/swagger@11.4.2': - resolution: {integrity: sha512-aBihEogDMj/bLEcaqhkvyX/ZVWUw/bmnhKzR0zwUoyGJikvZyaq7rOPYl/H7Lxkkr3c90SJxyuv1AX2UT1WKlw==} + '@nestjs/swagger@11.4.6': + resolution: {integrity: sha512-Le136h2WC7HGsd70+WyK1qrm+Zq7kFxBLkYC1JgAVqNRCt8kNh7bMF7Qkn65D5j2t/aks0+VbWmUVlYIwPrs3A==} peerDependencies: - '@fastify/static': ^8.0.0 || ^9.0.0 + '@fastify/static': ^8.0.0 || ^9.0.0 || ^10.0.0 '@nestjs/common': ^11.0.1 '@nestjs/core': ^11.0.1 class-transformer: '*' @@ -1497,8 +1501,8 @@ packages: class-validator: optional: true - '@nestjs/testing@11.1.19': - resolution: {integrity: sha512-/UFNWXvPEdu4v4DlC5oWLbGKmD27LehLK06b8oLzs6D6lf4vAQTdST8LRAXBadyMUQnVEQWMuBo3CtAVtlfXtQ==} + '@nestjs/testing@11.1.29': + resolution: {integrity: sha512-u+aEp90Ly3uQbLoicZs+ZOrJTyvaD0fbd6dpnSgFZz5Bf77gbKKfr+xFE2JxqZuIH15HlBrqDL7/OL2qL8oG+A==} peerDependencies: '@nestjs/common': ^11.0.0 '@nestjs/core': ^11.0.0 @@ -1510,8 +1514,8 @@ packages: '@nestjs/platform-express': optional: true - '@nestjs/typeorm@11.0.1': - resolution: {integrity: sha512-8rw/nKT0S+L+MkzgE9F2/mox7mAgsPlwfzmW9gsESN1lmQtIrVEfiiBwC2O8+guS1jBfQehJIdcdUj2OAp4VUQ==} + '@nestjs/typeorm@11.0.3': + resolution: {integrity: sha512-zJ+E5l7auVVA7c0PsvcMdyvRPKTUqU5s2ToYmOA2QEsXQ42qbUGtK4+1HlRfpHqBkCSXP+phiH4luvf9DyJNog==} peerDependencies: '@nestjs/common': ^10.0.0 || ^11.0.0 '@nestjs/core': ^10.0.0 || ^11.0.0 @@ -1519,8 +1523,8 @@ packages: rxjs: ^7.2.0 typeorm: ^0.3.0 || ^1.0.0-dev - '@nestjs/websockets@11.1.19': - resolution: {integrity: sha512-2qo8jtIwwwgkqAI1BtnJ02EaFLrRkKA39eYXS8IhZCHilhBHCWdjnJ5cLcFq4oF+s+KZ7LcLGD/3stxJy8ijzg==} + '@nestjs/websockets@11.1.29': + resolution: {integrity: sha512-Mg0sNK2k+PpamcJpDy9ZP24ZvM08xB6AZs061Kb6dhTesGxr6zrxlbZ7+40r5xwuxD/PN8LM2tSRKf5ojbFNDQ==} peerDependencies: '@nestjs/common': ^11.0.0 '@nestjs/core': ^11.0.0 @@ -1550,11 +1554,6 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@nuxt/opencollective@0.4.1': - resolution: {integrity: sha512-GXD3wy50qYbxCJ652bDrDzgMr3NFEkIS374+IgFQKkCvk9yiYcLvX2XDYr7UyQxf4wK0e+yqDYRubZ0DtOxnmQ==} - engines: {node: ^14.18.0 || >=16.10.0, npm: '>=5.10.0'} - hasBin: true - '@opentelemetry/api-logs@0.202.0': resolution: {integrity: sha512-fTBjMqKCfotFWfLzaKyhjLvyEyq5vDKTTFfBmx21btv3gvy8Lq6N5Dh2OzqeuN4DjtpSvNT1uNVfg08eD2Rfxw==} engines: {node: '>=8.0.0'} @@ -2284,11 +2283,11 @@ packages: '@types/stack-utils@2.0.3': resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} - '@types/superagent@8.1.9': - resolution: {integrity: sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==} + '@types/superagent@8.1.11': + resolution: {integrity: sha512-KA7srSW/HENDtOw9DOqaFLgWuMqN9WgjEw62lh9dpvRaZDkhdOkazASd7X7i2eMUYLHa1U37ZttnePsH5zTDHw==} - '@types/supertest@7.2.0': - resolution: {integrity: sha512-uh2Lv57xvggst6lCqNdFAmDSvoMG7M/HDtX4iUCquxQ5EGPtaPM5PL5Hmi7LCvOG8db7YaCPNJEeoI8s/WzIQw==} + '@types/supertest@7.2.1': + resolution: {integrity: sha512-4CbBvoYVLHL7+yhbYrZET0vsvuyXTC05aRe7dNQkwMzm56auceoy6Yu3K50uZmwfHna1os3CMSgM/3QVkUtPTw==} '@types/tough-cookie@4.0.5': resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} @@ -2491,6 +2490,11 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + acorn@8.18.0: + resolution: {integrity: sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==} + engines: {node: '>=0.4.0'} + hasBin: true + agent-base@6.0.2: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} @@ -2528,9 +2532,6 @@ packages: ajv@6.15.0: resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} - ajv@8.17.1: - resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} - ajv@8.18.0: resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} @@ -2569,8 +2570,8 @@ packages: resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} engines: {node: '>=12'} - ansis@4.1.0: - resolution: {integrity: sha512-BGcItUBWSMRgOCe+SVZJ+S7yTRG0eGt9cXAHev72yuGcY23hnLA7Bky5L/xLyPINoSN95geovfBkqoTlNZYa7w==} + ansis@4.2.0: + resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==} engines: {node: '>=14'} ansis@4.3.1: @@ -2778,8 +2779,8 @@ packages: resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==} engines: {node: ^4.5.0 || >= 5.9} - baseline-browser-mapping@2.10.27: - resolution: {integrity: sha512-zEs/ufmZoUd7WftKpKyXaT6RFxpQ5Qm9xytKRHvJfxFV9DFJkZph9RvJ1LcOUi0Z1ZVijMte65JbILeV+8QQEA==} + baseline-browser-mapping@2.11.14: + resolution: {integrity: sha512-JyJ954WzuIR8/FFzX0o5krdSTrBAkcCSRfWSleRsIHSWV+cZe2FI1PKggVkFke1hBldRs+LRxUczzE9iPmgZww==} engines: {node: '>=6.0.0'} hasBin: true @@ -2836,9 +2837,9 @@ packages: brace-expansion@2.1.2: resolution: {integrity: sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==} - brace-expansion@5.0.5: - resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} - engines: {node: 18 || 20 || >=22} + brace-expansion@5.0.9: + resolution: {integrity: sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==} + engines: {node: 20 || >=22} braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} @@ -2850,8 +2851,8 @@ packages: browserify-zlib@0.2.0: resolution: {integrity: sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==} - browserslist@4.28.2: - resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} + browserslist@4.28.8: + resolution: {integrity: sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -2928,8 +2929,8 @@ packages: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} - caniuse-lite@1.0.30001792: - resolution: {integrity: sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==} + caniuse-lite@1.0.30001809: + resolution: {integrity: sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==} caseless@0.12.0: resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} @@ -2946,8 +2947,8 @@ packages: resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} engines: {node: '>=10'} - chardet@2.1.1: - resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} + chardet@2.2.0: + resolution: {integrity: sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==} chokidar@4.0.3: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} @@ -3096,10 +3097,6 @@ packages: resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} engines: {'0': node >= 6.0} - consola@3.4.2: - resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} - engines: {node: ^14.18.0 || >=16.10.0} - content-disposition@1.1.0: resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} engines: {node: '>=18'} @@ -3389,8 +3386,8 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - electron-to-chromium@1.5.352: - resolution: {integrity: sha512-9wHk8x6dyuimoe18EdiDPWKExNdxYqo4fn4FwOVVper6RxT3cmpBwBkWWfSOCYJjQdIco/nPhJhNLmn4Ufg1Yg==} + electron-to-chromium@1.5.406: + resolution: {integrity: sha512-hWH5ORBi3d0IipnMh7BN5GDTaAmrSSSWmznwt2zltdiRNEWoEQyTwF0FFSBxzHO7hLSRT6loQu3IQGV0wg/Tvg==} emittery@0.13.1: resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} @@ -3419,14 +3416,18 @@ packages: resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==} engines: {node: '>=10.0.0'} - engine.io@6.6.7: - resolution: {integrity: sha512-DgOngfDKM2EviOH3Mr9m7ks1q8roetLy/IMmYthAYzbpInMbYc/GS+fWFA3rl1gvwKVsQrVV61fo5emD1y3OJQ==} + engine.io@6.6.9: + resolution: {integrity: sha512-clKkw4C7nJ22mGgoVcCg6V/W/TxdNyIOTr89k2ONZu81qqkddPFDF0LXcbAwhzPD8DjkiRCjzuiO6Y+fkpD4vg==} engines: {node: '>=10.2.0'} enhanced-resolve@5.21.0: resolution: {integrity: sha512-otxSQPw4lkOZWkHpB3zaEQs6gWYEsmX4xQF68ElXC/TWvGxGMSGOvoNbaLXm6/cS/fSfHtsEdw90y20PCd+sCA==} engines: {node: '>=10.13.0'} + enhanced-resolve@5.24.5: + resolution: {integrity: sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==} + engines: {node: '>=10.13.0'} + env-paths@2.2.1: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} @@ -3449,9 +3450,6 @@ packages: es-get-iterator@1.1.3: resolution: {integrity: sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==} - es-module-lexer@1.7.0: - resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} - es-module-lexer@2.3.1: resolution: {integrity: sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==} @@ -3746,6 +3744,9 @@ packages: fast-uri@3.1.2: resolution: {integrity: sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==} + fast-uri@3.1.5: + resolution: {integrity: sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==} + fast-xml-builder@1.1.9: resolution: {integrity: sha512-jcyKVSEX13iseJqg7n/KWw+xnu/7fdrZ333Fac54KjHDIELVCfDDJXYIm6DTJ0Su4gSzrhqiK0DzY/wZbF40mw==} @@ -3886,8 +3887,8 @@ packages: resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} engines: {node: '>=12'} - fs-extra@11.3.5: - resolution: {integrity: sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==} + fs-extra@11.3.1: + resolution: {integrity: sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g==} engines: {node: '>=14.14'} fs-monkey@1.1.0: @@ -3999,11 +4000,9 @@ packages: deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true - glob@11.0.3: - resolution: {integrity: sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==} - engines: {node: 20 || >=22} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me - hasBin: true + glob@13.0.6: + resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} + engines: {node: 18 || 20 || >=22} glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} @@ -4440,10 +4439,6 @@ packages: jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - jackspeak@4.2.3: - resolution: {integrity: sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==} - engines: {node: 20 || >=22} - jest-changed-files@29.7.0: resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -4590,14 +4585,22 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - js-yaml@3.14.2: - resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} + js-yaml@3.15.1: + resolution: {integrity: sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag==} hasBin: true js-yaml@4.1.1: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true + js-yaml@4.3.1: + resolution: {integrity: sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==} + hasBin: true + + js-yaml@5.2.1: + resolution: {integrity: sha512-zfLtNfQqxVqq3uaTqSkh4x4hZw3KHobGUA0fJUj4wawW8bsQLTVqpHdXSIzidh7o+4lEW36tANuAGdaFx6Zgnw==} + hasBin: true + jsbn@0.1.1: resolution: {integrity: sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==} @@ -4816,8 +4819,8 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - lru-cache@11.3.6: - resolution: {integrity: sha512-Gf/KoL3C/MlI7Bt0PGI9I+TeTC/I6r/csU58N4BSNc4lppLBeKsOdFYkK+dX0ABDUMJNfCHTyPpzwwO21Awd3A==} + lru-cache@11.5.2: + resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==} engines: {node: 20 || >=22} lru-cache@5.1.1: @@ -4933,8 +4936,8 @@ packages: minimalistic-assert@1.0.1: resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} - minimatch@10.2.5: - resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + minimatch@10.2.6: + resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==} engines: {node: 18 || 20 || >=22} minimatch@3.1.5: @@ -4999,10 +5002,6 @@ packages: msgpackr@1.11.12: resolution: {integrity: sha512-RBdJ1Un7yGlXWajrkxcSa93nvQ0w4zBf60c0yYv7YtBelP8H2FA7XsfBbMHtXKXUMUxH7zV3Zuozh+kUQWhHvg==} - multer@2.1.1: - resolution: {integrity: sha512-mo+QTzKlx8R7E5ylSXxWzGoXoZbOsRMpyitcht8By2KHvMbf3tjwosZ/Mu/XYU6UuJ3VZnODIrak5ZrPiPyB6A==} - engines: {node: '>= 10.16.0'} - multer@2.2.0: resolution: {integrity: sha512-6rdyFg2kLrMh9Jee7/BMPuV9lEAd7lLW2YUpF9/YxR7njyoUwwQ0ZPh3TaIY50Sw6vlyD2HW3wGOkTS4P79xrQ==} engines: {node: '>= 10.16.0'} @@ -5035,8 +5034,8 @@ packages: neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} - nestjs-cls@6.2.0: - resolution: {integrity: sha512-b2Remha7gV5gId3ezjr2tupjqqgYK7/JqjqX6oZ0ZIDFATUggKH1/32+ul2lOe7FepnHasDONDoePuWEE64cug==} + nestjs-cls@6.2.1: + resolution: {integrity: sha512-LTVvreuhH132+rjP4MwnykUS3byBAuN6WQzh2QA3YSYCjy9GfgRyoiSKE5qrzQe+uCB1/TUNHPCxEMYgR3tRlg==} engines: {node: '>=18'} peerDependencies: '@nestjs/common': '>= 10 < 12' @@ -5118,11 +5117,12 @@ packages: resolution: {integrity: sha512-Xoxe53XsEriaVLWJTl3YtHE54qaZnO2GeoNKKALLy6cKUAjYZuFlTaNCcsVkfoIbZv2YYWuggeuqhTQ1XPbvgw==} engines: {node: '>=10.17.0'} - node-releases@2.0.38: - resolution: {integrity: sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==} + node-releases@2.0.53: + resolution: {integrity: sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==} + engines: {node: '>=18'} - nodemailer@9.0.1: - resolution: {integrity: sha512-Gwv8SQewT616ZM/URn0H54b8PWo/Wum7md3EW2aWy1lO27+WZCX+Xyak3J+NlmHUjDh5ME+uesJUDRbR3Ye8Bw==} + nodemailer@9.0.5: + resolution: {integrity: sha512-wvjiKvjczmsN7U/8006JOdXubgBk2XFAbioDMbT+sM7cPs0QrhJTa6KBRX7P5REGGkDcLUz/EarWidb8G8C1jQ==} engines: {node: '>=6.0.0'} normalize-path@3.0.0: @@ -5251,8 +5251,8 @@ packages: pako@1.0.11: resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} - papaparse@5.5.3: - resolution: {integrity: sha512-5QvjGxYVjxO59MGU2lHVYpRWBBtKHnlIAcSe1uNFCkkptUh63NFRj0FJQm7nR67puEruUci/ZkjmEFrjCAyP4A==} + papaparse@5.5.4: + resolution: {integrity: sha512-SwzWD9gl/ElwYLCI0nUja1mFJzjq2D8ziShfNBa7zCHzkOozeOGDwHWQ+tvCzEZcewecWZ5U7kUopDnG+DFYEQ==} parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} @@ -5380,10 +5380,6 @@ packages: resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} engines: {node: '>=8.6'} - picomatch@4.0.2: - resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} - engines: {node: '>=12'} - picomatch@4.0.4: resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} engines: {node: '>=12'} @@ -5500,10 +5496,6 @@ packages: resolution: {integrity: sha512-NWWCCscLjs+cOKF/s/XVNFRW7Yih0fdH+9brffR5NZCy8k42yRdl5KlWKMVXuI1vfCoy4o1z80XR/W/QUb3V3w==} engines: {node: '>=12.0.0'} - protobufjs@8.7.2: - resolution: {integrity: sha512-oTVHV+oelUBtiu5iTuTNNZ0eLYsXSMxry4cgr30mayNkgIZL6qZ0IOQVPuSWGcyAaXKl/XgqwWHIC3a0khYVBA==} - engines: {node: '>=12.0.0'} - proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -5568,8 +5560,8 @@ packages: engines: {node: '>=10.13.0'} hasBin: true - qs@6.15.2: - resolution: {integrity: sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==} + qs@6.15.3: + resolution: {integrity: sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==} engines: {node: '>=0.6'} qs@6.5.5: @@ -5802,6 +5794,11 @@ packages: engines: {node: '>=10'} hasBin: true + semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} + engines: {node: '>=10'} + hasBin: true + send@1.2.1: resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} engines: {node: '>= 18'} @@ -5879,8 +5876,8 @@ packages: resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} - socket.io-adapter@2.5.6: - resolution: {integrity: sha512-DkkO/dz7MGln0dHn5bmN3pPy+JmywNICWrJqVWiVOyvXjWQFIv9c2h24JrQLLFJ2aQVQf/Cvl1vblnd4r2apLQ==} + socket.io-adapter@2.5.8: + resolution: {integrity: sha512-6Oy52pbg+kvdCVvjcN+FnY7BvxZ7cIHNScbvztT/It5d0vbwoJoVZmF2gjJmnV0/4WlXRfG15zc45ySk9Ah8bw==} socket.io-client@4.8.3: resolution: {integrity: sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==} @@ -5890,6 +5887,10 @@ packages: resolution: {integrity: sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg==} engines: {node: '>=10.0.0'} + socket.io-parser@4.2.7: + resolution: {integrity: sha512-IH/iSeO9T6gz1KkFleGDWkG9N3dl4jXVYUtMhIqH10Md0ttMer8nUNWiP1DKuNrybD2xBrixLJdCC9J6ECoYkg==} + engines: {node: '>=10.0.0'} + socket.io@4.8.3: resolution: {integrity: sha512-2Dd78bqzzjE6KPkD5fHZmDAKRNe3J15q+YHDrIsy9WEkqttc7GY+kT9OBLSMaPbQaEd0x1BjcmtMtXkfpc+T5A==} engines: {node: '>=10.2.0'} @@ -6065,12 +6066,12 @@ packages: svix@1.92.2: resolution: {integrity: sha512-ZmuA3UVvlnF9EgxlzmPtF7CKjQb64Z6OFlyfdDfU0sdcC7dJa+3aOYX5B9mA+RS6ch1AxBa4UP/l6KmqfGtWBQ==} - swagger-ui-dist@5.32.4: - resolution: {integrity: sha512-0AADFFQNJzExEN49SrD/34Nn9cxNxVLiydYl2MBwSZFPVXNkVwC/EFAjoezGGqE8oDegiDC+p47t8lKObCinMQ==} - swagger-ui-dist@5.32.5: resolution: {integrity: sha512-7/FQfWe9A4qoyYFdAwy0chD0uDYidDp/ZT9VQ9LZlgD4AnnHJk8/+ytAA1HkJYOPySmK6helPDdJQMlcumt7HA==} + swagger-ui-dist@5.32.8: + resolution: {integrity: sha512-dgMdWXIgnI4zX4OPhKEdWnlDODbgm8W3AX0Ivn/BBqcUh6xZsBxhZMnvk6DJyRz1BTrj8dPxtarmEGgkz30oyA==} + swagger-ui-express@5.0.1: resolution: {integrity: sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA==} engines: {node: '>= v0.10.32'} @@ -6101,24 +6102,51 @@ packages: telejson@7.2.0: resolution: {integrity: sha512-1QTEcJkJEhc8OnStBx/ILRu5J2p0GjvWsBx56bmZRqnrkdBMUe+nX92jxV+p3dB4CP6PZCdJMQJwCggkNBMzkQ==} - terser-webpack-plugin@5.5.0: - resolution: {integrity: sha512-UYhptBwhWvfIjKd/UuFo6D8uq9xpGLDK+z8EDsj/zWhrTaH34cKEbrkMKfV5YWqGBvAYA3tlzZbs2R+qYrbQJA==} + terser-webpack-plugin@5.6.1: + resolution: {integrity: sha512-201R5j+sJpK8nFWwKVyNfZot8FaJbLZDq5evriVzbV1wDtSXDjRUDRfJzHpAaxFDMEhsZL1QkeqM61wgsS3KaQ==} engines: {node: '>= 10.13.0'} peerDependencies: + '@minify-html/node': '*' '@swc/core': '*' + '@swc/css': '*' + '@swc/html': '*' + clean-css: '*' + cssnano: '*' + csso: '*' esbuild: '*' + html-minifier-terser: '*' + lightningcss: '*' + postcss: '*' uglify-js: '*' webpack: ^5.1.0 peerDependenciesMeta: + '@minify-html/node': + optional: true '@swc/core': optional: true + '@swc/css': + optional: true + '@swc/html': + optional: true + clean-css: + optional: true + cssnano: + optional: true + csso: + optional: true esbuild: optional: true + html-minifier-terser: + optional: true + lightningcss: + optional: true + postcss: + optional: true uglify-js: optional: true - terser@5.47.1: - resolution: {integrity: sha512-tPbLXTI6ohPASb/1YViL428oEHu6/qv1OxqYnfaonVCFHqx4+wCd95pHrQWsL5X4pl90CTyW9piSAsS2L0VoMw==} + terser@5.50.0: + resolution: {integrity: sha512-CN9BVxWhgS/hRxtUMjtC2uRWSTcSfQFHMDWma6sKKfIivCD91sM+FOPfvwoaRMqCSrUpe1nv3jDamd9eEQ4y+w==} engines: {node: '>=10'} hasBin: true @@ -6170,10 +6198,6 @@ packages: tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - tree-kill@1.2.2: - resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} - hasBin: true - triple-beam@1.4.1: resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==} engines: {node: '>= 14.0.0'} @@ -6184,8 +6208,8 @@ packages: peerDependencies: typescript: '>=4.2.0' - ts-jest@29.4.9: - resolution: {integrity: sha512-LTb9496gYPMCqjeDLdPrKuXtncudeV1yRZnF4Wo5l3SFi0RYEnYRNgMrFIdg+FHvfzjCyQk1cLncWVqiSX+EvQ==} + ts-jest@29.4.12: + resolution: {integrity: sha512-Ov6ClY53Fflh6BGAnY2DlTq1hYDrTycz2PVTXBWFW2CU+9zrEqAp9fWdGXl42EXO5RLSFAcAZ2JFKbP+zBTFfw==} engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -6378,11 +6402,6 @@ packages: typeorm-aurora-data-api-driver: optional: true - typescript@5.8.3: - resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} - engines: {node: '>=14.17'} - hasBin: true - typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} @@ -6428,11 +6447,11 @@ packages: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} - unzipper@0.12.3: - resolution: {integrity: sha512-PZ8hTS+AqcGxsaQntl3IRBw65QrBI6lxzqDEL7IAo/XCEqRTKGfOX56Vea5TH9SZczRVxuzk1re04z/YjuYCJA==} + unzipper@0.12.5: + resolution: {integrity: sha512-tXYOi9R57Uj/2Z25SOs5RRSzq886MBQj2gY8dPL+xl/kv6s6SvByoKfAtvfVeEuhntWDgjd2o9p2lb4TVPAz0A==} - update-browserslist-db@1.2.3: - resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + update-browserslist-db@1.3.1: + resolution: {integrity: sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -6508,8 +6527,8 @@ packages: walker@1.0.8: resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} - watchpack@2.5.1: - resolution: {integrity: sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==} + watchpack@2.5.2: + resolution: {integrity: sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg==} engines: {node: '>=10.13.0'} wcwidth@1.0.1: @@ -6531,12 +6550,12 @@ packages: resolution: {integrity: sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==} engines: {node: '>=6'} - webpack-sources@3.4.1: - resolution: {integrity: sha512-eACpxRN02yaawnt+uUNIF7Qje6A9zArxBbcAJjK1PK3S9Ycg5jIuJ8pW4q8EMnwNZCEGltcjkRx1QzOxOkKD8A==} + webpack-sources@3.5.1: + resolution: {integrity: sha512-jyuiGJdtvY434z5bUZrjz67v76/ePNvFZTp9Mdz29IlH4+GPsgyGjiv0fKI+M7BdkU6ADjulUcKAd3tUK3WlEw==} engines: {node: '>=10.13.0'} - webpack@5.100.2: - resolution: {integrity: sha512-QaNKAvGCDRh3wW1dsDjeMdDXwZm2vqq3zn6Pvq4rHOEOGSaUMgOOjG2Y9ZbIGzpfkJk9ZYTHpDqgDfeBDcnLaw==} + webpack@5.106.2: + resolution: {integrity: sha512-wGN3qcrBQIFmQ/c0AiOAQBvrZ5lmY8vbbMv4Mxfgzqd/B6+9pXtLo73WuS1dSGXM5QYY3hZnIbvx+K1xxe6FyA==} engines: {node: '>=10.13.0'} hasBin: true peerDependencies: @@ -6675,6 +6694,18 @@ packages: utf-8-validate: optional: true + ws@8.21.3: + resolution: {integrity: sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + xml-js@1.6.11: resolution: {integrity: sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==} hasBin: true @@ -6758,18 +6789,18 @@ packages: snapshots: - '@angular-devkit/core@19.2.15(chokidar@4.0.3)': + '@angular-devkit/core@19.2.24(chokidar@4.0.3)': dependencies: - ajv: 8.17.1 - ajv-formats: 3.0.1(ajv@8.17.1) + ajv: 8.18.0 + ajv-formats: 3.0.1(ajv@8.18.0) jsonc-parser: 3.3.1 - picomatch: 4.0.2 + picomatch: 4.0.4 rxjs: 7.8.1 source-map: 0.7.4 optionalDependencies: chokidar: 4.0.3 - '@angular-devkit/core@19.2.24(chokidar@4.0.3)': + '@angular-devkit/core@19.2.27(chokidar@4.0.3)': dependencies: ajv: 8.18.0 ajv-formats: 3.0.1(ajv@8.18.0) @@ -6780,10 +6811,10 @@ snapshots: optionalDependencies: chokidar: 4.0.3 - '@angular-devkit/schematics-cli@19.2.15(@types/node@20.19.39)(chokidar@4.0.3)': + '@angular-devkit/schematics-cli@19.2.27(@types/node@20.19.39)(chokidar@4.0.3)': dependencies: - '@angular-devkit/core': 19.2.15(chokidar@4.0.3) - '@angular-devkit/schematics': 19.2.15(chokidar@4.0.3) + '@angular-devkit/core': 19.2.27(chokidar@4.0.3) + '@angular-devkit/schematics': 19.2.27(chokidar@4.0.3) '@inquirer/prompts': 7.3.2(@types/node@20.19.39) ansi-colors: 4.1.3 symbol-observable: 4.0.0 @@ -6792,9 +6823,9 @@ snapshots: - '@types/node' - chokidar - '@angular-devkit/schematics@19.2.15(chokidar@4.0.3)': + '@angular-devkit/schematics@19.2.24(chokidar@4.0.3)': dependencies: - '@angular-devkit/core': 19.2.15(chokidar@4.0.3) + '@angular-devkit/core': 19.2.24(chokidar@4.0.3) jsonc-parser: 3.3.1 magic-string: 0.30.17 ora: 5.4.1 @@ -6802,9 +6833,9 @@ snapshots: transitivePeerDependencies: - chokidar - '@angular-devkit/schematics@19.2.24(chokidar@4.0.3)': + '@angular-devkit/schematics@19.2.27(chokidar@4.0.3)': dependencies: - '@angular-devkit/core': 19.2.24(chokidar@4.0.3) + '@angular-devkit/core': 19.2.27(chokidar@4.0.3) jsonc-parser: 3.3.1 magic-string: 0.30.17 ora: 5.4.1 @@ -7178,11 +7209,17 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + '@babel/compat-data@7.29.3': {} '@babel/core@7.29.0': dependencies: - '@babel/code-frame': 7.29.0 + '@babel/code-frame': 7.29.7 '@babel/generator': 7.29.1 '@babel/helper-compilation-targets': 7.28.6 '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) @@ -7212,7 +7249,7 @@ snapshots: dependencies: '@babel/compat-data': 7.29.3 '@babel/helper-validator-option': 7.27.1 - browserslist: 4.28.2 + browserslist: 4.28.8 lru-cache: 5.1.1 semver: 6.3.1 @@ -7229,7 +7266,7 @@ snapshots: dependencies: '@babel/core': 7.29.0 '@babel/helper-module-imports': 7.28.6 - '@babel/helper-validator-identifier': 7.28.5 + '@babel/helper-validator-identifier': 7.29.7 '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color @@ -7240,6 +7277,8 @@ snapshots: '@babel/helper-validator-identifier@7.28.5': {} + '@babel/helper-validator-identifier@7.29.7': {} + '@babel/helper-validator-option@7.27.1': {} '@babel/helpers@7.29.2': @@ -7338,13 +7377,13 @@ snapshots: '@babel/template@7.28.6': dependencies: - '@babel/code-frame': 7.29.0 + '@babel/code-frame': 7.29.7 '@babel/parser': 7.29.3 '@babel/types': 7.29.0 '@babel/traverse@7.29.0': dependencies: - '@babel/code-frame': 7.29.0 + '@babel/code-frame': 7.29.7 '@babel/generator': 7.29.1 '@babel/helper-globals': 7.28.0 '@babel/parser': 7.29.3 @@ -7357,7 +7396,7 @@ snapshots: '@babel/types@7.29.0': dependencies: '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.28.5 + '@babel/helper-validator-identifier': 7.29.7 '@bcoe/v8-coverage@0.2.3': {} @@ -7393,7 +7432,7 @@ snapshots: globals: 13.24.0 ignore: 5.3.2 import-fresh: 3.3.1 - js-yaml: 4.1.1 + js-yaml: 4.3.1 minimatch: 3.1.5 strip-json-comments: 3.1.1 transitivePeerDependencies: @@ -7590,7 +7629,7 @@ snapshots: '@inquirer/external-editor@1.0.3(@types/node@20.19.39)': dependencies: - chardet: 2.1.1 + chardet: 2.2.0 iconv-lite: 0.7.3 optionalDependencies: '@types/node': 20.19.39 @@ -7619,7 +7658,7 @@ snapshots: optionalDependencies: '@types/node': 20.19.39 - '@inquirer/prompts@7.3.2(@types/node@20.19.39)': + '@inquirer/prompts@7.10.1(@types/node@20.19.39)': dependencies: '@inquirer/checkbox': 4.3.2(@types/node@20.19.39) '@inquirer/confirm': 5.1.21(@types/node@20.19.39) @@ -7634,7 +7673,7 @@ snapshots: optionalDependencies: '@types/node': 20.19.39 - '@inquirer/prompts@7.8.0(@types/node@20.19.39)': + '@inquirer/prompts@7.3.2(@types/node@20.19.39)': dependencies: '@inquirer/checkbox': 4.3.2(@types/node@20.19.39) '@inquirer/confirm': 5.1.21(@types/node@20.19.39) @@ -7691,14 +7730,12 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 - '@isaacs/cliui@9.0.0': {} - '@istanbuljs/load-nyc-config@1.1.0': dependencies: camelcase: 5.3.1 find-up: 4.1.0 get-package-type: 0.1.0 - js-yaml: 3.14.2 + js-yaml: 3.15.1 resolve-from: 5.0.0 '@istanbuljs/schema@0.1.6': {} @@ -7918,63 +7955,71 @@ snapshots: '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3': optional: true - '@nestjs/axios@4.0.1(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(axios@1.18.0)(rxjs@7.8.2)': + '@nestjs/axios@4.0.1(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(axios@1.18.0)(rxjs@7.8.2)': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) + '@nestjs/common': 11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) axios: 1.18.0 rxjs: 7.8.2 - '@nestjs/bull-shared@11.0.4(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)': + '@nestjs/bull-shared@11.0.5(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.29)': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@11.1.19)(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.1.14)(rxjs@7.8.2) + '@nestjs/common': 11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) + '@nestjs/core': 11.1.29(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@11.1.29)(@nestjs/platform-express@11.1.29)(@nestjs/websockets@11.1.29)(reflect-metadata@0.1.14)(rxjs@7.8.2) tslib: 2.8.1 - '@nestjs/bull@11.0.4(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(bull@4.16.5)': + '@nestjs/bull@11.0.5(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.29)(bull@4.16.5)': dependencies: - '@nestjs/bull-shared': 11.0.4(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19) - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@11.1.19)(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.1.14)(rxjs@7.8.2) + '@nestjs/bull-shared': 11.0.5(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.29) + '@nestjs/common': 11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) + '@nestjs/core': 11.1.29(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@11.1.29)(@nestjs/platform-express@11.1.29)(@nestjs/websockets@11.1.29)(reflect-metadata@0.1.14)(rxjs@7.8.2) bull: 4.16.5 tslib: 2.8.1 - '@nestjs/cache-manager@3.1.2(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(cache-manager@5.7.6)(keyv@4.5.4)(rxjs@7.8.2)': + '@nestjs/cache-manager@3.1.3(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.29)(cache-manager@5.7.6)(keyv@4.5.4)(rxjs@7.8.2)': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@11.1.19)(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.1.14)(rxjs@7.8.2) + '@nestjs/common': 11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) + '@nestjs/core': 11.1.29(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@11.1.29)(@nestjs/platform-express@11.1.29)(@nestjs/websockets@11.1.29)(reflect-metadata@0.1.14)(rxjs@7.8.2) cache-manager: 5.7.6 keyv: 4.5.4 rxjs: 7.8.2 - '@nestjs/cli@11.0.10(@types/node@20.19.39)(prettier@3.8.3)': + '@nestjs/cli@11.0.24(@types/node@20.19.39)(prettier@3.8.3)': dependencies: - '@angular-devkit/core': 19.2.15(chokidar@4.0.3) - '@angular-devkit/schematics': 19.2.15(chokidar@4.0.3) - '@angular-devkit/schematics-cli': 19.2.15(@types/node@20.19.39)(chokidar@4.0.3) - '@inquirer/prompts': 7.8.0(@types/node@20.19.39) - '@nestjs/schematics': 11.1.0(chokidar@4.0.3)(prettier@3.8.3)(typescript@5.8.3) - ansis: 4.1.0 + '@angular-devkit/core': 19.2.27(chokidar@4.0.3) + '@angular-devkit/schematics': 19.2.27(chokidar@4.0.3) + '@angular-devkit/schematics-cli': 19.2.27(@types/node@20.19.39)(chokidar@4.0.3) + '@inquirer/prompts': 7.10.1(@types/node@20.19.39) + '@nestjs/schematics': 11.1.0(chokidar@4.0.3)(prettier@3.8.3)(typescript@5.9.3) + ansis: 4.2.0 chokidar: 4.0.3 cli-table3: 0.6.5 commander: 4.1.1 - fork-ts-checker-webpack-plugin: 9.1.0(typescript@5.8.3)(webpack@5.100.2) - glob: 11.0.3 + fork-ts-checker-webpack-plugin: 9.1.0(typescript@5.9.3)(webpack@5.106.2) + glob: 13.0.6 node-emoji: 1.11.0 ora: 5.4.1 - tree-kill: 1.2.2 tsconfig-paths: 4.2.0 tsconfig-paths-webpack-plugin: 4.2.0 - typescript: 5.8.3 - webpack: 5.100.2 + typescript: 5.9.3 + webpack: 5.106.2 webpack-node-externals: 3.0.0 transitivePeerDependencies: + - '@minify-html/node' + - '@swc/css' + - '@swc/html' - '@types/node' + - clean-css + - cssnano + - csso - esbuild + - html-minifier-terser + - lightningcss + - postcss - prettier - uglify-js - webpack-cli - '@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2)': + '@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2)': dependencies: file-type: 21.3.4 iterare: 1.2.1 @@ -7989,18 +8034,17 @@ snapshots: transitivePeerDependencies: - supports-color - '@nestjs/config@4.0.4(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(rxjs@7.8.2)': + '@nestjs/config@4.0.4(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(rxjs@7.8.2)': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) + '@nestjs/common': 11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) dotenv: 17.4.1 dotenv-expand: 12.0.3 lodash: 4.18.1 rxjs: 7.8.2 - '@nestjs/core@11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@11.1.19)(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.1.14)(rxjs@7.8.2)': + '@nestjs/core@11.1.29(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@11.1.29)(@nestjs/platform-express@11.1.29)(@nestjs/websockets@11.1.29)(reflect-metadata@0.1.14)(rxjs@7.8.2)': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) - '@nuxt/opencollective': 0.4.1 + '@nestjs/common': 11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) fast-safe-stringify: 2.1.1 iterare: 1.2.1 path-to-regexp: 8.4.2 @@ -8009,60 +8053,60 @@ snapshots: tslib: 2.8.1 uid: 2.0.2 optionalDependencies: - '@nestjs/microservices': 11.1.19(@grpc/grpc-js@1.14.4)(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/websockets@11.1.19)(cache-manager@5.7.6)(ioredis@5.10.1)(nats@2.29.3)(reflect-metadata@0.1.14)(rxjs@7.8.2) - '@nestjs/platform-express': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19) - '@nestjs/websockets': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/platform-socket.io@11.1.19)(reflect-metadata@0.1.14)(rxjs@7.8.2) + '@nestjs/microservices': 11.1.29(@grpc/grpc-js@1.14.4)(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.29)(@nestjs/websockets@11.1.29)(cache-manager@5.7.6)(ioredis@5.10.1)(nats@2.29.3)(reflect-metadata@0.1.14)(rxjs@7.8.2) + '@nestjs/platform-express': 11.1.29(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.29) + '@nestjs/websockets': 11.1.29(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.29)(@nestjs/platform-socket.io@11.1.29)(reflect-metadata@0.1.14)(rxjs@7.8.2) - '@nestjs/jwt@11.0.2(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))': + '@nestjs/jwt@11.0.2(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) + '@nestjs/common': 11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) '@types/jsonwebtoken': 9.0.10 jsonwebtoken: 9.0.3 - '@nestjs/mapped-types@2.1.1(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)': + '@nestjs/mapped-types@2.1.1(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) + '@nestjs/common': 11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) reflect-metadata: 0.1.14 optionalDependencies: class-transformer: 0.5.1 class-validator: 0.14.4 - '@nestjs/microservices@11.1.19(@grpc/grpc-js@1.14.4)(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/websockets@11.1.19)(cache-manager@5.7.6)(ioredis@5.10.1)(nats@2.29.3)(reflect-metadata@0.1.14)(rxjs@7.8.2)': + '@nestjs/microservices@11.1.29(@grpc/grpc-js@1.14.4)(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.29)(@nestjs/websockets@11.1.29)(cache-manager@5.7.6)(ioredis@5.10.1)(nats@2.29.3)(reflect-metadata@0.1.14)(rxjs@7.8.2)': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@11.1.19)(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.1.14)(rxjs@7.8.2) + '@nestjs/common': 11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) + '@nestjs/core': 11.1.29(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@11.1.29)(@nestjs/platform-express@11.1.29)(@nestjs/websockets@11.1.29)(reflect-metadata@0.1.14)(rxjs@7.8.2) iterare: 1.2.1 reflect-metadata: 0.1.14 rxjs: 7.8.2 tslib: 2.8.1 optionalDependencies: '@grpc/grpc-js': 1.14.4 - '@nestjs/websockets': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/platform-socket.io@11.1.19)(reflect-metadata@0.1.14)(rxjs@7.8.2) + '@nestjs/websockets': 11.1.29(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.29)(@nestjs/platform-socket.io@11.1.29)(reflect-metadata@0.1.14)(rxjs@7.8.2) cache-manager: 5.7.6 ioredis: 5.10.1 nats: 2.29.3 - '@nestjs/passport@11.0.5(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(passport@0.6.0)': + '@nestjs/passport@11.0.5(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(passport@0.6.0)': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) + '@nestjs/common': 11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) passport: 0.6.0 - '@nestjs/platform-express@11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)': + '@nestjs/platform-express@11.1.29(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.29)': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@11.1.19)(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.1.14)(rxjs@7.8.2) + '@nestjs/common': 11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) + '@nestjs/core': 11.1.29(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@11.1.29)(@nestjs/platform-express@11.1.29)(@nestjs/websockets@11.1.29)(reflect-metadata@0.1.14)(rxjs@7.8.2) cors: 2.8.6 express: 5.2.1 - multer: 2.1.1 + multer: 2.2.0 path-to-regexp: 8.4.2 tslib: 2.8.1 transitivePeerDependencies: - supports-color - '@nestjs/platform-socket.io@11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/websockets@11.1.19)(rxjs@7.8.2)': + '@nestjs/platform-socket.io@11.1.29(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/websockets@11.1.29)(rxjs@7.8.2)': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) - '@nestjs/websockets': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/platform-socket.io@11.1.19)(reflect-metadata@0.1.14)(rxjs@7.8.2) + '@nestjs/common': 11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) + '@nestjs/websockets': 11.1.29(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.29)(@nestjs/platform-socket.io@11.1.29)(reflect-metadata@0.1.14)(rxjs@7.8.2) rxjs: 7.8.2 socket.io: 4.8.3 tslib: 2.8.1 @@ -8071,25 +8115,12 @@ snapshots: - supports-color - utf-8-validate - '@nestjs/schedule@6.1.3(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)': + '@nestjs/schedule@6.1.3(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.29)': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@11.1.19)(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.1.14)(rxjs@7.8.2) + '@nestjs/common': 11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) + '@nestjs/core': 11.1.29(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@11.1.29)(@nestjs/platform-express@11.1.29)(@nestjs/websockets@11.1.29)(reflect-metadata@0.1.14)(rxjs@7.8.2) cron: 4.4.0 - '@nestjs/schematics@11.1.0(chokidar@4.0.3)(prettier@3.8.3)(typescript@5.8.3)': - dependencies: - '@angular-devkit/core': 19.2.24(chokidar@4.0.3) - '@angular-devkit/schematics': 19.2.24(chokidar@4.0.3) - comment-json: 5.0.0 - jsonc-parser: 3.3.1 - pluralize: 8.0.0 - typescript: 5.8.3 - optionalDependencies: - prettier: 3.8.3 - transitivePeerDependencies: - - chokidar - '@nestjs/schematics@11.1.0(chokidar@4.0.3)(prettier@3.8.3)(typescript@5.9.3)': dependencies: '@angular-devkit/core': 19.2.24(chokidar@4.0.3) @@ -8103,49 +8134,49 @@ snapshots: transitivePeerDependencies: - chokidar - '@nestjs/swagger@11.4.2(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)': + '@nestjs/swagger@11.4.6(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.29)(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)': dependencies: '@microsoft/tsdoc': 0.16.0 - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@11.1.19)(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.1.14)(rxjs@7.8.2) - '@nestjs/mapped-types': 2.1.1(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14) - js-yaml: 4.1.1 + '@nestjs/common': 11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) + '@nestjs/core': 11.1.29(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@11.1.29)(@nestjs/platform-express@11.1.29)(@nestjs/websockets@11.1.29)(reflect-metadata@0.1.14)(rxjs@7.8.2) + '@nestjs/mapped-types': 2.1.1(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14) + js-yaml: 5.2.1 lodash: 4.18.1 path-to-regexp: 8.4.2 reflect-metadata: 0.1.14 - swagger-ui-dist: 5.32.4 + swagger-ui-dist: 5.32.8 optionalDependencies: class-transformer: 0.5.1 class-validator: 0.14.4 - '@nestjs/testing@11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/microservices@11.1.19)(@nestjs/platform-express@11.1.19)': + '@nestjs/testing@11.1.29(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.29)(@nestjs/microservices@11.1.29)(@nestjs/platform-express@11.1.29)': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@11.1.19)(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.1.14)(rxjs@7.8.2) + '@nestjs/common': 11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) + '@nestjs/core': 11.1.29(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@11.1.29)(@nestjs/platform-express@11.1.29)(@nestjs/websockets@11.1.29)(reflect-metadata@0.1.14)(rxjs@7.8.2) tslib: 2.8.1 optionalDependencies: - '@nestjs/microservices': 11.1.19(@grpc/grpc-js@1.14.4)(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/websockets@11.1.19)(cache-manager@5.7.6)(ioredis@5.10.1)(nats@2.29.3)(reflect-metadata@0.1.14)(rxjs@7.8.2) - '@nestjs/platform-express': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19) + '@nestjs/microservices': 11.1.29(@grpc/grpc-js@1.14.4)(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.29)(@nestjs/websockets@11.1.29)(cache-manager@5.7.6)(ioredis@5.10.1)(nats@2.29.3)(reflect-metadata@0.1.14)(rxjs@7.8.2) + '@nestjs/platform-express': 11.1.29(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.29) - '@nestjs/typeorm@11.0.1(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(reflect-metadata@0.1.14)(rxjs@7.8.2)(typeorm@0.3.31(ioredis@5.10.1)(pg@8.20.0)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)))': + '@nestjs/typeorm@11.0.3(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.29)(reflect-metadata@0.1.14)(rxjs@7.8.2)(typeorm@0.3.31(ioredis@5.10.1)(pg@8.20.0)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)))': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@11.1.19)(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.1.14)(rxjs@7.8.2) + '@nestjs/common': 11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) + '@nestjs/core': 11.1.29(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@11.1.29)(@nestjs/platform-express@11.1.29)(@nestjs/websockets@11.1.29)(reflect-metadata@0.1.14)(rxjs@7.8.2) reflect-metadata: 0.1.14 rxjs: 7.8.2 typeorm: 0.3.31(ioredis@5.10.1)(pg@8.20.0)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)) - '@nestjs/websockets@11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/platform-socket.io@11.1.19)(reflect-metadata@0.1.14)(rxjs@7.8.2)': + '@nestjs/websockets@11.1.29(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.29)(@nestjs/platform-socket.io@11.1.29)(reflect-metadata@0.1.14)(rxjs@7.8.2)': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@11.1.19)(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.1.14)(rxjs@7.8.2) + '@nestjs/common': 11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) + '@nestjs/core': 11.1.29(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@11.1.29)(@nestjs/platform-express@11.1.29)(@nestjs/websockets@11.1.29)(reflect-metadata@0.1.14)(rxjs@7.8.2) iterare: 1.2.1 object-hash: 3.0.0 reflect-metadata: 0.1.14 rxjs: 7.8.2 tslib: 2.8.1 optionalDependencies: - '@nestjs/platform-socket.io': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/websockets@11.1.19)(rxjs@7.8.2) + '@nestjs/platform-socket.io': 11.1.29(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/websockets@11.1.29)(rxjs@7.8.2) '@noble/hashes@1.8.0': {} @@ -8163,10 +8194,6 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.20.1 - '@nuxt/opencollective@0.4.1': - dependencies: - consola: 3.4.2 - '@opentelemetry/api-logs@0.202.0': dependencies: '@opentelemetry/api': 1.9.1 @@ -9161,17 +9188,17 @@ snapshots: '@types/stack-utils@2.0.3': {} - '@types/superagent@8.1.9': + '@types/superagent@8.1.11': dependencies: '@types/cookiejar': 2.1.5 '@types/methods': 1.1.4 '@types/node': 20.19.39 form-data: 4.0.6 - '@types/supertest@7.2.0': + '@types/supertest@7.2.1': dependencies: '@types/methods': 1.1.4 - '@types/superagent': 8.1.9 + '@types/superagent': 8.1.11 '@types/tough-cookie@4.0.5': optional: true @@ -9273,7 +9300,7 @@ snapshots: debug: 4.4.3 globby: 11.1.0 is-glob: 4.0.3 - semver: 7.7.4 + semver: 7.8.5 tsutils: 3.21.0(typescript@5.9.3) optionalDependencies: typescript: 5.9.3 @@ -9288,7 +9315,7 @@ snapshots: globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.3 - semver: 7.7.4 + semver: 7.8.5 ts-api-utils: 1.4.3(typescript@5.9.3) optionalDependencies: typescript: 5.9.3 @@ -9416,13 +9443,13 @@ snapshots: mime-types: 3.0.2 negotiator: 1.0.0 - acorn-import-attributes@1.9.5(acorn@8.16.0): + acorn-import-attributes@1.9.5(acorn@8.18.0): dependencies: - acorn: 8.16.0 + acorn: 8.18.0 - acorn-import-phases@1.0.4(acorn@8.16.0): + acorn-import-phases@1.0.4(acorn@8.18.0): dependencies: - acorn: 8.16.0 + acorn: 8.18.0 acorn-jsx@5.3.2(acorn@8.16.0): dependencies: @@ -9434,6 +9461,8 @@ snapshots: acorn@8.16.0: {} + acorn@8.18.0: {} + agent-base@6.0.2: dependencies: debug: 4.4.3 @@ -9446,10 +9475,6 @@ snapshots: optionalDependencies: ajv: 8.20.0 - ajv-formats@3.0.1(ajv@8.17.1): - optionalDependencies: - ajv: 8.17.1 - ajv-formats@3.0.1(ajv@8.18.0): optionalDependencies: ajv: 8.18.0 @@ -9470,13 +9495,6 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 - ajv@8.17.1: - dependencies: - fast-deep-equal: 3.1.3 - fast-uri: 3.1.2 - json-schema-traverse: 1.0.0 - require-from-string: 2.0.2 - ajv@8.18.0: dependencies: fast-deep-equal: 3.1.3 @@ -9487,7 +9505,7 @@ snapshots: ajv@8.20.0: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.1.2 + fast-uri: 3.1.5 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 @@ -9513,7 +9531,7 @@ snapshots: ansi-styles@6.2.3: {} - ansis@4.1.0: {} + ansis@4.2.0: {} ansis@4.3.1: {} @@ -9628,7 +9646,7 @@ snapshots: idtoken-verifier: 2.2.4 js-cookie: 2.2.1 minimist: 1.2.8 - qs: 6.15.2 + qs: 6.15.3 superagent: 10.3.0 url-join: 4.0.1 winchan: 0.2.2 @@ -9773,7 +9791,7 @@ snapshots: base64id@2.0.0: {} - baseline-browser-mapping@2.10.27: {} + baseline-browser-mapping@2.11.14: {} basic-auth@2.0.1: dependencies: @@ -9819,7 +9837,7 @@ snapshots: http-errors: 2.0.1 iconv-lite: 0.4.24 on-finished: 2.4.1 - qs: 6.15.2 + qs: 6.15.3 raw-body: 2.5.3 type-is: 1.6.18 unpipe: 1.0.0 @@ -9834,7 +9852,7 @@ snapshots: http-errors: 2.0.1 iconv-lite: 0.7.3 on-finished: 2.4.1 - qs: 6.15.2 + qs: 6.15.3 raw-body: 3.0.2 type-is: 2.1.0 transitivePeerDependencies: @@ -9855,7 +9873,7 @@ snapshots: dependencies: balanced-match: 1.0.2 - brace-expansion@5.0.5: + brace-expansion@5.0.9: dependencies: balanced-match: 4.0.4 @@ -9871,13 +9889,13 @@ snapshots: dependencies: pako: 1.0.11 - browserslist@4.28.2: + browserslist@4.28.8: dependencies: - baseline-browser-mapping: 2.10.27 - caniuse-lite: 1.0.30001792 - electron-to-chromium: 1.5.352 - node-releases: 2.0.38 - update-browserslist-db: 1.2.3(browserslist@4.28.2) + baseline-browser-mapping: 2.11.14 + caniuse-lite: 1.0.30001809 + electron-to-chromium: 1.5.406 + node-releases: 2.0.53 + update-browserslist-db: 1.3.1(browserslist@4.28.8) bs-logger@0.2.6: dependencies: @@ -9969,7 +9987,7 @@ snapshots: camelcase@6.3.0: {} - caniuse-lite@1.0.30001792: {} + caniuse-lite@1.0.30001809: {} caseless@0.12.0: optional: true @@ -9983,7 +10001,7 @@ snapshots: char-regex@1.0.2: {} - chardet@2.1.1: {} + chardet@2.2.0: {} chokidar@4.0.3: dependencies: @@ -10120,8 +10138,6 @@ snapshots: readable-stream: 3.6.2 typedarray: 0.0.6 - consola@3.4.2: {} - content-disposition@1.1.0: {} content-type@1.0.5: {} @@ -10156,24 +10172,24 @@ snapshots: cosmiconfig@8.2.0: dependencies: import-fresh: 3.3.1 - js-yaml: 4.1.1 + js-yaml: 4.3.1 parse-json: 5.2.0 path-type: 4.0.0 - cosmiconfig@8.3.6(typescript@5.8.3): + cosmiconfig@8.3.6(typescript@5.9.3): dependencies: import-fresh: 3.3.1 - js-yaml: 4.1.1 + js-yaml: 4.3.1 parse-json: 5.2.0 path-type: 4.0.0 optionalDependencies: - typescript: 5.8.3 + typescript: 5.9.3 cosmiconfig@9.0.0(typescript@5.9.3): dependencies: env-paths: 2.2.1 import-fresh: 3.3.1 - js-yaml: 4.1.1 + js-yaml: 4.3.1 parse-json: 5.2.0 optionalDependencies: typescript: 5.9.3 @@ -10400,7 +10416,7 @@ snapshots: ee-first@1.1.1: {} - electron-to-chromium@1.5.352: {} + electron-to-chromium@1.5.406: {} emittery@0.13.1: {} @@ -10430,7 +10446,7 @@ snapshots: engine.io-parser@5.2.3: {} - engine.io@6.6.7: + engine.io@6.6.9: dependencies: '@types/cors': 2.8.19 '@types/node': 20.19.39 @@ -10441,7 +10457,7 @@ snapshots: cors: 2.8.6 debug: 4.4.3 engine.io-parser: 5.2.3 - ws: 8.18.3 + ws: 8.21.3 transitivePeerDependencies: - bufferutil - supports-color @@ -10452,6 +10468,11 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.3.3 + enhanced-resolve@5.24.5: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.3 + env-paths@2.2.1: {} error-ex@1.3.4: @@ -10531,8 +10552,6 @@ snapshots: isarray: 2.0.5 stop-iteration-iterator: 1.1.0 - es-module-lexer@1.7.0: {} - es-module-lexer@2.3.1: {} es-object-atoms@1.1.1: @@ -10844,7 +10863,7 @@ snapshots: once: 1.4.0 parseurl: 1.3.3 proxy-addr: 2.0.7 - qs: 6.15.2 + qs: 6.15.3 range-parser: 1.2.1 router: 2.2.0 send: 1.2.1 @@ -10869,7 +10888,7 @@ snapshots: extract-zip@2.0.1: dependencies: - debug: 4.4.3 + debug: 4.3.4 get-stream: 5.2.0 yauzl: 2.10.0 optionalDependencies: @@ -10906,6 +10925,8 @@ snapshots: fast-uri@3.1.2: {} + fast-uri@3.1.5: {} + fast-xml-builder@1.1.9: dependencies: path-expression-matcher: 1.5.0 @@ -11045,22 +11066,22 @@ snapshots: forever-agent@0.6.1: optional: true - fork-ts-checker-webpack-plugin@9.1.0(typescript@5.8.3)(webpack@5.100.2): + fork-ts-checker-webpack-plugin@9.1.0(typescript@5.9.3)(webpack@5.106.2): dependencies: - '@babel/code-frame': 7.29.0 + '@babel/code-frame': 7.29.7 chalk: 4.1.2 chokidar: 4.0.3 - cosmiconfig: 8.3.6(typescript@5.8.3) + cosmiconfig: 8.3.6(typescript@5.9.3) deepmerge: 4.3.1 fs-extra: 10.1.0 memfs: 3.5.3 minimatch: 3.1.5 node-abort-controller: 3.1.1 schema-utils: 3.3.0 - semver: 7.7.4 + semver: 7.8.5 tapable: 2.3.3 - typescript: 5.8.3 - webpack: 5.100.2 + typescript: 5.9.3 + webpack: 5.106.2 form-data@2.3.3: dependencies: @@ -11116,7 +11137,7 @@ snapshots: jsonfile: 6.2.1 universalify: 2.0.1 - fs-extra@11.3.5: + fs-extra@11.3.1: dependencies: graceful-fs: 4.2.11 jsonfile: 6.2.1 @@ -11231,7 +11252,7 @@ snapshots: dependencies: basic-ftp: 5.3.1 data-uri-to-buffer: 6.0.2 - debug: 4.4.3 + debug: 4.3.4 transitivePeerDependencies: - supports-color @@ -11259,13 +11280,10 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 1.11.1 - glob@11.0.3: + glob@13.0.6: dependencies: - foreground-child: 3.3.1 - jackspeak: 4.2.3 - minimatch: 10.2.5 + minimatch: 10.2.6 minipass: 7.1.3 - package-json-from-dist: 1.0.1 path-scurry: 2.0.2 glob@7.2.3: @@ -11461,7 +11479,7 @@ snapshots: http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.4 - debug: 4.4.3 + debug: 4.3.4 transitivePeerDependencies: - supports-color @@ -11526,8 +11544,8 @@ snapshots: import-in-the-middle@1.15.0: dependencies: - acorn: 8.16.0 - acorn-import-attributes: 1.9.5(acorn@8.16.0) + acorn: 8.18.0 + acorn-import-attributes: 1.9.5(acorn@8.18.0) cjs-module-lexer: 1.4.3 module-details-from-path: 1.0.4 @@ -11743,7 +11761,7 @@ snapshots: '@babel/parser': 7.29.3 '@istanbuljs/schema': 0.1.6 istanbul-lib-coverage: 3.2.2 - semver: 7.7.4 + semver: 7.8.5 transitivePeerDependencies: - supports-color @@ -11774,10 +11792,6 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 - jackspeak@4.2.3: - dependencies: - '@isaacs/cliui': 9.0.0 - jest-changed-files@29.7.0: dependencies: execa: 5.1.1 @@ -12035,7 +12049,7 @@ snapshots: jest-util: 29.7.0 natural-compare: 1.4.0 pretty-format: 29.7.0 - semver: 7.7.4 + semver: 7.8.5 transitivePeerDependencies: - supports-color @@ -12101,7 +12115,7 @@ snapshots: js-tokens@4.0.0: {} - js-yaml@3.14.2: + js-yaml@3.15.1: dependencies: argparse: 1.0.10 esprima: 4.0.1 @@ -12110,6 +12124,14 @@ snapshots: dependencies: argparse: 2.0.1 + js-yaml@4.3.1: + dependencies: + argparse: 2.0.1 + + js-yaml@5.2.1: + dependencies: + argparse: 2.0.1 + jsbn@0.1.1: optional: true @@ -12337,7 +12359,7 @@ snapshots: lru-cache@10.4.3: {} - lru-cache@11.3.6: {} + lru-cache@11.5.2: {} lru-cache@5.1.1: dependencies: @@ -12362,7 +12384,7 @@ snapshots: make-dir@4.0.0: dependencies: - semver: 7.7.4 + semver: 7.8.5 make-error@1.3.6: {} @@ -12427,9 +12449,9 @@ snapshots: minimalistic-assert@1.0.1: {} - minimatch@10.2.5: + minimatch@10.2.6: dependencies: - brace-expansion: 5.0.5 + brace-expansion: 5.0.9 minimatch@3.1.5: dependencies: @@ -12498,13 +12520,6 @@ snapshots: optionalDependencies: msgpackr-extract: 3.0.3 - multer@2.1.1: - dependencies: - append-field: 1.0.0 - busboy: 1.6.0 - concat-stream: 2.0.0 - type-is: 1.6.18 - multer@2.2.0: dependencies: append-field: 1.0.0 @@ -12528,10 +12543,10 @@ snapshots: neo-async@2.6.2: {} - nestjs-cls@6.2.0(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(reflect-metadata@0.1.14)(rxjs@7.8.2): + nestjs-cls@6.2.1(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.29)(reflect-metadata@0.1.14)(rxjs@7.8.2): dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@11.1.19)(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.1.14)(rxjs@7.8.2) + '@nestjs/common': 11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) + '@nestjs/core': 11.1.29(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@11.1.29)(@nestjs/platform-express@11.1.29)(@nestjs/websockets@11.1.29)(reflect-metadata@0.1.14)(rxjs@7.8.2) reflect-metadata: 0.1.14 rxjs: 7.8.2 @@ -12541,9 +12556,9 @@ snapshots: nestjs-supabase-auth@1.0.9: {} - nestjs-typeorm-paginate@4.1.0(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(typeorm@0.3.31(ioredis@5.10.1)(pg@8.20.0)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3))): + nestjs-typeorm-paginate@4.1.0(@nestjs/common@11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(typeorm@0.3.31(ioredis@5.10.1)(pg@8.20.0)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3))): dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) + '@nestjs/common': 11.1.29(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) typeorm: 0.3.31(ioredis@5.10.1)(pg@8.20.0)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)) netmask@2.1.1: {} @@ -12606,9 +12621,9 @@ snapshots: node-qpdf2@2.0.0: {} - node-releases@2.0.38: {} + node-releases@2.0.53: {} - nodemailer@9.0.1: {} + nodemailer@9.0.5: {} normalize-path@3.0.0: {} @@ -12743,7 +12758,7 @@ snapshots: dependencies: '@tootallnate/quickjs-emscripten': 0.23.0 agent-base: 7.1.4 - debug: 4.4.3 + debug: 4.3.4 get-uri: 6.0.5 http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 @@ -12763,7 +12778,7 @@ snapshots: pako@1.0.11: {} - papaparse@5.5.3: {} + papaparse@5.5.4: {} parent-module@1.0.1: dependencies: @@ -12771,7 +12786,7 @@ snapshots: parse-json@5.2.0: dependencies: - '@babel/code-frame': 7.29.0 + '@babel/code-frame': 7.29.7 error-ex: 1.3.4 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 @@ -12814,7 +12829,7 @@ snapshots: path-scurry@2.0.2: dependencies: - lru-cache: 11.3.6 + lru-cache: 11.5.2 minipass: 7.1.3 path-to-regexp@8.4.2: {} @@ -12894,8 +12909,6 @@ snapshots: picomatch@2.3.2: {} - picomatch@4.0.2: {} - picomatch@4.0.4: {} pidtree@0.6.0: {} @@ -13006,10 +13019,6 @@ snapshots: '@types/node': 20.19.39 long: 5.3.2 - protobufjs@8.7.2: - dependencies: - long: 5.3.2 - proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 @@ -13018,7 +13027,7 @@ snapshots: proxy-agent@6.3.0: dependencies: agent-base: 7.1.4 - debug: 4.4.3 + debug: 4.3.4 http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 lru-cache: 7.18.3 @@ -13031,7 +13040,7 @@ snapshots: proxy-agent@6.3.1: dependencies: agent-base: 7.1.4 - debug: 4.4.3 + debug: 4.3.4 http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 lru-cache: 7.18.3 @@ -13137,8 +13146,9 @@ snapshots: pngjs: 5.0.0 yargs: 15.4.1 - qs@6.15.2: + qs@6.15.3: dependencies: + es-define-property: 1.0.1 side-channel: 1.1.1 qs@6.5.5: @@ -13407,6 +13417,8 @@ snapshots: semver@7.7.4: {} + semver@7.8.5: {} + send@1.2.1: dependencies: debug: 4.4.3 @@ -13513,10 +13525,10 @@ snapshots: smart-buffer@4.2.0: {} - socket.io-adapter@2.5.6: + socket.io-adapter@2.5.8: dependencies: debug: 4.4.3 - ws: 8.18.3 + ws: 8.21.3 transitivePeerDependencies: - bufferutil - supports-color @@ -13540,15 +13552,22 @@ snapshots: transitivePeerDependencies: - supports-color + socket.io-parser@4.2.7: + dependencies: + '@socket.io/component-emitter': 3.1.2 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + socket.io@4.8.3: dependencies: accepts: 1.3.8 base64id: 2.0.0 cors: 2.8.6 debug: 4.4.3 - engine.io: 6.6.7 - socket.io-adapter: 2.5.6 - socket.io-parser: 4.2.6 + engine.io: 6.6.9 + socket.io-adapter: 2.5.8 + socket.io-parser: 4.2.7 transitivePeerDependencies: - bufferutil - supports-color @@ -13557,7 +13576,7 @@ snapshots: socks-proxy-agent@8.0.5: dependencies: agent-base: 7.1.4 - debug: 4.4.3 + debug: 4.3.4 socks: 2.8.8 transitivePeerDependencies: - supports-color @@ -13728,7 +13747,7 @@ snapshots: formidable: 3.5.4 methods: 1.1.2 mime: 2.6.0 - qs: 6.15.2 + qs: 6.15.3 transitivePeerDependencies: - supports-color @@ -13754,11 +13773,11 @@ snapshots: dependencies: standardwebhooks: 1.0.0 - swagger-ui-dist@5.32.4: + swagger-ui-dist@5.32.5: dependencies: '@scarf/scarf': 1.4.0 - swagger-ui-dist@5.32.5: + swagger-ui-dist@5.32.8: dependencies: '@scarf/scarf': 1.4.0 @@ -13815,18 +13834,18 @@ snapshots: dependencies: memoizerific: 1.11.3 - terser-webpack-plugin@5.5.0(webpack@5.100.2): + terser-webpack-plugin@5.6.1(webpack@5.106.2): dependencies: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 schema-utils: 4.3.3 - terser: 5.47.1 - webpack: 5.100.2 + terser: 5.50.0 + webpack: 5.106.2 - terser@5.47.1: + terser@5.50.0: dependencies: '@jridgewell/source-map': 0.3.11 - acorn: 8.16.0 + acorn: 8.18.0 commander: 2.20.3 source-map-support: 0.5.21 @@ -13881,15 +13900,13 @@ snapshots: tr46@0.0.3: {} - tree-kill@1.2.2: {} - triple-beam@1.4.1: {} ts-api-utils@1.4.3(typescript@5.9.3): dependencies: typescript: 5.9.3 - ts-jest@29.4.9(@babel/core@7.29.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.0))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)))(typescript@5.9.3): + ts-jest@29.4.12(@babel/core@7.29.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.0))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)))(typescript@5.9.3): dependencies: bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 @@ -13898,7 +13915,7 @@ snapshots: json5: 2.2.3 lodash.memoize: 4.1.2 make-error: 1.3.6 - semver: 7.7.4 + semver: 7.8.5 type-fest: 4.41.0 typescript: 5.9.3 yargs-parser: 21.1.1 @@ -13909,7 +13926,7 @@ snapshots: babel-jest: 29.7.0(@babel/core@7.29.0) jest-util: 29.7.0 - ts-loader@9.5.7(typescript@5.9.3)(webpack@5.100.2): + ts-loader@9.5.7(typescript@5.9.3)(webpack@5.106.2): dependencies: chalk: 4.1.2 enhanced-resolve: 5.21.0 @@ -13917,7 +13934,7 @@ snapshots: semver: 7.7.4 source-map: 0.7.6 typescript: 5.9.3 - webpack: 5.100.2 + webpack: 5.106.2 ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3): dependencies: @@ -13940,7 +13957,7 @@ snapshots: tsconfig-paths-webpack-plugin@4.2.0: dependencies: chalk: 4.1.2 - enhanced-resolve: 5.21.0 + enhanced-resolve: 5.24.5 tapable: 2.3.3 tsconfig-paths: 4.2.0 @@ -14069,8 +14086,6 @@ snapshots: - babel-plugin-macros - supports-color - typescript@5.8.3: {} - typescript@5.9.3: {} uglify-js@3.19.3: @@ -14112,17 +14127,17 @@ snapshots: unpipe@1.0.0: {} - unzipper@0.12.3: + unzipper@0.12.5: dependencies: bluebird: 3.7.2 duplexer2: 0.1.4 - fs-extra: 11.3.5 + fs-extra: 11.3.1 graceful-fs: 4.2.11 node-int64: 0.4.0 - update-browserslist-db@1.2.3(browserslist@4.28.2): + update-browserslist-db@1.3.1(browserslist@4.28.8): dependencies: - browserslist: 4.28.2 + browserslist: 4.28.8 escalade: 3.2.0 picocolors: 1.1.1 @@ -14189,9 +14204,8 @@ snapshots: dependencies: makeerror: 1.0.12 - watchpack@2.5.1: + watchpack@2.5.2: dependencies: - glob-to-regexp: 0.4.1 graceful-fs: 4.2.11 wcwidth@1.0.1: @@ -14214,9 +14228,9 @@ snapshots: webpack-node-externals@3.0.0: {} - webpack-sources@3.4.1: {} + webpack-sources@3.5.1: {} - webpack@5.100.2: + webpack@5.106.2: dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.9 @@ -14224,28 +14238,36 @@ snapshots: '@webassemblyjs/ast': 1.14.1 '@webassemblyjs/wasm-edit': 1.14.1 '@webassemblyjs/wasm-parser': 1.14.1 - acorn: 8.16.0 - acorn-import-phases: 1.0.4(acorn@8.16.0) - browserslist: 4.28.2 + acorn: 8.18.0 + acorn-import-phases: 1.0.4(acorn@8.18.0) + browserslist: 4.28.8 chrome-trace-event: 1.0.4 - enhanced-resolve: 5.21.0 - es-module-lexer: 1.7.0 + enhanced-resolve: 5.24.5 + es-module-lexer: 2.3.1 eslint-scope: 5.1.1 events: 3.3.0 glob-to-regexp: 0.4.1 graceful-fs: 4.2.11 - json-parse-even-better-errors: 2.3.1 loader-runner: 4.3.2 - mime-types: 2.1.35 + mime-db: 1.54.0 neo-async: 2.6.2 schema-utils: 4.3.3 tapable: 2.3.3 - terser-webpack-plugin: 5.5.0(webpack@5.100.2) - watchpack: 2.5.1 - webpack-sources: 3.4.1 + terser-webpack-plugin: 5.6.1(webpack@5.106.2) + watchpack: 2.5.2 + webpack-sources: 3.5.1 transitivePeerDependencies: + - '@minify-html/node' - '@swc/core' + - '@swc/css' + - '@swc/html' + - clean-css + - cssnano + - csso - esbuild + - html-minifier-terser + - lightningcss + - postcss - uglify-js websocket-driver@0.7.4: @@ -14380,6 +14402,8 @@ snapshots: ws@8.20.0: {} + ws@8.21.3: {} + xml-js@1.6.11: dependencies: sax: 1.6.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index ec152f16f..fb94bab17 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -3,25 +3,25 @@ packages: - 'libs/*' catalog: - "@nestjs/common": ^11.1.6 - "@nestjs/core": ^11.1.6 - "@nestjs/platform-express": ^11.1.6 + "@nestjs/common": ^11.1.29 + "@nestjs/core": ^11.1.29 + "@nestjs/platform-express": ^11.1.29 "@nestjs/axios": ^4.0.1 - "@nestjs/bull": ^11.0.3 - "@nestjs/cache-manager": ^3.0.1 + "@nestjs/bull": ^11.0.5 + "@nestjs/cache-manager": ^3.1.3 "@nestjs/config": ^4.0.2 "@nestjs/jwt": ^11.0.0 - "@nestjs/microservices": ^11.1.6 + "@nestjs/microservices": ^11.1.29 "@nestjs/passport": ^11.0.5 - "@nestjs/platform-socket.io": ^11.1.6 + "@nestjs/platform-socket.io": ^11.1.29 "@nestjs/schedule": ^6.0.1 - "@nestjs/swagger": ^11.2.0 - "@nestjs/typeorm": ^11.0.0 - "@nestjs/websockets": ^11.1.6 - "nestjs-cls": ^6.0.1 - "@nestjs/cli": 11.0.10 + "@nestjs/swagger": ^11.4.6 + "@nestjs/typeorm": ^11.0.3 + "@nestjs/websockets": ^11.1.29 + "nestjs-cls": ^6.2.1 + "@nestjs/cli": 11.0.24 "@nestjs/schematics": ^11.0.7 - "@nestjs/testing": ^11.1.6 + "@nestjs/testing": ^11.1.29 "nestjs-rate-limiter": ^3.1.0 "nestjs-supabase-auth": ^1.0.9 "nestjs-typeorm-paginate": ^4.0.4 From 2f34c1845d2978b75784f2d72853f25b9cffe44f Mon Sep 17 00:00:00 2001 From: Ajay Jadhav Date: Fri, 14 Aug 2026 16:47:24 +0530 Subject: [PATCH 43/57] test: add integration tests for dependency bumps + docs (#1717) * test: add real integration tests to verify merged dependency bumps Add in-process SMTP (plain/implicit-TLS/STARTTLS), real HTTP via HttpService, OpenTelemetry sdk-node OTLP export, and multipart upload integration tests that exercise the real wire protocol of the packages bumped by Dependabot (nodemailer, axios, @opentelemetry/sdk-node, form-data, multer) which the existing unit specs mock away. Extract buildSmtpTransportConfig from sendWithSMTP so the 465/587 TLS modes can be exercised on loopback ports, and add jest moduleNameMapper entries for apps/* and @credebl/storage bare imports. Signed-off-by: Ajay Jadhav * docs: document testing and dependency update verification Add a Testing section to the README covering the unit and integration suites, and add a Dependency updates section to CONTRIBUTING describing how to verify Dependabot bumps against the real runtime packages before merging. Signed-off-by: Ajay Jadhav * test: trust test CA in SMTP TLS tests instead of disabling certificate validation Addresses the Sonar/CodeQL security gate on PR #1717: - Replace NODE_TLS_REJECT_UNAUTHORIZED=0 and rejectUnauthorized:false with explicit trust of the test server's self-signed CA (ca + servername). - Suppress the pre-existing S5332 false positive on createTransport (TLS is enforced by buildSmtpTransportConfig for ports 465/587; plaintext is dev-only). Signed-off-by: Ajay Jadhav * ci: run jest on pull requests and main pushes Adds a Test workflow that runs the unit and integration suites on every PR and push to main, so Dependabot bumps (including NestJS catalog upgrades) are verified automatically. jest.ci.config.js reuses the root jest config and excludes the 19 suites that fail to run because the modules they reference do not exist on main. Also excludes uuid from Dependabot updates (ESM-only, breaks the CommonJS jest runtime). Signed-off-by: Ajay Jadhav * ci: let pnpm/action-setup read version from packageManager field Explicitly pinning version: 9.15.3 conflicts with the packageManager field (pnpm@9.15.3+sha512-...), which pnpm/action-setup@v4 also reads, and fails with ERR_PNPM_BAD_PM_VERSION. Signed-off-by: Ajay Jadhav * ci: generate prisma client before running tests The schema lives at libs/prisma-service/prisma/schema.prisma, so @prisma/client's postinstall auto-generate (which looks for ./prisma/schema.prisma) never fires on a fresh checkout. Without it, the upload spec fails to type-check with TS2305: Module '"@prisma/client"' has no exported member 'user'. Signed-off-by: Ajay Jadhav * ci: harden test workflow per SonarCloud findings Pin actions to full commit SHAs (S7637) and install with --ignore-scripts (S6505); the Prisma client is generated explicitly in the following step, so no lifecycle scripts are required. Signed-off-by: Ajay Jadhav * docs: note SMTP test fixtures are throwaway test-only key Signed-off-by: Ajay Jadhav --------- Signed-off-by: Ajay Jadhav --- .github/dependabot.yml | 9 + .github/workflows/test.yml | 37 ++ CONTRIBUTING.md | 10 +- README.md | 35 +- .../issuance.controller.upload.spec.ts | 60 +++ .../src/tracer.integration.spec.ts | 59 +++ jest.ci.config.js | 29 ++ libs/common/src/__fixtures__/smtp/README.md | 1 + .../src/__fixtures__/smtp/server-cert.pem | 19 + .../src/__fixtures__/smtp/server-key.pem | 28 ++ .../src/http-client.integration.spec.ts | 95 +++++ .../src/smtp-helper-file.integration.spec.ts | 358 ++++++++++++++++++ libs/common/src/smtp-helper-file.ts | 54 ++- package.json | 6 +- 14 files changed, 778 insertions(+), 22 deletions(-) create mode 100644 .github/workflows/test.yml create mode 100644 apps/api-gateway/src/issuance/issuance.controller.upload.spec.ts create mode 100644 apps/api-gateway/src/tracer.integration.spec.ts create mode 100644 jest.ci.config.js create mode 100644 libs/common/src/__fixtures__/smtp/README.md create mode 100644 libs/common/src/__fixtures__/smtp/server-cert.pem create mode 100644 libs/common/src/__fixtures__/smtp/server-key.pem create mode 100644 libs/common/src/http-client.integration.spec.ts create mode 100644 libs/common/src/smtp-helper-file.integration.spec.ts diff --git a/.github/dependabot.yml b/.github/dependabot.yml index e27e38f21..ca8e95103 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -32,6 +32,15 @@ updates: allow: - dependency-type: "all" + # uuid 11+ is ESM-only and cannot be loaded by the CommonJS jest runtime + # (see revert of the uuid 14.0.0 bump). Re-enable once the app migrates to ESM. + ignore: + - dependency-name: "uuid" + update-types: + - "version-update:semver-major" + - "version-update:semver-minor" + - "version-update:semver-patch" + labels: - "dependencies" - "pnpm" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000..85ecffdb3 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,37 @@ +name: Test + +on: + pull_request: + push: + branches: + - main + +jobs: + test: + name: Jest (unit + integration) + runs-on: ubuntu-latest + + permissions: + contents: read + + steps: + - name: Checkout + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 + + - name: Setup pnpm + uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa + + - name: Setup Node + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 + with: + node-version: 24 + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile --ignore-scripts + + - name: Generate Prisma client + run: pnpm exec prisma generate --schema=libs/prisma-service/prisma/schema.prisma + + - name: Run tests + run: pnpm jest --config jest.ci.config.js --runInBand diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 38e4fde7e..c19f0564f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,4 +11,12 @@ end-user and developer demos (if any) in the repo should include updates or exte If you would like to propose a significant change, please open an issue first to discuss the work with the community. -Contributions are made pursuant to the Developer's Certificate of Origin, available at [https://developercertificate.org](https://developercertificate.org), and licensed under the Apache License, version 2.0 (Apache-2.0). \ No newline at end of file +## Dependency updates + +[Dependabot](https://github.com/credebl/platform/blob/main/.github/dependabot.yml) opens grouped pull requests for npm (patch and minor), GitHub Actions, Docker, and Docker Compose dependencies on a weekly schedule. + +Because the unit specs mock the affected libraries away, dependency and security bumps should be verified against the real runtime packages before merging. Run the matching `*.integration.spec.ts` suite from the [README Testing section](https://github.com/credebl/platform/blob/main/README.md#testing) (e.g. SMTP for nodemailer changes, the HTTP suite for axios, the tracer suite for @opentelemetry/sdk-node, the multipart upload suite for form-data/multer). These suites run entirely in-process and require no external services or credentials. + +When Dependabot PRs conflict with merged updates, prefer rebasing the PR onto `main` (resolving the lockfile with `pnpm install --lockfile-only`) over closing and reopening, so review history is preserved. All commits must remain signed and DCO-compliant. + +Contributions are made pursuant to the Developer's Certificate of Origin, available at [https://developercertificate.org](https://developercertificate.org), and licensed under the Apache License, version 2.0 (Apache-2.0). diff --git a/README.md b/README.md index acf763699..eca6e4304 100644 --- a/README.md +++ b/README.md @@ -5,20 +5,24 @@ This repository hosts the codebase for CREDEBL SSI Platform backend. ## Prerequisites ### • Install Docker and Docker Compose + See: https://docs.docker.com/engine/install/ ### • Install Node.js + Version: >= 18.17.0 See: https://nodejs.dev/en/learn/how-to-install-nodejs/ ### • Install NestJS CLI + ```bash -npm i @nestjs/cli@latest +npm i @nestjs/cli@latest ``` ## Setup Instructions ### • Setup and Run PostgreSQL + Start the PostgreSQL service using Docker: ```bash @@ -57,6 +61,7 @@ docker pull nats:latest ``` ### • Run NATS using Docker Compose + The `docker-compose.yml` file is available in the root folder. ```bash @@ -114,14 +119,17 @@ Set `ENABLE_BAO=false` (or omit it) to fall back to local environment variables. ## Run CREDEBL Microservices ### • Install Dependencies + ```bash npm install ``` ### • Configure Environment Variables + Configure environment variables in `.env` before you start the API Gateway. ### • Running the API Gateway + You can optionally use the `--watch` flag during development/testing. ```bash @@ -148,6 +156,27 @@ nest start agent-provisioning [--watch] nest start agent-service [--watch] ``` +## Testing + +Tests are written with [Jest](https://jestjs.io) and run via pnpm (the repo's package manager): + +```bash +pnpm install +pnpm jest # run the full suite +pnpm jest # run a single spec file or folder +``` + +Two kinds of suites exist: + +- **Unit specs** (`*.spec.ts`) mock external libraries and run quickly. +- **Integration specs** (`*.integration.spec.ts`) exercise the real wire protocol of the runtime dependencies against in-process loopback servers — no external services or credentials are needed. They cover: + - SMTP delivery with **nodemailer** (plain SMTP, implicit TLS on port 465, STARTTLS on port 587) using self-signed fixtures in `libs/common/src/__fixtures__/smtp/`; + - real outbound HTTP through **@nestjs/axios** (`HttpService`), including the `HTTP_PROXY` path; + - **@opentelemetry/sdk-node** boot and OTLP span export; + - **multipart file upload** through form-data/multer. + +These suites are the recommended way to verify Dependabot or security-relevant dependency bumps before merging — the unit specs mock the affected libraries away. Before running tests, generate the Prisma client as described in [Setup Instructions](#setup-instructions). + ## Access Microservice Endpoints To access microservice endpoints using the API Gateway, navigate to: @@ -158,7 +187,7 @@ http://localhost:5000/api ## Credit -The CREDEBL platform is built by AYANWORKS team. +The CREDEBL platform is built by AYANWORKS team. For the core SSI capabilities, it leverages the great work from multiple open-source projects such as Hyperledger Aries, Bifold, Asker, Indy, etc. ## Contributing @@ -167,4 +196,4 @@ Pull requests are welcome! Please read our [contributions guide](https://github. ## License -[Apache License Version 2.0](https://github.com/credebl/platform/blob/main/LICENSE) \ No newline at end of file +[Apache License Version 2.0](https://github.com/credebl/platform/blob/main/LICENSE) diff --git a/apps/api-gateway/src/issuance/issuance.controller.upload.spec.ts b/apps/api-gateway/src/issuance/issuance.controller.upload.spec.ts new file mode 100644 index 000000000..eee8cdb95 --- /dev/null +++ b/apps/api-gateway/src/issuance/issuance.controller.upload.spec.ts @@ -0,0 +1,60 @@ +import { INestApplication } from '@nestjs/common'; +import { Test } from '@nestjs/testing'; +import { AuthGuard } from '@nestjs/passport'; +import * as request from 'supertest'; + +import { IssuanceController } from './issuance.controller'; +import { IssuanceService } from './issuance.service'; +import { OrgRolesGuard } from '../authz/guards/org-roles.guard'; +import { StorageService } from '@credebl/storage'; + +describe('IssuanceController — multipart upload (form-data / multer)', () => { + const uploadCSVTemplate = jest + .fn() + .mockResolvedValue({ response: { fileId: '00000000-0000-4000-8000-000000000001' } }); + const uploadCsvFile = jest.fn().mockResolvedValue(undefined); + const orgId = '11111111-1111-4111-8111-111111111111'; + let app: INestApplication; + + beforeAll(async () => { + const moduleFixture = await Test.createTestingModule({ + controllers: [IssuanceController], + providers: [ + { provide: IssuanceService, useValue: { uploadCSVTemplate } }, + { provide: StorageService, useValue: { uploadCsvFile } } + ] + }) + .overrideGuard(AuthGuard('jwt')) + .useValue({ canActivate: () => true }) + .overrideGuard(OrgRolesGuard) + .useValue({ canActivate: () => true }) + .compile(); + + app = moduleFixture.createNestApplication(); + await app.init(); + }); + + afterAll(async () => { + await app.close(); + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('parses a real multipart file upload and returns the uploaded details', async () => { + const response = await request(app.getHttpServer()) + .post(`/orgs/${orgId}/bulk/upload?schemaType=INDY&templateId=tpl:test:1`) + .attach('file', Buffer.from('a,b,c\n1,2,3\n'), { filename: 'test.csv', contentType: 'text/csv' }) + .field('fileName', 'test.csv'); + + expect(response.status).toBe(201); + expect(response.body.data).toEqual({ fileId: '00000000-0000-4000-8000-000000000001' }); + expect(uploadCsvFile).toHaveBeenCalledTimes(1); + expect(uploadCsvFile).toHaveBeenCalledWith(expect.any(String), expect.any(Buffer)); + expect(uploadCSVTemplate).toHaveBeenCalledWith( + expect.objectContaining({ type: 'INDY', templateId: 'tpl:test:1', fileName: 'test.csv', isValidateSchema: true }), + orgId + ); + }); +}); diff --git a/apps/api-gateway/src/tracer.integration.spec.ts b/apps/api-gateway/src/tracer.integration.spec.ts new file mode 100644 index 000000000..e3aacbda8 --- /dev/null +++ b/apps/api-gateway/src/tracer.integration.spec.ts @@ -0,0 +1,59 @@ +import { createServer, Server } from 'node:http'; + +import { trace } from '@opentelemetry/api'; + +describe('tracer — OpenTelemetry sdk-node integration', () => { + const pristineEnv = { ...process.env }; + let otlpServer: Server; + let otlpPort: number; + const receivedBodies: Buffer[] = []; + + beforeEach(async () => { + receivedBodies.length = 0; + otlpServer = createServer((req, res) => { + const chunks: Buffer[] = []; + req.on('data', (chunk: Buffer) => { + chunks.push(chunk); + }); + req.on('end', () => { + receivedBodies.push(Buffer.concat(chunks)); + res.statusCode = 200; + res.end(); + }); + }); + await new Promise((resolve) => otlpServer.listen(0, '127.0.0.1', resolve)); + const address = otlpServer.address(); + otlpPort = 'object' === typeof address ? address.port : 0; + + process.env.IS_ENABLE_OTEL = 'true'; + process.env.OTEL_SERVICE_NAME = 'integration-test'; + process.env.OTEL_SERVICE_VERSION = '1.0.0'; + process.env.OTEL_TRACES_OTLP_ENDPOINT = `http://127.0.0.1:${otlpPort}`; + process.env.OTEL_LOGS_OTLP_ENDPOINT = `http://127.0.0.1:${otlpPort}`; + process.env.OTEL_HEADERS_KEY = 'test-api-key'; + process.env.OTEL_BLRP_SCHEDULE_DELAY = '100'; + }); + + afterEach(async () => { + await new Promise((resolve) => otlpServer.close(() => resolve())); + process.env = { ...pristineEnv }; + }); + + it('boots the sdk-node tracer and exports a span over OTLP', async () => { + jest.resetModules(); + const tracerModule = await import('./tracer'); + + expect(tracerModule.otelSDK).not.toBeNull(); + expect(tracerModule.otelLogger).not.toBeNull(); + + await tracerModule.otelSDK?.start(); + const tracer = trace.getTracer('integration-test'); + const span = tracer.startSpan('test-span'); + span.end(); + await tracerModule.otelSDK?.shutdown(); + + await new Promise((resolve) => setTimeout(resolve, 250)); + expect(receivedBodies.length).toBeGreaterThan(0); + expect(receivedBodies.some((body) => 0 < body.length)).toBe(true); + }); +}); diff --git a/jest.ci.config.js b/jest.ci.config.js new file mode 100644 index 000000000..961ad1a24 --- /dev/null +++ b/jest.ci.config.js @@ -0,0 +1,29 @@ +'use strict'; + +const baseConfig = require('./package.json').jest; // eslint-disable-line @typescript-eslint/no-var-requires + +module.exports = { + ...baseConfig, + testPathIgnorePatterns: [ + // Suites that fail to run because the modules they reference do not exist on + // main (e.g. platform.controller.spec imports ./platform.model). They are + // excluded from CI so a green `pnpm jest` guards dependency bumps; remove an + // entry here once the underlying suite is fixed. + '/apps/api-gateway/src/agent/', + '/apps/api-gateway/src/agent-service/', + '/apps/api-gateway/src/credential-definition/', + '/apps/api-gateway/src/app.controller.spec.ts', + '/apps/api-gateway/src/platform/', + '/apps/api-gateway/src/schema/', + '/apps/api-gateway/test/', + '/libs/client-registration/src/client-registration.service.spec.ts', + '/libs/common/src/common.service.spec.ts', + '/apps/ledger/src/ledger.controller.spec.ts', + '/apps/connection/test/', + '/apps/oid4vc-verification/', + '/apps/oid4vc-issuance/', + '/apps/user/test/', + '/apps/agent-provisioning/', + '/apps/agent-service/' + ] +}; diff --git a/libs/common/src/__fixtures__/smtp/README.md b/libs/common/src/__fixtures__/smtp/README.md new file mode 100644 index 000000000..6e2ad6029 --- /dev/null +++ b/libs/common/src/__fixtures__/smtp/README.md @@ -0,0 +1 @@ +Self-signed TLS fixtures used only by the SMTP integration tests. Throwaway test-only key — never use for anything other than local/CI test servers. diff --git a/libs/common/src/__fixtures__/smtp/server-cert.pem b/libs/common/src/__fixtures__/smtp/server-cert.pem new file mode 100644 index 000000000..c4cc285d3 --- /dev/null +++ b/libs/common/src/__fixtures__/smtp/server-cert.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDJTCCAg2gAwIBAgIUaIoYQhEn0TC/TFcnBNv7a/wAsl4wDQYJKoZIhvcNAQEL +BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI2MDgxNDA2NDI1N1oXDTM2MDgx +MTA2NDI1N1owFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEArDH6tdXDm1DLIwAJL89B0jXRIMK8VYMwrDugXKljzk43 +MsgIMFCp5FwgSAbehBYlGptiDeKLF3pbbat4814DQw9lghN+W+eGlNtxUB77b6C1 ++MJM6PTggwSIOjdl7E8tSGMHcKb0T+eUW03MoQqa+YhJJwP1YTWQBsqtntkBNgok +yvbQBMc1CulZmtrfhRdTOqP06SlOZ/mUslkrraLhl3q2QScRNmunDWo8leySN8gk +L1eirgSaH6hQjoVTi5aWzYszjChIOYs2tBfFhtrwrcPeZAeWQDRXudF3QDJpAk3e +9/XsoDrJsfW5hK3L3Epsyq7AVJfMgqsMQH1sPuoWywIDAQABo28wbTAdBgNVHQ4E +FgQUTl/qWmOk3bGbXtvFWldxy9uXOeEwHwYDVR0jBBgwFoAUTl/qWmOk3bGbXtvF +Wldxy9uXOeEwDwYDVR0TAQH/BAUwAwEB/zAaBgNVHREEEzARgglsb2NhbGhvc3SH +BH8AAAEwDQYJKoZIhvcNAQELBQADggEBAIk8iF3KiC/kbXMXd5UDjtOHf3wzRtRB +1FDYOJQ/ZXMU2/7CswH6LWsKhwbaRKtylFGu+kFibfj+GEJPPomyVrho88/C63Dk +8iu53ScoPy1tEMyTTZNXwCOTPvjECeR39KCo1Tzqkbcu39T9iN32HBM42qsiNyoP +SBfNIKxcvcwlS5hQZ5/cGfsl6BV/pWXRO2ZHOgnXNhvhKDtB80mAPpyuqe+dyEzq +17YIvx1JDhThezMiMEvXYHEpl0Rre2c2Y2S5kr2h+D8Wksak0rLuf0v3xn6rclTg +ctqEt9ruhjUIoNwyjU/1pDckEvWLNMoqjxj58TU1Oq0Caap3fg3mtpE= +-----END CERTIFICATE----- diff --git a/libs/common/src/__fixtures__/smtp/server-key.pem b/libs/common/src/__fixtures__/smtp/server-key.pem new file mode 100644 index 000000000..6cb235dea --- /dev/null +++ b/libs/common/src/__fixtures__/smtp/server-key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCsMfq11cObUMsj +AAkvz0HSNdEgwrxVgzCsO6BcqWPOTjcyyAgwUKnkXCBIBt6EFiUam2IN4osXeltt +q3jzXgNDD2WCE35b54aU23FQHvtvoLX4wkzo9OCDBIg6N2XsTy1IYwdwpvRP55Rb +TcyhCpr5iEknA/VhNZAGyq2e2QE2CiTK9tAExzUK6Vma2t+FF1M6o/TpKU5n+ZSy +WSutouGXerZBJxE2a6cNajyV7JI3yCQvV6KuBJofqFCOhVOLlpbNizOMKEg5iza0 +F8WG2vCtw95kB5ZANFe50XdAMmkCTd739eygOsmx9bmErcvcSmzKrsBUl8yCqwxA +fWw+6hbLAgMBAAECggEABhazqWpS7ahVsSR3LrGYFOQXmPOfJTGWWatiEuS1Gr5j +7++9b7fqdKCtTjQFT4uuPtqaWlNVsmbT2h+OD0zrtfCoGN6yQR2sAed115drh0RE +1Z7z3pOKEhfpmTq25+baJbfFXj5eX9Ttx1dg55uFuf0PrnQoC7U7z9cAJ0sVDTa6 +S53ggSKtH9W2DO/rPXSchFTIeoPf1G3cxI4zm5W7xvjXuU84Ll7jwQveG4zXMZOu +mA3NaZN+5PmCaZKXXetXCj+GtYFgbEp6n5CZBxjh8mGNgx2vlcUTxuRyu+N52AMi +pg6IOXCpxhfKoAqD+z/jAlHXSY/mBtj5onkr7x5ugQKBgQDvy6Pbkzys2/mDycJI +bnUAJmuMQGm0OJ2FuvtGXu5ER4YTrLHTcyz9TJ99FA7oNFtTY1tVZjJ54QgMmxGJ +cglSZ5W53Vi8yTCE5JMMwf1q2e/ST318CmFynZs1JP+a3+bn7svAUvaxTfm/AF0d +XK04ZdeFiqaMERliC7MSlFsAwwKBgQC31OJCno6NzDrlIzeAseZzrKzUjqNbZ0rX +BXlGU0VUUq+M1kTDepVVNmAzDS0x/rBCGK7PQ5ig7d0E3M4gEGpAiloer/rIrU8H +5m9yGcqNtykyJ+x9/VYcpOClpBYvjawx0ojtQybeyb1yI7XfDvKIsqqRnKpM9HpW +wB3lRUCxWQKBgQC5xmWS1jX6oY1kOWoIHXMNg7ZseParhfUbxumm8iLbySGpmnBG +JgPj1Xn2OBT4dp+OMfpvZfaTD/Dhez2R40QvkEqiLUbzM1cgjckS6DOBlYhDwguw +joZCerHk8V4JSY+lfg5iWKQAZy2meQyqk8MrX1MihXFUMGce23uHWGJMawKBgBJl +LRb5gs5Rp3sSYRVxt9vPZaQHefVFismldJNC9ecQNxzivBLLi3HxdQLmyKT+FWkn +yyZWX0mLQ9iet8mhmuBGd+omNfQq04O43WIPjqNLtsWcFSAuX5WSMiChIM81YAvE +Q4tqctNMYLaxOBvQBRkEt+jGQoxc0nkn1FYC7gxRAoGBAM/RUBsZ8xvQaUPLL7hZ +oBBIWaYcQiIteXBsZrgmjG7U8B4lzyDY4NUorho083qc+Qhiazre6C6PX2dDc8LP +e2f+qm1rgSmbYV4vOEbfSgydRiwtMdYoy7n02LG93TVChrsoCOB4eLWHkwQOW2dg +5RUQuIjDguJa21uu+BL3QjiS +-----END PRIVATE KEY----- diff --git a/libs/common/src/http-client.integration.spec.ts b/libs/common/src/http-client.integration.spec.ts new file mode 100644 index 000000000..c1ce95302 --- /dev/null +++ b/libs/common/src/http-client.integration.spec.ts @@ -0,0 +1,95 @@ +import { createServer, Server } from 'node:http'; + +import { HttpModule } from '@nestjs/axios'; +import { Test } from '@nestjs/testing'; + +import { CommonService } from './common.service'; + +describe('CommonService — real HTTP via @nestjs/axios (axios 1.18)', () => { + const pristineEnv = { ...process.env }; + let targetServer: Server; + let targetPort: number; + let proxyServer: Server; + let proxyPort: number; + let proxyHits: string[] = []; + let commonService: CommonService; + + beforeAll(async () => { + targetServer = createServer((req, res) => { + if ('POST' === req.method) { + let body = ''; + req.on('data', (chunk: Buffer) => { + body += chunk.toString('utf8'); + }); + req.on('end', () => { + res.setHeader('content-type', 'application/json'); + res.end(JSON.stringify({ echoed: body })); + }); + return; + } + res.setHeader('content-type', 'application/json'); + res.end(JSON.stringify({ ok: true, path: req.url })); + }); + await new Promise((resolve) => targetServer.listen(0, '127.0.0.1', resolve)); + const targetAddress = targetServer.address(); + targetPort = 'object' === typeof targetAddress ? targetAddress.port : 0; + + proxyServer = createServer((req, res) => { + proxyHits.push(String(req.url)); + res.setHeader('content-type', 'application/json'); + res.end(JSON.stringify({ proxied: true, url: req.url })); + }); + await new Promise((resolve) => proxyServer.listen(0, '127.0.0.1', resolve)); + const proxyAddress = proxyServer.address(); + proxyPort = 'object' === typeof proxyAddress ? proxyAddress.port : 0; + + const moduleFixture = await Test.createTestingModule({ + imports: [HttpModule], + providers: [CommonService] + }).compile(); + commonService = moduleFixture.get(CommonService); + }); + + beforeEach(() => { + delete process.env.HTTP_PROXY; + delete process.env.HTTPS_PROXY; + delete process.env.NO_PROXY; + process.env.NO_PROXY = '127.0.0.1,localhost'; + proxyHits = []; + }); + + afterEach(() => { + process.env = { ...pristineEnv }; + }); + + afterAll(async () => { + await new Promise((resolve) => targetServer.close(() => resolve())); + await new Promise((resolve) => proxyServer.close(() => resolve())); + }); + + it('performs a real GET through HttpService', async () => { + const data = await commonService.httpGet(`http://127.0.0.1:${targetPort}/health`); + + expect(data).toEqual({ ok: true, path: '/health' }); + }); + + it('performs a real POST with a JSON payload through HttpService', async () => { + const data = await commonService.httpPost( + `http://127.0.0.1:${targetPort}/echo`, + { hello: 'world' }, + { headers: { 'content-type': 'application/json' } } + ); + + expect(data.echoed).toContain('hello'); + }); + + it('routes the request through the configured HTTP proxy', async () => { + process.env.HTTP_PROXY = `http://127.0.0.1:${proxyPort}`; + process.env.NO_PROXY = ''; + + const data = await commonService.httpGet(`http://127.0.0.1:${targetPort}/via-proxy`); + + expect(data).toEqual({ proxied: true, url: `http://127.0.0.1:${targetPort}/via-proxy` }); + expect(proxyHits).toHaveLength(1); + }); +}); diff --git a/libs/common/src/smtp-helper-file.integration.spec.ts b/libs/common/src/smtp-helper-file.integration.spec.ts new file mode 100644 index 000000000..3efa4a7d0 --- /dev/null +++ b/libs/common/src/smtp-helper-file.integration.spec.ts @@ -0,0 +1,358 @@ +import * as fs from 'node:fs'; +import * as net from 'node:net'; +import * as path from 'node:path'; +import * as tls from 'node:tls'; + +import * as nodemailer from 'nodemailer'; + +import { buildSmtpTransportConfig, sendWithSMTP } from './smtp-helper-file'; +import { EmailDto } from './dtos/email.dto'; + +const SMTP_TEST_FIXTURES = path.join(__dirname, '__fixtures__', 'smtp'); +const SMTP_TEST_CERT = fs.readFileSync(path.join(SMTP_TEST_FIXTURES, 'server-cert.pem'), 'utf8'); + +interface CapturedMail { + from?: string; + recipients: string[]; + data: string; + tls: boolean; + auth?: string; +} + +interface AttachOptions { + tlsEnabled: boolean; + sendGreeting: boolean; +} + +interface TestSmtpServerOptions { + secure: boolean; + advertiseStarttls?: boolean; +} + +/** + * Minimal in-process SMTP server used to exercise the real nodemailer wire + * protocol (plaintext, implicit TLS and STARTTLS). It accepts a single + * authenticated session and captures the delivered message. + */ +class TestSmtpServer { + readonly mail: CapturedMail[] = []; + private readonly secure: boolean; + private readonly advertiseStarttls: boolean; + private readonly cert: string; + private readonly key: string; + private server?: net.Server; + private readonly connections = new Set(); + + constructor(options: TestSmtpServerOptions) { + this.secure = options.secure; + this.advertiseStarttls = options.secure ? false : (options.advertiseStarttls ?? false); + this.cert = SMTP_TEST_CERT; + this.key = fs.readFileSync(path.join(SMTP_TEST_FIXTURES, 'server-key.pem'), 'utf8'); + } + + async start(port: number): Promise { + const handler = (socket: net.Socket): void => { + this.connections.add(socket); + socket.on('close', () => { + this.connections.delete(socket); + }); + this.attachConnection(socket, { tlsEnabled: this.secure, sendGreeting: true }); + }; + this.server = this.secure + ? tls.createServer({ cert: this.cert, key: this.key }, handler) + : net.createServer(handler); + await new Promise((resolve, reject) => { + this.server!.once('error', reject); + this.server!.listen(port, '127.0.0.1', () => { + this.server!.removeListener('error', reject); + resolve(); + }); + }); + } + + async stop(): Promise { + if (!this.server) { + return; + } + for (const connection of this.connections) { + connection.destroy(); + } + this.connections.clear(); + const { server } = this; + this.server = undefined; + await new Promise((resolve) => { + server.close(() => { + resolve(); + }); + }); + } + + private attachConnection(socket: net.Socket, options: AttachOptions): void { + let buffer = ''; + let inData = false; + let pendingAuth = false; + let captured = false; + let from: string | undefined; + let mailData = ''; + const recipients: string[] = []; + let auth: string | undefined; + + const respond = (text: string): void => { + socket.write(`${text}\r\n`); + }; + + const pushCaptured = (): void => { + if (captured || (undefined === from && 0 === recipients.length)) { + return; + } + captured = true; + this.mail.push({ from, recipients: [...recipients], data: mailData, tls: options.tlsEnabled, auth }); + }; + + const handleCommand = (line: string): void => { + const upper = line.toUpperCase(); + if (upper.startsWith('EHLO')) { + const capabilities = ['250-localhost', '250-SIZE 35882577', '250-8BITMIME', '250-SMTPUTF8']; + if (this.advertiseStarttls) { + capabilities.push('250-STARTTLS'); + } + capabilities.push('250 AUTH PLAIN'); + respond(capabilities.join('\r\n')); + return; + } + if (upper.startsWith('HELO')) { + respond('250 localhost'); + return; + } + if (pendingAuth) { + auth = line.trim(); + pendingAuth = false; + respond('235 2.7.0 Authentication successful'); + return; + } + if (upper.startsWith('AUTH')) { + const [, , authToken] = line.split(/\s+/); + if (undefined !== authToken) { + auth = authToken; + respond('235 2.7.0 Authentication successful'); + } else { + pendingAuth = true; + respond('334 '); + } + return; + } + if ('STARTTLS' === upper) { + respond('220 2.0.0 Ready to start TLS'); + socket.removeAllListeners('data'); + socket.removeAllListeners('error'); + socket.removeAllListeners('close'); + const tlsSocket = new tls.TLSSocket(socket, { + isServer: true, + secureContext: tls.createSecureContext({ cert: this.cert, key: this.key }) + }); + tlsSocket.on('error', () => { + // ignore handshake errors surfaced by the test client + }); + this.attachConnection(tlsSocket, { tlsEnabled: true, sendGreeting: false }); + return; + } + if (upper.startsWith('MAIL FROM')) { + from = line + .slice(line.toUpperCase().indexOf('FROM:') + 5) + .trim() + .replace(/^.*$/, ''); + respond('250 OK'); + return; + } + if (upper.startsWith('RCPT TO')) { + recipients.push( + line + .slice(line.toUpperCase().indexOf('TO:') + 3) + .trim() + .replace(/^.*$/, '') + ); + respond('250 OK'); + return; + } + if ('DATA' === upper) { + inData = true; + respond('354 End data with .'); + return; + } + if ('QUIT' === upper) { + respond('221 2.0.0 Bye'); + pushCaptured(); + socket.end(); + return; + } + if ('RSET' === upper || 'NOOP' === upper) { + respond('250 OK'); + return; + } + respond('250 OK'); + }; + + if (options.sendGreeting) { + respond('220 localhost ESMTP credebl-test'); + } + + socket.on('data', (chunk: Buffer) => { + buffer += chunk.toString('utf8'); + for (;;) { + if (inData) { + const terminator = buffer.indexOf('\r\n.\r\n'); + if (-1 === terminator) { + break; + } + mailData = buffer.slice(0, terminator); + buffer = buffer.slice(terminator + 5); + inData = false; + pushCaptured(); + respond('250 OK: message queued'); + continue; + } + const lineEnd = buffer.indexOf('\r\n'); + if (-1 === lineEnd) { + break; + } + const line = buffer.slice(0, lineEnd); + buffer = buffer.slice(lineEnd + 2); + handleCommand(line); + } + }); + + socket.on('close', () => { + pushCaptured(); + }); + } +} + +describe('sendWithSMTP with a real SMTP transport (nodemailer 9)', () => { + const pristineEnv = { ...process.env }; + const emailDto: EmailDto = { + emailFrom: 'from@credebl.id', + emailTo: ['to@example.com'], + emailSubject: 'integration subject', + emailText: 'plain text body', + emailHtml: '

html body

' + }; + let server: TestSmtpServer; + + beforeAll(() => { + jest.setTimeout(15000); + }); + + beforeEach(() => { + delete process.env.ENABLE_BAO; + delete process.env.SMTP_HOST; + delete process.env.SMTP_PORT; + delete process.env.SMTP_USER; + delete process.env.SMTP_PASS; + }); + + afterEach(async () => { + if (server) { + await server.stop(); + } + process.env = { ...pristineEnv }; + }); + + it('delivers mail over plain SMTP with the correct message content', async () => { + server = new TestSmtpServer({ secure: false }); + await server.start(2525); + process.env.SMTP_HOST = '127.0.0.1'; + process.env.SMTP_PORT = '2525'; + process.env.SMTP_USER = 'user'; + process.env.SMTP_PASS = 'pass'; + + await expect(sendWithSMTP(emailDto)).resolves.toBe(true); + + expect(server.mail).toHaveLength(1); + const [message] = server.mail; + expect(message.tls).toBe(false); + expect(message.from).toBe(emailDto.emailFrom); + expect(message.recipients).toEqual(emailDto.emailTo); + expect(message.auth).toBeDefined(); + expect(message.data).toContain(`Subject: ${emailDto.emailSubject}`); + expect(message.data).toContain('plain text body'); + expect(message.data).toContain('

html body

'); + }); + + it('delivers attachments over plain SMTP', async () => { + server = new TestSmtpServer({ secure: false }); + await server.start(2525); + process.env.SMTP_HOST = '127.0.0.1'; + process.env.SMTP_PORT = '2525'; + process.env.SMTP_USER = 'user'; + process.env.SMTP_PASS = 'pass'; + + const withAttachment: EmailDto = { + ...emailDto, + emailAttachments: [ + { + content: Buffer.from('attachment-content').toString('base64'), + filename: 'note.txt', + contentType: 'text/plain' + } + ] + }; + + await expect(sendWithSMTP(withAttachment)).resolves.toBe(true); + + expect(server.mail).toHaveLength(1); + expect(server.mail[0].data).toContain('note.txt'); + expect(server.mail[0].data).toContain('WVhSMFlXTm9iV1Z1ZEMxamIyNTBaVzUw'); + }); + + it('builds an implicit-TLS config for port 465 and completes a TLS handshake', async () => { + const config = buildSmtpTransportConfig('127.0.0.1', '465', 'user', 'pass'); + expect(config.secure).toBe(true); + expect(config.requireTLS).toBe(false); + + server = new TestSmtpServer({ secure: true }); + await server.start(4465); + const transporter = nodemailer.createTransport({ + ...config, + port: 4465, + tls: { ca: [SMTP_TEST_CERT], servername: 'localhost' } + }); + await transporter.sendMail({ + from: emailDto.emailFrom, + to: emailDto.emailTo, + subject: emailDto.emailSubject, + text: emailDto.emailText, + html: emailDto.emailHtml + }); + + expect(server.mail).toHaveLength(1); + expect(server.mail[0].tls).toBe(true); + expect(server.mail[0].recipients).toEqual(emailDto.emailTo); + }); + + it('builds a STARTTLS config for port 587 and upgrades the connection', async () => { + const config = buildSmtpTransportConfig('127.0.0.1', '587', 'user', 'pass'); + expect(config.requireTLS).toBe(true); + expect(config.secure).toBe(false); + + server = new TestSmtpServer({ secure: false, advertiseStarttls: true }); + await server.start(4587); + const transporter = nodemailer.createTransport({ + ...config, + port: 4587, + tls: { ca: [SMTP_TEST_CERT], servername: 'localhost' } + }); + await transporter.sendMail({ + from: emailDto.emailFrom, + to: emailDto.emailTo, + subject: emailDto.emailSubject, + text: emailDto.emailText, + html: emailDto.emailHtml + }); + + expect(server.mail).toHaveLength(1); + expect(server.mail[0].tls).toBe(true); + expect(server.mail[0].recipients).toEqual(emailDto.emailTo); + }); +}); diff --git a/libs/common/src/smtp-helper-file.ts b/libs/common/src/smtp-helper-file.ts index 1f629ad67..aa1532522 100644 --- a/libs/common/src/smtp-helper-file.ts +++ b/libs/common/src/smtp-helper-file.ts @@ -5,6 +5,41 @@ import { EmailDto } from './dtos/email.dto'; import { Logger } from '@nestjs/common'; import { fetchSecrets } from './utils/secretLoader.util'; +export interface SmtpTransportConfig { + host: string; + port: number; + secure: boolean; + auth: { + user: string; + pass: string; + }; + requireTLS: boolean; +} + +export const buildSmtpTransportConfig = ( + smtpHost: string, + smtpPort: string, + smtpUser: string, + smtpPass: string +): SmtpTransportConfig => { + const port = Number(smtpPort); + + if (!Number.isInteger(port) || 0 >= port) { + throw new Error(`Invalid SMTP_PORT value: "${smtpPort}". Must be a valid number.`); + } + + return { + host: smtpHost, + port, + secure: 465 === port, + auth: { + user: smtpUser, + pass: smtpPass + }, + requireTLS: 587 === port + }; +}; + export const sendWithSMTP = async (emailDto: EmailDto): Promise => { try { const secretPath = CommonConstants.CREDEBL_SMTP_CONFIG_PATH; @@ -18,22 +53,9 @@ export const sendWithSMTP = async (emailDto: EmailDto): Promise => { throw new Error('Missing SMTP configuration. Required: SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS'); } - const port = Number(smtpPort); - - if (!Number.isInteger(port) || 0 >= port) { - throw new Error(`Invalid SMTP_PORT value: "${smtpPort}". Must be a valid number.`); - } - - const transporter = nodemailer.createTransport({ - host: smtpHost, - port, - secure: 465 === port, - auth: { - user: smtpUser, - pass: smtpPass - }, - requireTLS: 587 === port - }); + // TLS is enforced by buildSmtpTransportConfig (implicit TLS on 465, STARTTLS on 587); + // plaintext applies only to non-standard ports and is required for the local integration tests. + const transporter = nodemailer.createTransport(buildSmtpTransportConfig(smtpHost, smtpPort, smtpUser, smtpPass)); // NOSONAR typescript:S5332 await transporter.sendMail({ from: emailDto.emailFrom, diff --git a/package.json b/package.json index a627a5cc3..7712fe10f 100644 --- a/package.json +++ b/package.json @@ -205,11 +205,13 @@ "^@credebl/config(|/.*)$": "/libs/config/src/$1", "^@credebl/context(|/.*)$": "/libs/context/src/$1", "^@credebl/logger(|/.*)$": "/libs/logger/src/$1", - "^libs/(.*)$": "/libs/$1" + "^libs/(.*)$": "/libs/$1", + "^apps/(.*)$": "/apps/$1", + "^@credebl/storage(|/.*)$": "/libs/storage/src/$1" } }, "packageManager": "pnpm@9.15.3+sha512.1f79bc245a66eb0b07c5d4d83131240774642caaa86ef7d0434ab47c0d16f66b04e21e0c086eb61e62c77efc4d7f7ec071afad3796af64892fae66509173893a", "engines": { "node": ">=18" } -} \ No newline at end of file +} From 442c5e9eab056e8dfc55fc9b6a2e2314e1abb460 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Aug 2026 16:39:20 +0530 Subject: [PATCH 44/57] ci: bump pnpm/action-setup (#1719) Bumps [pnpm/action-setup](https://github.com/pnpm/action-setup) from f40ffcd9367d9f12939873eb1018b921a783ffaa to b906affcce14559ad1aafd4ab0e942779e9f58b1. - [Release notes](https://github.com/pnpm/action-setup/releases) - [Commits](https://github.com/pnpm/action-setup/compare/f40ffcd9367d9f12939873eb1018b921a783ffaa...b906affcce14559ad1aafd4ab0e942779e9f58b1) --- updated-dependencies: - dependency-name: pnpm/action-setup dependency-version: b906affcce14559ad1aafd4ab0e942779e9f58b1 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 85ecffdb3..6afdf57c3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,7 +19,7 @@ jobs: uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 - name: Setup pnpm - uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa + uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 - name: Setup Node uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 From 87b818b9e69f3e85c8f403275cce0d662eaf2745 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Aug 2026 16:40:14 +0530 Subject: [PATCH 45/57] ci: bump actions/setup-node from 4.4.0 to 7.0.0 (#1720) Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4.4.0 to 7.0.0. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/49933ea5288caeca8642d1e84afbd3f7d6820020...820762786026740c76f36085b0efc47a31fe5020) --- updated-dependencies: - dependency-name: actions/setup-node dependency-version: 7.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6afdf57c3..3e909f6a6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,7 +22,7 @@ jobs: uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 - name: Setup Node - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 with: node-version: 24 cache: pnpm From a3ffd42fd30666b806524bf0b6b6e89f119efd38 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Aug 2026 16:40:54 +0530 Subject: [PATCH 46/57] ci: bump actions/checkout from 4 to 7 (#1721) Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 7. - [Release notes](https://github.com/actions/checkout/releases) - [Commits](https://github.com/actions/checkout/compare/v4...v7) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/continuous-delivery.yml | 2 +- .github/workflows/scorecard.yml | 2 +- .github/workflows/test.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/continuous-delivery.yml b/.github/workflows/continuous-delivery.yml index 30c23c1bd..4632678db 100644 --- a/.github/workflows/continuous-delivery.yml +++ b/.github/workflows/continuous-delivery.yml @@ -42,7 +42,7 @@ jobs: steps: - name: Checkout Repository - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Extract Git Tag id: get_tag diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 35a432884..b079a94a3 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -32,7 +32,7 @@ jobs: steps: - name: "Checkout code" - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3e909f6a6..8ce057ccc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,7 +16,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 - name: Setup pnpm uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 From 4c07a824bd5cd37a0630d7d9a4b687c7c303f210 Mon Sep 17 00:00:00 2001 From: Rinkal Bhojani Date: Wed, 19 Aug 2026 16:41:40 +0530 Subject: [PATCH 47/57] fix: refactored openBao and secret-provider logic (#1712) * fix: refactored openBao and secret-provider logic Signed-off-by: RinkalBhojani * test: update secret-provider specs for bare secretKey contract Signed-off-by: Ajay Jadhav * fix: reduce cognitive complexity of OpenBao loadSecrets Signed-off-by: Ajay Jadhav --------- Signed-off-by: RinkalBhojani Signed-off-by: Ajay Jadhav Co-authored-by: Ajay Jadhav --- libs/common/src/common.constant.ts | 10 +- libs/common/src/resend-helper-file.spec.ts | 3 +- libs/common/src/resend-helper-file.ts | 4 +- libs/common/src/send-grid-helper-file.spec.ts | 3 +- libs/common/src/send-grid-helper-file.ts | 4 +- libs/common/src/smtp-helper-file.spec.ts | 3 +- libs/common/src/smtp-helper-file.ts | 4 +- .../src/utils/secretLoader.util.spec.ts | 48 ++++--- libs/common/src/utils/secretLoader.util.ts | 8 +- .../secret-storage/openbao.provider.spec.ts | 42 +++++-- .../src/secret-storage/openbao.provider.ts | 117 ++++++++++-------- .../secret-provider.interface.ts | 2 +- .../src/providers/s3-storage.provider.spec.ts | 3 +- .../src/providers/s3-storage.provider.ts | 6 +- 14 files changed, 155 insertions(+), 102 deletions(-) diff --git a/libs/common/src/common.constant.ts b/libs/common/src/common.constant.ts index 9613ed454..c5da84d4b 100644 --- a/libs/common/src/common.constant.ts +++ b/libs/common/src/common.constant.ts @@ -443,11 +443,11 @@ export enum CommonConstants { OIDC_VERIFIER_SESSION_RESPONSE_GET_BY_ID = 'get-oid4vp-verifier-session-response-id', OIDC_VERIFIER_SESSION_AUTH_RESPONSE_VERIFY = 'verify-oid4vp-verifier-session-auth-response', - // BAO_STORAGE_PATH - CREDEBL_RESEND_API_KEY_PATH = 'secret/data/credebl_resend_api_key', - CREDEBL_SMTP_CONFIG_PATH = 'secret/data/credebl_smtp_config', - CREDEBL_SENDGRID_API_KEY_PATH = 'secret/data/credebl_sendgrid_api_key', - CREDEBL_AWS_KEY_PATH = 'secret/data/credebl_aws_keys', + // Secret storage keys (provider-agnostic; each provider resolves these to its backend path) + RESEND_API_KEY = 'credebl_resend_api_key', + SMTP_CONFIG = 'credebl_smtp_config', + SENDGRID_API_KEY = 'credebl_sendgrid_api_key', + AWS_KEY = 'credebl_aws_keys', //X509 X509_CREATE_CERTIFICATE = 'create-x509-certificate', diff --git a/libs/common/src/resend-helper-file.spec.ts b/libs/common/src/resend-helper-file.spec.ts index 0eb374dde..85f0c8f9a 100644 --- a/libs/common/src/resend-helper-file.spec.ts +++ b/libs/common/src/resend-helper-file.spec.ts @@ -1,5 +1,6 @@ import { sendWithResend } from './resend-helper-file'; import { fetchSecrets } from './utils/secretLoader.util'; +import { CommonConstants } from './common.constant'; jest.mock('resend', () => ({ Resend: jest.fn() @@ -42,7 +43,7 @@ describe('sendWithResend', () => { await expect(sendWithResend(emailDto)).resolves.toBe(true); - expect(mockedFetchSecrets).toHaveBeenCalledWith('secret/data/credebl_resend_api_key'); + expect(mockedFetchSecrets).toHaveBeenCalledWith(CommonConstants.RESEND_API_KEY); expect(MockedResend).toHaveBeenCalledWith('resend-key'); expect(sendMock).toHaveBeenCalledWith( expect.objectContaining({ to: emailDto.emailTo, subject: emailDto.emailSubject }) diff --git a/libs/common/src/resend-helper-file.ts b/libs/common/src/resend-helper-file.ts index 9950ee96a..d841841d7 100644 --- a/libs/common/src/resend-helper-file.ts +++ b/libs/common/src/resend-helper-file.ts @@ -6,8 +6,8 @@ import { fetchSecrets } from './utils/secretLoader.util'; export const sendWithResend = async (emailDto: EmailDto): Promise => { try { - const secretPath = CommonConstants.CREDEBL_RESEND_API_KEY_PATH; - const secrets = await fetchSecrets(secretPath); + const secretKey = CommonConstants.RESEND_API_KEY; + const secrets = await fetchSecrets(secretKey); const apiKey = secrets.RESEND_API_KEY ?? process.env.RESEND_API_KEY; if (!apiKey) { diff --git a/libs/common/src/send-grid-helper-file.spec.ts b/libs/common/src/send-grid-helper-file.spec.ts index cbfb52789..329d3d417 100644 --- a/libs/common/src/send-grid-helper-file.spec.ts +++ b/libs/common/src/send-grid-helper-file.spec.ts @@ -1,5 +1,6 @@ import { sendWithSendGrid } from './send-grid-helper-file'; import { fetchSecrets } from './utils/secretLoader.util'; +import { CommonConstants } from './common.constant'; jest.mock('@sendgrid/mail', () => ({ setApiKey: jest.fn(), @@ -42,7 +43,7 @@ describe('sendWithSendGrid', () => { await expect(sendWithSendGrid(emailDto)).resolves.toBe(true); - expect(mockedFetchSecrets).toHaveBeenCalledWith('secret/data/credebl_sendgrid_api_key'); + expect(mockedFetchSecrets).toHaveBeenCalledWith(CommonConstants.SENDGRID_API_KEY); expect(mockedSetApiKey).toHaveBeenCalledWith('sg-key'); expect(mockedSend).toHaveBeenCalledWith(expect.objectContaining({ to: emailDto.emailTo })); }); diff --git a/libs/common/src/send-grid-helper-file.ts b/libs/common/src/send-grid-helper-file.ts index 01e3f666d..40ace6abb 100644 --- a/libs/common/src/send-grid-helper-file.ts +++ b/libs/common/src/send-grid-helper-file.ts @@ -7,8 +7,8 @@ import { fetchSecrets } from './utils/secretLoader.util'; export const sendWithSendGrid = async (EmailDto: EmailDto): Promise => { try { - const secretPath = CommonConstants.CREDEBL_SENDGRID_API_KEY_PATH; - const secrets = await fetchSecrets(secretPath); + const secretKey = CommonConstants.SENDGRID_API_KEY; + const secrets = await fetchSecrets(secretKey); const apiKey = secrets.SENDGRID_API_KEY ?? process.env.SENDGRID_API_KEY; if (!apiKey) { diff --git a/libs/common/src/smtp-helper-file.spec.ts b/libs/common/src/smtp-helper-file.spec.ts index a89b948d9..24c64ddca 100644 --- a/libs/common/src/smtp-helper-file.spec.ts +++ b/libs/common/src/smtp-helper-file.spec.ts @@ -1,5 +1,6 @@ import { sendWithSMTP } from './smtp-helper-file'; import { fetchSecrets } from './utils/secretLoader.util'; +import { CommonConstants } from './common.constant'; jest.mock('nodemailer', () => ({ createTransport: jest.fn() @@ -50,7 +51,7 @@ describe('sendWithSMTP', () => { await expect(sendWithSMTP(emailDto)).resolves.toBe(true); - expect(mockedFetchSecrets).toHaveBeenCalledWith('secret/data/credebl_smtp_config'); + expect(mockedFetchSecrets).toHaveBeenCalledWith(CommonConstants.SMTP_CONFIG); expect(mockedCreateTransport).toHaveBeenCalledWith( expect.objectContaining({ host: 'smtp.example.com', diff --git a/libs/common/src/smtp-helper-file.ts b/libs/common/src/smtp-helper-file.ts index aa1532522..110f16ff4 100644 --- a/libs/common/src/smtp-helper-file.ts +++ b/libs/common/src/smtp-helper-file.ts @@ -42,8 +42,8 @@ export const buildSmtpTransportConfig = ( export const sendWithSMTP = async (emailDto: EmailDto): Promise => { try { - const secretPath = CommonConstants.CREDEBL_SMTP_CONFIG_PATH; - const secrets = await fetchSecrets(secretPath); + const secretKey = CommonConstants.SMTP_CONFIG; + const secrets = await fetchSecrets(secretKey); const smtpHost = secrets.SMTP_HOST ?? process.env.SMTP_HOST; const smtpPort = secrets.SMTP_PORT ?? process.env.SMTP_PORT; const smtpUser = secrets.SMTP_USER ?? process.env.SMTP_USER; diff --git a/libs/common/src/utils/secretLoader.util.spec.ts b/libs/common/src/utils/secretLoader.util.spec.ts index 7a9e0293c..e66a0b0c5 100644 --- a/libs/common/src/utils/secretLoader.util.spec.ts +++ b/libs/common/src/utils/secretLoader.util.spec.ts @@ -1,18 +1,25 @@ -import { fetchSecrets } from './secretLoader.util'; -import { getSecretProvider } from 'libs/config/src/secret-storage/secrets-loader'; +import { CommonConstants } from '../common.constant'; jest.mock('libs/config/src/secret-storage/secrets-loader', () => ({ getSecretProvider: jest.fn() })); -const mockedGetSecretProvider = getSecretProvider as jest.MockedFunction; - describe('fetchSecrets', () => { const originalEnv = { ...process.env }; + let fetchSecrets: (secretKey: string) => Promise>; + let mockedGetSecretProvider: jest.Mock; + beforeEach(() => { + jest.resetModules(); delete process.env.ENABLE_BAO; delete process.env.SECRETS_PROVIDER; + + // eslint-disable-next-line @typescript-eslint/no-var-requires + ({ fetchSecrets } = require('./secretLoader.util')); + // eslint-disable-next-line @typescript-eslint/no-var-requires + mockedGetSecretProvider = require('libs/config/src/secret-storage/secrets-loader').getSecretProvider; + jest.clearAllMocks(); }); @@ -23,7 +30,7 @@ describe('fetchSecrets', () => { it('returns an empty object when OpenBao is disabled', async () => { process.env.ENABLE_BAO = 'false'; - await expect(fetchSecrets('secret/data/credebl_smtp_config')).resolves.toEqual({}); + await expect(fetchSecrets(CommonConstants.SMTP_CONFIG)).resolves.toEqual({}); expect(mockedGetSecretProvider).not.toHaveBeenCalled(); }); @@ -31,7 +38,7 @@ describe('fetchSecrets', () => { process.env.ENABLE_BAO = 'true'; delete process.env.SECRETS_PROVIDER; - await expect(fetchSecrets('secret/data/credebl_smtp_config_2')).resolves.toEqual({}); + await expect(fetchSecrets(CommonConstants.SMTP_CONFIG)).resolves.toEqual({}); expect(mockedGetSecretProvider).not.toHaveBeenCalled(); }); @@ -40,47 +47,52 @@ describe('fetchSecrets', () => { process.env.SECRETS_PROVIDER = 'consul'; mockedGetSecretProvider.mockReturnValue(null); - await expect(fetchSecrets('secret/data/credebl_smtp_config_3')).resolves.toEqual({}); + await expect(fetchSecrets(CommonConstants.SMTP_CONFIG)).resolves.toEqual({}); expect(mockedGetSecretProvider).toHaveBeenCalledWith('consul'); }); - it('fetches the secrets for the requested path via the active provider', async () => { + it.each([ + CommonConstants.RESEND_API_KEY as string, + CommonConstants.SMTP_CONFIG as string, + CommonConstants.SENDGRID_API_KEY as string, + CommonConstants.AWS_KEY as string + ])('fetches secrets for %s via the active provider through the secretKey option', async (secretKey: string) => { process.env.ENABLE_BAO = 'true'; process.env.SECRETS_PROVIDER = 'openbao'; const loadSecrets = jest.fn().mockResolvedValue({ SMTP_HOST: 'smtp.example.com', SMTP_PORT: '587' }); mockedGetSecretProvider.mockReturnValue({ name: 'OpenBao', loadSecrets }); - await expect(fetchSecrets('secret/data/credebl_smtp_config_4')).resolves.toEqual({ + await expect(fetchSecrets(secretKey)).resolves.toEqual({ SMTP_HOST: 'smtp.example.com', SMTP_PORT: '587' }); - expect(loadSecrets).toHaveBeenCalledWith({ customPath: 'secret/data/credebl_smtp_config_4' }); + expect(loadSecrets).toHaveBeenCalledWith({ secretKey }); }); - it('caches the result per path and does not re-invoke the provider', async () => { + it('caches the result per secret key and does not re-invoke the provider', async () => { process.env.ENABLE_BAO = 'true'; process.env.SECRETS_PROVIDER = 'openbao'; const loadSecrets = jest.fn().mockResolvedValue({ RESEND_API_KEY: 'k' }); mockedGetSecretProvider.mockReturnValue({ name: 'OpenBao', loadSecrets }); - await fetchSecrets('secret/data/credebl_resend_api_key'); - await fetchSecrets('secret/data/credebl_resend_api_key'); + await fetchSecrets(CommonConstants.RESEND_API_KEY); + await fetchSecrets(CommonConstants.RESEND_API_KEY); expect(loadSecrets).toHaveBeenCalledTimes(1); }); - it('uses an independent cache per secret path', async () => { + it('uses an independent cache per secret key', async () => { process.env.ENABLE_BAO = 'true'; process.env.SECRETS_PROVIDER = 'openbao'; const loadSecrets = jest.fn().mockResolvedValue({ SOME_KEY: 'v' }); mockedGetSecretProvider.mockReturnValue({ name: 'OpenBao', loadSecrets }); - await fetchSecrets('secret/data/path_a'); - await fetchSecrets('secret/data/path_b'); + await fetchSecrets(CommonConstants.SMTP_CONFIG); + await fetchSecrets(CommonConstants.RESEND_API_KEY); expect(loadSecrets).toHaveBeenCalledTimes(2); - expect(loadSecrets).toHaveBeenCalledWith({ customPath: 'secret/data/path_a' }); - expect(loadSecrets).toHaveBeenCalledWith({ customPath: 'secret/data/path_b' }); + expect(loadSecrets).toHaveBeenCalledWith({ secretKey: CommonConstants.SMTP_CONFIG }); + expect(loadSecrets).toHaveBeenCalledWith({ secretKey: CommonConstants.RESEND_API_KEY }); }); }); diff --git a/libs/common/src/utils/secretLoader.util.ts b/libs/common/src/utils/secretLoader.util.ts index 63cd53352..8818c591a 100644 --- a/libs/common/src/utils/secretLoader.util.ts +++ b/libs/common/src/utils/secretLoader.util.ts @@ -5,14 +5,14 @@ const CACHE_TTL_MS = 10 * 60 * 1000; const secretCaches = new Map>>(); -export async function fetchSecrets(secretPath: string): Promise> { +export async function fetchSecrets(secretKey: string): Promise> { if ('true' !== process.env.ENABLE_BAO?.trim()?.toLowerCase()) { return {}; } - let cache = secretCaches.get(secretPath); + let cache = secretCaches.get(secretKey); if (!cache) { cache = new TtlCache>(CACHE_TTL_MS); - secretCaches.set(secretPath, cache); + secretCaches.set(secretKey, cache); } return cache.get(async () => { const providerType = process.env.SECRETS_PROVIDER?.trim(); @@ -23,6 +23,6 @@ export async function fetchSecrets(secretPath: string): Promise { @@ -41,6 +42,7 @@ describe('OpenBaoProvider', () => { it('authenticates with AppRole and fetches secrets from the configured path', async () => { fetchMock + // eslint-disable-next-line camelcase .mockResolvedValueOnce(jsonResponse({ auth: { client_token: 'client-token' } })) .mockResolvedValueOnce(jsonResponse({ data: { data: { RESEND_API_KEY: 'resend-key' } } })); @@ -51,6 +53,7 @@ describe('OpenBaoProvider', () => { 'http://bao:8200/v1/auth/approle/login', expect.objectContaining({ method: 'POST', + // eslint-disable-next-line camelcase body: JSON.stringify({ role_id: 'role-id', secret_id: 'secret-id' }) }) ); @@ -64,18 +67,31 @@ describe('OpenBaoProvider', () => { ); }); - it('fetches from a custom path when one is supplied', async () => { - fetchMock - .mockResolvedValueOnce(jsonResponse({ auth: { client_token: 'client-token' } })) - .mockResolvedValueOnce(jsonResponse({ data: { data: { KEY: 'value' } } })); - - await provider.loadSecrets({ customPath: 'secret/data/credebl_smtp_config' }); - - expect(fetchMock).toHaveBeenNthCalledWith( - 2, - 'http://bao:8200/v1/secret/data/credebl_smtp_config', - expect.anything() + it.each([ + [CommonConstants.RESEND_API_KEY as string, 'secret/data/credebl_resend_api_key'], + [CommonConstants.SMTP_CONFIG as string, 'secret/data/credebl_smtp_config'], + [CommonConstants.SENDGRID_API_KEY as string, 'secret/data/credebl_sendgrid_api_key'], + [CommonConstants.AWS_KEY as string, 'secret/data/credebl_aws_keys'] + ])( + 'fetches %s from its mapped OpenBao path (%s), independent of BAO_SECRET_PATH', + async (secretKey: string, expectedPath: string) => { + delete process.env.BAO_SECRET_PATH; + fetchMock + // eslint-disable-next-line camelcase + .mockResolvedValueOnce(jsonResponse({ auth: { client_token: 'client-token' } })) + .mockResolvedValueOnce(jsonResponse({ data: { data: { KEY: 'value' } } })); + + await expect(provider.loadSecrets({ secretKey })).resolves.toEqual({ KEY: 'value' }); + + expect(fetchMock).toHaveBeenNthCalledWith(2, `http://bao:8200/v1/${expectedPath}`, expect.anything()); + } + ); + + it('rejects when a secret key has no mapped OpenBao path', async () => { + await expect(provider.loadSecrets({ secretKey: 'unknown_key' })).rejects.toThrow( + 'No OpenBao path mapped for secret key: unknown_key' ); + expect(fetchMock).not.toHaveBeenCalled(); }); it('rejects when the AppRole login response is not ok', async () => { @@ -92,6 +108,7 @@ describe('OpenBaoProvider', () => { it('rejects when the secrets fetch response is not ok', async () => { fetchMock + // eslint-disable-next-line camelcase .mockResolvedValueOnce(jsonResponse({ auth: { client_token: 'client-token' } })) .mockResolvedValueOnce(jsonResponse({}, false, 404)); @@ -100,6 +117,7 @@ describe('OpenBaoProvider', () => { it('rejects when the payload has an unexpected structure', async () => { fetchMock + // eslint-disable-next-line camelcase .mockResolvedValueOnce(jsonResponse({ auth: { client_token: 'client-token' } })) .mockResolvedValueOnce(jsonResponse({ data: {} })); @@ -108,6 +126,7 @@ describe('OpenBaoProvider', () => { it('rejects when the payload data is an array', async () => { fetchMock + // eslint-disable-next-line camelcase .mockResolvedValueOnce(jsonResponse({ auth: { client_token: 'client-token' } })) .mockResolvedValueOnce(jsonResponse({ data: { data: [] } })); @@ -122,6 +141,7 @@ describe('OpenBaoProvider', () => { it('rejects with a timeout error when the secrets fetch is aborted', async () => { fetchMock + // eslint-disable-next-line camelcase .mockResolvedValueOnce(jsonResponse({ auth: { client_token: 'client-token' } })) .mockRejectedValueOnce(new DOMException('The operation was aborted.', 'AbortError')); diff --git a/libs/config/src/secret-storage/openbao.provider.ts b/libs/config/src/secret-storage/openbao.provider.ts index 2bffbe7fd..3042214b9 100644 --- a/libs/config/src/secret-storage/openbao.provider.ts +++ b/libs/config/src/secret-storage/openbao.provider.ts @@ -17,13 +17,20 @@ export class OpenBaoProvider implements SecretProvider { readonly name = 'OpenBao'; private readonly logger = new Logger(OpenBaoProvider.name); + private readonly secretPathMap: Record = { + [CommonConstants.RESEND_API_KEY]: 'secret/data/credebl_resend_api_key', + [CommonConstants.SMTP_CONFIG]: 'secret/data/credebl_smtp_config', + [CommonConstants.SENDGRID_API_KEY]: 'secret/data/credebl_sendgrid_api_key', + [CommonConstants.AWS_KEY]: 'secret/data/credebl_aws_keys' + }; + isEnabled(): boolean { return 'true' === process.env.ENABLE_BAO?.trim()?.toLowerCase(); } - async loadSecrets(options?: { customPath?: string }): Promise> { + async loadSecrets(options?: { secretKey?: string }): Promise> { const baoUrl = process.env.BAO_URL; - const secretPath = options?.customPath || process.env.BAO_SECRET_PATH; + const secretPath = this.resolveSecretPath(options?.secretKey); const roleId = process.env.BAO_ROLE_ID; const secretId = process.env.BAO_SECRET_ID; if (!baoUrl || !secretPath || !roleId || !secretId) { @@ -31,62 +38,40 @@ export class OpenBaoProvider implements SecretProvider { } this.logger.log(`Fetching secrets from ${baoUrl}/v1/${secretPath}`); this.logger.log(`Authenticating with AppRole at ${baoUrl}/v1/auth/approle/login`); - // --- Authentication --- - let authResponse: Response; - try { - authResponse = await fetchWithTimeout( - `${baoUrl}/v1/auth/approle/login`, - { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - // eslint-disable-next-line camelcase - body: JSON.stringify({ role_id: roleId, secret_id: secretId }) - }, - CommonConstants.OPENBAO_REQUEST_TIMEOUT - ); - } catch (error) { - if (error instanceof DOMException && 'AbortError' === error.name) { - throw new Error('OpenBao authentication request timed out.'); - } - throw error; - } - if (!authResponse.ok) { - throw new Error(`Authentication failed: Status ${authResponse.status}`); - } - const authData = await authResponse.json(); + const authData = await this.sendRequest<{ auth?: { client_token?: string } }>( + `${baoUrl}/v1/auth/approle/login`, + { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + // eslint-disable-next-line camelcase + body: JSON.stringify({ role_id: roleId, secret_id: secretId }) + }, + 'Authentication failed', + 'OpenBao authentication request timed out.' + ); + const baoToken = authData.auth?.client_token; if (!baoToken) { throw new Error('Failed to retrieve client token from OpenBao.'); } this.logger.log(`Fetching secrets from ${baoUrl}/v1/${secretPath}`); - // --- Fetch Secrets --- - let response: Response; - try { - response = await fetchWithTimeout( - `${baoUrl}/v1/${secretPath}`, - { - method: 'GET', - headers: { - 'X-Vault-Token': baoToken, - 'Content-Type': 'application/json' - } - }, - CommonConstants.OPENBAO_REQUEST_TIMEOUT - ); - } catch (error) { - if (error instanceof DOMException && 'AbortError' === error.name) { - throw new Error('OpenBao secrets fetch request timed out.'); - } - throw error; - } - if (!response.ok) { - throw new Error(`Fetch failed: Status ${response.status}`); - } - const result = await response.json(); - const secrets = result.data?.data; + const secretsData = await this.sendRequest<{ data?: { data?: Record } }>( + `${baoUrl}/v1/${secretPath}`, + { + method: 'GET', + headers: { + 'X-Vault-Token': baoToken, + 'Content-Type': 'application/json' + } + }, + 'Fetch failed', + 'OpenBao secrets fetch request timed out.' + ); + + const secrets = secretsData.data?.data; this.logger.log('Successfully fetched secrets from OpenBao'); if (!secrets || 'object' !== typeof secrets || Array.isArray(secrets)) { throw new Error('Unexpected secrets payload structure.'); @@ -94,4 +79,36 @@ export class OpenBaoProvider implements SecretProvider { return secrets as Record; } + + private resolveSecretPath(secretKey?: string): string | undefined { + if (!secretKey) { + return process.env.BAO_SECRET_PATH; + } + const secretPath = this.secretPathMap[secretKey]; + if (!secretPath) { + throw new Error(`No OpenBao path mapped for secret key: ${secretKey}`); + } + return secretPath; + } + + private async sendRequest( + url: string, + init: RequestInit, + statusErrorMessage: string, + timeoutErrorMessage: string + ): Promise { + try { + const response = await fetchWithTimeout(url, init, CommonConstants.OPENBAO_REQUEST_TIMEOUT); + if (!response.ok) { + throw new Error(`${statusErrorMessage}: Status ${response.status}`); + } + const body = await response.json(); + return body as T; + } catch (error) { + if (error instanceof DOMException && 'AbortError' === error.name) { + throw new Error(timeoutErrorMessage); + } + throw error; + } + } } diff --git a/libs/config/src/secret-storage/secret-provider.interface.ts b/libs/config/src/secret-storage/secret-provider.interface.ts index f982c868a..bce6bb6eb 100644 --- a/libs/config/src/secret-storage/secret-provider.interface.ts +++ b/libs/config/src/secret-storage/secret-provider.interface.ts @@ -9,5 +9,5 @@ export interface SecretProvider { * as a flat key-value object of strings. * * @returns A promise resolving to Record */ - loadSecrets(options?: Record): Promise>; + loadSecrets(options?: { secretKey?: string }): Promise>; } diff --git a/libs/storage/src/providers/s3-storage.provider.spec.ts b/libs/storage/src/providers/s3-storage.provider.spec.ts index 5ff6c4c6c..c55d55c3f 100644 --- a/libs/storage/src/providers/s3-storage.provider.spec.ts +++ b/libs/storage/src/providers/s3-storage.provider.spec.ts @@ -1,5 +1,6 @@ import { S3StorageService } from './s3-storage.provider'; import { fetchSecrets } from '@credebl/common/utils/secretLoader.util'; +import { CommonConstants } from '@credebl/common/common.constant'; jest.mock('aws-sdk', () => ({ S3: jest.fn() @@ -43,7 +44,7 @@ describe('S3StorageService', () => { await (service as unknown as { getS3Client: ClientGetter }).getS3Client(); - expect(mockedFetchSecrets).toHaveBeenCalledWith('secret/data/credebl_aws_keys'); + expect(mockedFetchSecrets).toHaveBeenCalledWith(CommonConstants.AWS_KEY); expect(MockedS3).toHaveBeenCalledWith({ accessKeyId: 'secret-key', secretAccessKey: 'secret-secret', diff --git a/libs/storage/src/providers/s3-storage.provider.ts b/libs/storage/src/providers/s3-storage.provider.ts index 1fba7d5b0..c4a5ae9df 100644 --- a/libs/storage/src/providers/s3-storage.provider.ts +++ b/libs/storage/src/providers/s3-storage.provider.ts @@ -7,7 +7,7 @@ import { fetchSecrets } from '@credebl/common/utils/secretLoader.util'; @Injectable() export class S3StorageService extends BaseS3StorageService { protected async getS3Client(): Promise { - const secrets = await fetchSecrets(CommonConstants.CREDEBL_AWS_KEY_PATH); + const secrets = await fetchSecrets(CommonConstants.AWS_KEY); return new S3({ accessKeyId: secrets.AWS_ACCESS_KEY ?? process.env.AWS_ACCESS_KEY, secretAccessKey: secrets.AWS_SECRET_KEY ?? process.env.AWS_SECRET_KEY, @@ -16,7 +16,7 @@ export class S3StorageService extends BaseS3StorageService { } protected async getPublicS3Client(): Promise { - const secrets = await fetchSecrets(CommonConstants.CREDEBL_AWS_KEY_PATH); + const secrets = await fetchSecrets(CommonConstants.AWS_KEY); return new S3({ accessKeyId: secrets.AWS_PUBLIC_ACCESS_KEY ?? process.env.AWS_PUBLIC_ACCESS_KEY, secretAccessKey: secrets.AWS_PUBLIC_SECRET_KEY ?? process.env.AWS_PUBLIC_SECRET_KEY, @@ -25,7 +25,7 @@ export class S3StorageService extends BaseS3StorageService { } protected async getStoreObjectS3Client(): Promise { - const secrets = await fetchSecrets(CommonConstants.CREDEBL_AWS_KEY_PATH); + const secrets = await fetchSecrets(CommonConstants.AWS_KEY); return new S3({ accessKeyId: secrets.AWS_S3_STOREOBJECT_ACCESS_KEY ?? process.env.AWS_S3_STOREOBJECT_ACCESS_KEY, secretAccessKey: secrets.AWS_S3_STOREOBJECT_SECRET_KEY ?? process.env.AWS_S3_STOREOBJECT_SECRET_KEY, From 20add1bfa660bc47ea59207873888c8e209eaecb Mon Sep 17 00:00:00 2001 From: Kumari Vaishnavi <145796948+vaishnavijha12@users.noreply.github.com> Date: Wed, 19 Aug 2026 18:30:33 +0530 Subject: [PATCH 48/57] feat: expose multiple Swagger UI endpoints grouped by modules (#1608) * Enhance Swagger setup with API filtering and grouping Added a helper function to filter APIs by tags and created separate Swagger documents for different API groups. Signed-off-by: Kumari Vaishnavi <145796948+vaishnavijha12@users.noreply.github.com> * fix: correct swagger tag names for proper API grouping Signed-off-by: Kumari Vaishnavi <145796948+vaishnavijha12@users.noreply.github.com> * refactor: improve filterByTags typing and fix import placement Signed-off-by: Kumari Vaishnavi <145796948+vaishnavijha12@users.noreply.github.com> * fix: address review feedback on Swagger grouping Signed-off-by: Tipu_Singh --------- Signed-off-by: Kumari Vaishnavi <145796948+vaishnavijha12@users.noreply.github.com> Signed-off-by: Tipu_Singh Co-authored-by: Tipu_Singh --- apps/api-gateway/src/main.ts | 47 +++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/apps/api-gateway/src/main.ts b/apps/api-gateway/src/main.ts index b8b8a9895..0a0d6b3a5 100755 --- a/apps/api-gateway/src/main.ts +++ b/apps/api-gateway/src/main.ts @@ -2,7 +2,7 @@ import { otelSDK } from './tracer'; import * as dotenv from 'dotenv'; import * as express from 'express'; -import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; +import { DocumentBuilder, OpenAPIObject, SwaggerModule } from '@nestjs/swagger'; import { Logger, VERSION_NEUTRAL, VersioningType } from '@nestjs/common'; import * as cookieParser from 'cookie-parser'; import { AppModule } from './app.module'; @@ -99,7 +99,52 @@ async function bootstrap(): Promise { Logger.warn('Skipping EcosystemSwaggerFilter due to error', err as Error); } + // Filter operations by tag into a standalone Swagger document + function filterByTags(doc: OpenAPIObject, tagNames: string[]): OpenAPIObject { + const filteredPaths: OpenAPIObject['paths'] = {}; + + for (const [path, methods] of Object.entries(doc.paths)) { + for (const [method, operation] of Object.entries(methods)) { + if (operation.tags && operation.tags.some((tag: string) => tagNames.includes(tag))) { + if (!filteredPaths[path]) { + filteredPaths[path] = {}; + } + filteredPaths[path][method] = operation; + } + } + } + + return { + ...doc, + paths: filteredPaths + }; + } + + // Credential APIs are grouped under DIDComm rather than as a standalone section + const didcommDoc = filterByTags(document, [ + 'connections', + 'verifications', + 'credentials', + 'credential-definitions', + 'revocation-registry', + 'schemas' + ]); + + const oid4vcDoc = filterByTags(document, ['OID4VC', 'OID4VP']); + + const authDoc = filterByTags(document, ['auth']); + + // Platform and Webhook APIs are excluded from the Utils section + const utilsDoc = filterByTags(document, ['utilities', 'ledgers', 'geolocation', 'notification']); + + // Default full Swagger SwaggerModule.setup('api', app, document); + + // Grouped Swagger UIs + SwaggerModule.setup('api/didcomm', app, didcommDoc); + SwaggerModule.setup('api/oid4vc', app, oid4vcDoc); + SwaggerModule.setup('api/auth', app, authDoc); + SwaggerModule.setup('api/utils', app, utilsDoc); const httpAdapter: HttpAdapterHost = app.get(HttpAdapterHost) as HttpAdapterHost; app.useGlobalFilters(new AllExceptionsFilter(httpAdapter)); const { ENABLE_CORS_IP_LIST } = process.env || {}; From b3137c11c153089b1a95edac7485469a025569b7 Mon Sep 17 00:00:00 2001 From: Mark Date: Fri, 7 Aug 2026 12:30:37 +0800 Subject: [PATCH 49/57] fix(security): harden agent provisioning execution Signed-off-by: Mark --- .../src/agent-provisioning.service.spec.ts | 88 +++++++++++++ .../src/agent-provisioning.service.ts | 117 ++++++++++++------ 2 files changed, 166 insertions(+), 39 deletions(-) create mode 100644 apps/agent-provisioning/src/agent-provisioning.service.spec.ts diff --git a/apps/agent-provisioning/src/agent-provisioning.service.spec.ts b/apps/agent-provisioning/src/agent-provisioning.service.spec.ts new file mode 100644 index 000000000..dfbde0601 --- /dev/null +++ b/apps/agent-provisioning/src/agent-provisioning.service.spec.ts @@ -0,0 +1,88 @@ +const mockExecFile = jest.fn(); +const mockReadFile = jest.fn(); +const mockAccess = jest.fn(); + +jest.mock('child_process', () => ({ execFile: mockExecFile })); +jest.mock('util', () => ({ promisify: jest.fn(() => mockExecFile) })); +jest.mock('fs', () => ({ promises: { access: mockAccess, readFile: mockReadFile } })); + +import { AgentType } from '@credebl/enum/enum'; +import { AgentProvisioningService } from './agent-provisioning.service'; + +const payload = { + orgId: 'org-123', + externalIp: '127.0.0.1', + walletName: 'wallet', + walletPassword: 'wallet-secret', + seed: 'seed', + webhookEndpoint: 'https://example.test/webhook', + walletStorageHost: 'postgres', + walletStoragePort: '5432', + walletStorageUser: 'user', + walletStoragePassword: 'storage-secret', + internalIp: '127.0.0.1', + containerName: 'issuer-agent', + agentType: AgentType.AFJ, + orgName: 'Organization', + indyLedger: '[]', + protocol: 'http', + credoImage: 'credo:latest', + tenant: false, + inboundEndpoint: '127.0.0.1' +}; + +describe('AgentProvisioningService', () => { + const logger = { log: jest.fn(), error: jest.fn() }; + const service = new AgentProvisioningService(logger as never); + const savedEnv = { ...process.env }; + + beforeEach(() => { + jest.clearAllMocks(); + process.env = { + ...savedEnv, + AFJ_AGENT_SPIN_UP: '/apps/agent-provisioning/AFJ/scripts/start_agent.sh', + AFJ_AGENT_ENDPOINT_PATH: '/apps/agent-provisioning/AFJ/endpoints/', + SCHEMA_FILE_SERVER_URL: 'https://schema.example', + AGENT_API_KEY: 'agent-key', + AWS_ACCOUNT_ID: 'account', + S3_BUCKET_ARN: 'bucket', + CLUSTER_NAME: 'cluster', + TASKDEFINITION_FAMILY: 'family', + ADMIN_TG_ARN: 'admin-tg', + INBOUND_TG_ARN: 'inbound-tg', + FILESYSTEMID: 'filesystem', + ECS_SUBNET_ID: 'subnet', + ECS_SECURITY_GROUP_ID: 'security-group' + }; + }); + + afterAll(() => { + process.env = savedEnv; + }); + + it('executes the provisioning script without a shell and returns the generated endpoint', async () => { + mockExecFile.mockResolvedValue({ stdout: '', stderr: '' }); + mockReadFile.mockResolvedValue('{"CONTROLLER_ENDPOINT":"https://agent.example"}'); + + await expect(service.walletProvision(payload)).resolves.toEqual({ agentEndPoint: 'https://agent.example' }); + expect(mockExecFile).toHaveBeenCalledWith( + expect.stringContaining('/apps/agent-provisioning/AFJ/scripts/start_agent.sh'), + expect.arrayContaining([payload.orgId, payload.containerName, payload.walletPassword]), + expect.objectContaining({ timeout: 120000 }) + ); + }); + + it('rejects identifiers that could escape the generated endpoint path before executing a script', async () => { + await expect(service.walletProvision({ ...payload, containerName: '../../tmp/owned' })).rejects.toThrow( + 'containerName contains unsafe characters' + ); + expect(mockExecFile).not.toHaveBeenCalled(); + }); + + it('propagates a provisioning script failure instead of attempting to read an endpoint file', async () => { + mockExecFile.mockRejectedValue(new Error('script failed')); + + await expect(service.walletProvision(payload)).rejects.toBeDefined(); + expect(mockReadFile).not.toHaveBeenCalled(); + }); +}); diff --git a/apps/agent-provisioning/src/agent-provisioning.service.ts b/apps/agent-provisioning/src/agent-provisioning.service.ts index 3fe825e47..0756be335 100644 --- a/apps/agent-provisioning/src/agent-provisioning.service.ts +++ b/apps/agent-provisioning/src/agent-provisioning.service.ts @@ -1,12 +1,17 @@ import * as dotenv from 'dotenv'; -import * as fs from 'fs'; +import { promises as fs } from 'fs'; import { Injectable, Logger, NotFoundException } from '@nestjs/common'; import { AgentType } from '@credebl/enum/enum'; import { IWalletProvision } from './interface/agent-provisioning.interfaces'; import { RpcException } from '@nestjs/microservices'; -import { exec } from 'child_process'; +import { execFile } from 'child_process'; +import { promisify } from 'util'; + +const execFileAsync = promisify(execFile); +const SAFE_FILE_IDENTIFIER = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/; +const AGENT_PROVISION_TIMEOUT_MS = 120_000; dotenv.config(); @@ -41,49 +46,77 @@ export class AgentProvisioningService { inboundEndpoint } = payload; if (agentType === AgentType.AFJ) { - // The wallet provision command is used to invoke a shell script - const walletProvision = `${process.cwd() + process.env.AFJ_AGENT_SPIN_UP} ${orgId} "${externalIp}" "${walletName}" "${walletPassword}" ${seed} ${webhookEndpoint} ${walletStorageHost} ${walletStoragePort} ${walletStorageUser} ${walletStoragePassword} ${containerName} ${protocol} ${tenant} ${credoImage} "${indyLedger}" ${inboundEndpoint} ${process.env.SCHEMA_FILE_SERVER_URL} ${process.env.AGENT_API_KEY} ${process.env.AWS_ACCOUNT_ID} ${process.env.S3_BUCKET_ARN} ${process.env.CLUSTER_NAME} ${process.env.TASKDEFINITION_FAMILY} ${process.env.ADMIN_TG_ARN} ${process.env.INBOUND_TG_ARN} ${process.env.FILESYSTEMID} ${process.env.ECS_SUBNET_ID} ${process.env.ECS_SECURITY_GROUP_ID}`; - const spinUpResponse: object = new Promise(async (resolve) => { - await exec(walletProvision, async (err, stdout, stderr) => { - this.logger.log(`shell script output: ${stdout}`); - if (stderr) { - this.logger.log(`shell script error: ${stderr}`); - } + this.assertSafeFileIdentifier(orgId, 'orgId'); + this.assertSafeFileIdentifier(containerName, 'containerName'); - const agentEndpointPath = `${process.cwd()}${process.env.AFJ_AGENT_ENDPOINT_PATH}${orgId}_${containerName}.json`; + const spinUpScript = process.env.AFJ_AGENT_SPIN_UP; + const endpointDirectory = process.env.AFJ_AGENT_ENDPOINT_PATH; + if (!spinUpScript || !endpointDirectory) { + throw new Error('AFJ_AGENT_SPIN_UP and AFJ_AGENT_ENDPOINT_PATH must be configured'); + } - const agentEndPointExists = await this.checkFileExistence(agentEndpointPath); + const requiredEnvironment = [ + 'SCHEMA_FILE_SERVER_URL', + 'AGENT_API_KEY', + 'AWS_ACCOUNT_ID', + 'S3_BUCKET_ARN', + 'CLUSTER_NAME', + 'TASKDEFINITION_FAMILY', + 'ADMIN_TG_ARN', + 'INBOUND_TG_ARN', + 'FILESYSTEMID', + 'ECS_SUBNET_ID', + 'ECS_SECURITY_GROUP_ID' + ]; + const missingEnvironment = requiredEnvironment.filter((name) => !process.env[name]); + if (missingEnvironment.length) { + throw new Error(`Missing provisioning configuration: ${missingEnvironment.join(', ')}`); + } - let agentEndPoint; + await execFileAsync( + `${process.cwd()}${spinUpScript}`, + [ + orgId, + externalIp, + walletName, + walletPassword, + seed, + webhookEndpoint, + walletStorageHost, + walletStoragePort, + walletStorageUser, + walletStoragePassword, + containerName, + protocol, + String(tenant), + credoImage, + indyLedger, + inboundEndpoint, + ...requiredEnvironment.map((name) => process.env[name] as string) + ], + { timeout: AGENT_PROVISION_TIMEOUT_MS, maxBuffer: 1024 * 1024 } + ); - if (agentEndPointExists) { - this.logger.log('Agent endpoint file exists'); - agentEndPoint = await fs.readFileSync(agentEndpointPath, 'utf8'); - // Proceed with accessing the files if needed - } else { - this.logger.log('Agent endpoint file does not exist'); - throw new NotFoundException(`Agent endpoint file does not exist: ${agentEndpointPath}`); - } + const agentEndpointPath = `${process.cwd()}${endpointDirectory}${orgId}_${containerName}.json`; + const agentEndPointExists = await this.checkFileExistence(agentEndpointPath); + if (!agentEndPointExists) { + throw new NotFoundException(`Agent endpoint file does not exist: ${agentEndpointPath}`); + } - let parsedEndpoint; - try { - parsedEndpoint = JSON.parse(agentEndPoint); - } catch (parseError) { - this.logger.error(`Failed to parse agent endpoint file: ${parseError.message}`); - throw new Error(`Invalid JSON in agent endpoint file: ${agentEndpointPath}`); - } + const agentEndPoint = await fs.readFile(agentEndpointPath, 'utf8'); + let parsedEndpoint: { CONTROLLER_ENDPOINT?: string }; + try { + parsedEndpoint = JSON.parse(agentEndPoint); + } catch (parseError) { + this.logger.error(`Failed to parse agent endpoint file: ${parseError.message}`); + throw new Error(`Invalid JSON in agent endpoint file: ${agentEndpointPath}`); + } - if (!parsedEndpoint.CONTROLLER_ENDPOINT) { - this.logger.error('CONTROLLER_ENDPOINT key missing in agent endpoint file'); - throw new Error(`Missing CONTROLLER_ENDPOINT in: ${agentEndpointPath}`); - } + if (!parsedEndpoint.CONTROLLER_ENDPOINT) { + throw new Error(`Missing CONTROLLER_ENDPOINT in: ${agentEndpointPath}`); + } - resolve({ - agentEndPoint: parsedEndpoint.CONTROLLER_ENDPOINT - }); - }); - }); - return spinUpResponse; + return { agentEndPoint: parsedEndpoint.CONTROLLER_ENDPOINT }; } else if (agentType === AgentType.ACAPY) { // TODO: ACA-PY Agent Spin-Up } @@ -95,10 +128,16 @@ export class AgentProvisioningService { async checkFileExistence(filePath: string): Promise { try { - await fs.accessSync(filePath); + await fs.access(filePath); return true; // File exists } catch (error) { return false; // File does not exist } } + + private assertSafeFileIdentifier(value: string, field: string): void { + if (!SAFE_FILE_IDENTIFIER.test(value)) { + throw new Error(`${field} contains unsafe characters`); + } + } } From e7ca8442b6b44e771619d65667cafaf05d1eea75 Mon Sep 17 00:00:00 2001 From: Mark Date: Wed, 12 Aug 2026 10:24:49 +0800 Subject: [PATCH 50/57] fix(security): sanitize agent provisioning failures Signed-off-by: Mark --- .../src/agent-provisioning.service.spec.ts | 25 +++++++++++++++++-- .../src/agent-provisioning.service.ts | 12 +++++---- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/apps/agent-provisioning/src/agent-provisioning.service.spec.ts b/apps/agent-provisioning/src/agent-provisioning.service.spec.ts index dfbde0601..8c410e2d0 100644 --- a/apps/agent-provisioning/src/agent-provisioning.service.spec.ts +++ b/apps/agent-provisioning/src/agent-provisioning.service.spec.ts @@ -79,10 +79,31 @@ describe('AgentProvisioningService', () => { expect(mockExecFile).not.toHaveBeenCalled(); }); + it('rejects non-string identifiers before executing a script', async () => { + await expect(service.walletProvision({ ...payload, orgId: 123 as unknown as string })).rejects.toThrow( + 'orgId contains unsafe characters' + ); + expect(mockExecFile).not.toHaveBeenCalled(); + }); + + it.each([{}, 1, [], '', ' '])('rejects invalid CONTROLLER_ENDPOINT values', async (endpoint) => { + mockExecFile.mockResolvedValue({ stdout: '', stderr: '' }); + mockReadFile.mockResolvedValue(JSON.stringify({ CONTROLLER_ENDPOINT: endpoint })); + + await expect(service.walletProvision(payload)).rejects.toThrow('Missing CONTROLLER_ENDPOINT'); + }); + it('propagates a provisioning script failure instead of attempting to read an endpoint file', async () => { - mockExecFile.mockRejectedValue(new Error('script failed')); + const failure = Object.assign(new Error('script failed'), { + stdout: 'stdout-secret', + stderr: 'stderr-secret' + }); + mockExecFile.mockRejectedValue(failure); - await expect(service.walletProvision(payload)).rejects.toBeDefined(); + await expect(service.walletProvision(payload)).rejects.toThrow('Agent provisioning script failed'); expect(mockReadFile).not.toHaveBeenCalled(); + expect(logger.error).toHaveBeenCalled(); + expect(JSON.stringify(logger.error.mock.calls)).not.toContain('stdout-secret'); + expect(JSON.stringify(logger.error.mock.calls)).not.toContain('stderr-secret'); }); }); diff --git a/apps/agent-provisioning/src/agent-provisioning.service.ts b/apps/agent-provisioning/src/agent-provisioning.service.ts index 0756be335..13421cbcf 100644 --- a/apps/agent-provisioning/src/agent-provisioning.service.ts +++ b/apps/agent-provisioning/src/agent-provisioning.service.ts @@ -95,7 +95,9 @@ export class AgentProvisioningService { ...requiredEnvironment.map((name) => process.env[name] as string) ], { timeout: AGENT_PROVISION_TIMEOUT_MS, maxBuffer: 1024 * 1024 } - ); + ).catch(() => { + throw new Error('Agent provisioning script failed'); + }); const agentEndpointPath = `${process.cwd()}${endpointDirectory}${orgId}_${containerName}.json`; const agentEndPointExists = await this.checkFileExistence(agentEndpointPath); @@ -104,7 +106,7 @@ export class AgentProvisioningService { } const agentEndPoint = await fs.readFile(agentEndpointPath, 'utf8'); - let parsedEndpoint: { CONTROLLER_ENDPOINT?: string }; + let parsedEndpoint: { CONTROLLER_ENDPOINT?: unknown }; try { parsedEndpoint = JSON.parse(agentEndPoint); } catch (parseError) { @@ -112,7 +114,7 @@ export class AgentProvisioningService { throw new Error(`Invalid JSON in agent endpoint file: ${agentEndpointPath}`); } - if (!parsedEndpoint.CONTROLLER_ENDPOINT) { + if ('string' !== typeof parsedEndpoint.CONTROLLER_ENDPOINT || !parsedEndpoint.CONTROLLER_ENDPOINT.trim()) { throw new Error(`Missing CONTROLLER_ENDPOINT in: ${agentEndpointPath}`); } @@ -135,8 +137,8 @@ export class AgentProvisioningService { } } - private assertSafeFileIdentifier(value: string, field: string): void { - if (!SAFE_FILE_IDENTIFIER.test(value)) { + private assertSafeFileIdentifier(value: unknown, field: string): void { + if ('string' !== typeof value || !SAFE_FILE_IDENTIFIER.test(value)) { throw new Error(`${field} contains unsafe characters`); } } From 06018edac7b2d13df044c657a1eed167d5a4da1d Mon Sep 17 00:00:00 2001 From: Mark Date: Fri, 21 Aug 2026 22:46:29 +0800 Subject: [PATCH 51/57] fix(security): preserve provisioning compatibility Signed-off-by: Mark --- .../src/agent-provisioning.service.spec.ts | 33 ++++++++++-- .../src/agent-provisioning.service.ts | 54 ++++++++++++++++--- 2 files changed, 74 insertions(+), 13 deletions(-) diff --git a/apps/agent-provisioning/src/agent-provisioning.service.spec.ts b/apps/agent-provisioning/src/agent-provisioning.service.spec.ts index 8c410e2d0..7694a83cb 100644 --- a/apps/agent-provisioning/src/agent-provisioning.service.spec.ts +++ b/apps/agent-provisioning/src/agent-provisioning.service.spec.ts @@ -68,15 +68,23 @@ describe('AgentProvisioningService', () => { expect(mockExecFile).toHaveBeenCalledWith( expect.stringContaining('/apps/agent-provisioning/AFJ/scripts/start_agent.sh'), expect.arrayContaining([payload.orgId, payload.containerName, payload.walletPassword]), - expect.objectContaining({ timeout: 120000 }) + expect.objectContaining({ timeout: 300000 }) ); }); - it('rejects identifiers that could escape the generated endpoint path before executing a script', async () => { - await expect(service.walletProvision({ ...payload, containerName: '../../tmp/owned' })).rejects.toThrow( - 'containerName contains unsafe characters' + it('normalizes organization-derived container names before executing the script and reading its endpoint', async () => { + mockExecFile.mockResolvedValue({ stdout: '', stderr: '' }); + mockReadFile.mockResolvedValue('{"CONTROLLER_ENDPOINT":"https://agent.example"}'); + + await expect(service.walletProvision({ ...payload, containerName: 'Crédit Agricole, Inc.' })).resolves.toEqual({ + agentEndPoint: 'https://agent.example' + }); + expect(mockExecFile).toHaveBeenCalledWith( + expect.any(String), + expect.arrayContaining(['Credit_Agricole_Inc']), + expect.any(Object) ); - expect(mockExecFile).not.toHaveBeenCalled(); + expect(mockReadFile).toHaveBeenCalledWith(expect.stringContaining('org-123_Credit_Agricole_Inc.json'), 'utf8'); }); it('rejects non-string identifiers before executing a script', async () => { @@ -86,6 +94,19 @@ describe('AgentProvisioningService', () => { expect(mockExecFile).not.toHaveBeenCalled(); }); + it('uses a configured provisioning timeout', async () => { + process.env.AFJ_AGENT_PROVISION_TIMEOUT_MS = '600000'; + mockExecFile.mockResolvedValue({ stdout: '', stderr: '' }); + mockReadFile.mockResolvedValue('{"CONTROLLER_ENDPOINT":"https://agent.example"}'); + + await service.walletProvision(payload); + expect(mockExecFile).toHaveBeenCalledWith( + expect.any(String), + expect.any(Array), + expect.objectContaining({ timeout: 600000 }) + ); + }); + it.each([{}, 1, [], '', ' '])('rejects invalid CONTROLLER_ENDPOINT values', async (endpoint) => { mockExecFile.mockResolvedValue({ stdout: '', stderr: '' }); mockReadFile.mockResolvedValue(JSON.stringify({ CONTROLLER_ENDPOINT: endpoint })); @@ -95,6 +116,7 @@ describe('AgentProvisioningService', () => { it('propagates a provisioning script failure instead of attempting to read an endpoint file', async () => { const failure = Object.assign(new Error('script failed'), { + code: 17, stdout: 'stdout-secret', stderr: 'stderr-secret' }); @@ -105,5 +127,6 @@ describe('AgentProvisioningService', () => { expect(logger.error).toHaveBeenCalled(); expect(JSON.stringify(logger.error.mock.calls)).not.toContain('stdout-secret'); expect(JSON.stringify(logger.error.mock.calls)).not.toContain('stderr-secret'); + expect(JSON.stringify(logger.error.mock.calls)).toContain('exit code 17'); }); }); diff --git a/apps/agent-provisioning/src/agent-provisioning.service.ts b/apps/agent-provisioning/src/agent-provisioning.service.ts index 13421cbcf..67640acda 100644 --- a/apps/agent-provisioning/src/agent-provisioning.service.ts +++ b/apps/agent-provisioning/src/agent-provisioning.service.ts @@ -11,7 +11,7 @@ import { promisify } from 'util'; const execFileAsync = promisify(execFile); const SAFE_FILE_IDENTIFIER = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/; -const AGENT_PROVISION_TIMEOUT_MS = 120_000; +const DEFAULT_AGENT_PROVISION_TIMEOUT_MS = 300_000; dotenv.config(); @@ -47,7 +47,7 @@ export class AgentProvisioningService { } = payload; if (agentType === AgentType.AFJ) { this.assertSafeFileIdentifier(orgId, 'orgId'); - this.assertSafeFileIdentifier(containerName, 'containerName'); + const safeContainerName = this.normalizeContainerName(containerName); const spinUpScript = process.env.AFJ_AGENT_SPIN_UP; const endpointDirectory = process.env.AFJ_AGENT_ENDPOINT_PATH; @@ -73,6 +73,7 @@ export class AgentProvisioningService { throw new Error(`Missing provisioning configuration: ${missingEnvironment.join(', ')}`); } + const provisionTimeoutMs = this.getProvisionTimeoutMs(); await execFileAsync( `${process.cwd()}${spinUpScript}`, [ @@ -86,7 +87,7 @@ export class AgentProvisioningService { walletStoragePort, walletStorageUser, walletStoragePassword, - containerName, + safeContainerName, protocol, String(tenant), credoImage, @@ -94,12 +95,18 @@ export class AgentProvisioningService { inboundEndpoint, ...requiredEnvironment.map((name) => process.env[name] as string) ], - { timeout: AGENT_PROVISION_TIMEOUT_MS, maxBuffer: 1024 * 1024 } - ).catch(() => { - throw new Error('Agent provisioning script failed'); + { timeout: provisionTimeoutMs, maxBuffer: 1024 * 1024 } + ).catch((error) => { + const failureDetail = + 'number' === typeof error?.code + ? ` (exit code ${error.code})` + : 'string' === typeof error?.signal + ? ` (signal ${error.signal})` + : ''; + throw new Error(`Agent provisioning script failed${failureDetail}`); }); - const agentEndpointPath = `${process.cwd()}${endpointDirectory}${orgId}_${containerName}.json`; + const agentEndpointPath = `${process.cwd()}${endpointDirectory}${orgId}_${safeContainerName}.json`; const agentEndPointExists = await this.checkFileExistence(agentEndpointPath); if (!agentEndPointExists) { throw new NotFoundException(`Agent endpoint file does not exist: ${agentEndpointPath}`); @@ -123,11 +130,42 @@ export class AgentProvisioningService { // TODO: ACA-PY Agent Spin-Up } } catch (error) { - this.logger.error(`[walletProvision] - error in wallet provision: ${JSON.stringify(error)}`); + this.logger.error( + `[walletProvision] - error in wallet provision: ${error instanceof Error ? error.message : 'Unknown error'}` + ); throw new RpcException(error); } } + private getProvisionTimeoutMs(): number { + const configuredTimeout = process.env.AFJ_AGENT_PROVISION_TIMEOUT_MS; + if (!configuredTimeout) { + return DEFAULT_AGENT_PROVISION_TIMEOUT_MS; + } + + const timeout = Number(configuredTimeout); + if (!Number.isInteger(timeout) || 0 >= timeout) { + throw new Error('AFJ_AGENT_PROVISION_TIMEOUT_MS must be a positive integer'); + } + + return timeout; + } + + private normalizeContainerName(value: unknown): string { + if ('string' !== typeof value) { + throw new Error('containerName contains unsafe characters'); + } + + const normalized = value + .normalize('NFKD') + .replace(/[\u0300-\u036f]/g, '') + .replace(/[^A-Za-z0-9_-]+/g, '_') + .replace(/^_+|_+$/g, '') + .slice(0, 128); + + return normalized || 'agent'; + } + async checkFileExistence(filePath: string): Promise { try { await fs.access(filePath); From 1def944ea6c87d13f32ddcaf3f939644cae67de6 Mon Sep 17 00:00:00 2001 From: Mark Date: Fri, 21 Aug 2026 22:58:12 +0800 Subject: [PATCH 52/57] fix(security): validate agent endpoint documents Signed-off-by: Mark --- .../src/agent-provisioning.service.spec.ts | 7 +++++++ .../src/agent-provisioning.service.ts | 10 +++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/apps/agent-provisioning/src/agent-provisioning.service.spec.ts b/apps/agent-provisioning/src/agent-provisioning.service.spec.ts index 7694a83cb..0073fc618 100644 --- a/apps/agent-provisioning/src/agent-provisioning.service.spec.ts +++ b/apps/agent-provisioning/src/agent-provisioning.service.spec.ts @@ -114,6 +114,13 @@ describe('AgentProvisioningService', () => { await expect(service.walletProvision(payload)).rejects.toThrow('Missing CONTROLLER_ENDPOINT'); }); + it('rejects a non-object endpoint document', async () => { + mockExecFile.mockResolvedValue({ stdout: '', stderr: '' }); + mockReadFile.mockResolvedValue('null'); + + await expect(service.walletProvision(payload)).rejects.toThrow('Missing CONTROLLER_ENDPOINT'); + }); + it('propagates a provisioning script failure instead of attempting to read an endpoint file', async () => { const failure = Object.assign(new Error('script failed'), { code: 17, diff --git a/apps/agent-provisioning/src/agent-provisioning.service.ts b/apps/agent-provisioning/src/agent-provisioning.service.ts index 67640acda..2e674aa1a 100644 --- a/apps/agent-provisioning/src/agent-provisioning.service.ts +++ b/apps/agent-provisioning/src/agent-provisioning.service.ts @@ -113,7 +113,7 @@ export class AgentProvisioningService { } const agentEndPoint = await fs.readFile(agentEndpointPath, 'utf8'); - let parsedEndpoint: { CONTROLLER_ENDPOINT?: unknown }; + let parsedEndpoint: unknown; try { parsedEndpoint = JSON.parse(agentEndPoint); } catch (parseError) { @@ -121,11 +121,15 @@ export class AgentProvisioningService { throw new Error(`Invalid JSON in agent endpoint file: ${agentEndpointPath}`); } - if ('string' !== typeof parsedEndpoint.CONTROLLER_ENDPOINT || !parsedEndpoint.CONTROLLER_ENDPOINT.trim()) { + const controllerEndpoint = + null !== parsedEndpoint && 'object' === typeof parsedEndpoint && !Array.isArray(parsedEndpoint) + ? (parsedEndpoint as Record).CONTROLLER_ENDPOINT + : undefined; + if ('string' !== typeof controllerEndpoint || !controllerEndpoint.trim()) { throw new Error(`Missing CONTROLLER_ENDPOINT in: ${agentEndpointPath}`); } - return { agentEndPoint: parsedEndpoint.CONTROLLER_ENDPOINT }; + return { agentEndPoint: controllerEndpoint }; } else if (agentType === AgentType.ACAPY) { // TODO: ACA-PY Agent Spin-Up } From dd465c3b1dfc5f5d88de229eedc7e3bd6fd8b867 Mon Sep 17 00:00:00 2001 From: Ajay Jadhav Date: Wed, 26 Aug 2026 12:41:46 +0530 Subject: [PATCH 53/57] fix(security): address SonarQube issues in agent provisioning - Prefer node:util and node:child_process imports over bare specifiers - Reduce walletProvision cognitive complexity by extracting helper methods - Replace nested ternary with if/else in formatScriptFailure - Use TypeError for type-check failures in normalizeContainerName - Simplify regex using Unicode property escape (\p{M}) instead of backtracking-prone range [\u0300-\u036f] Signed-off-by: Ajay Jadhav --- .../src/agent-provisioning.service.spec.ts | 4 +- .../src/agent-provisioning.service.ts | 248 ++++++++++-------- 2 files changed, 140 insertions(+), 112 deletions(-) diff --git a/apps/agent-provisioning/src/agent-provisioning.service.spec.ts b/apps/agent-provisioning/src/agent-provisioning.service.spec.ts index 0073fc618..31b3b542a 100644 --- a/apps/agent-provisioning/src/agent-provisioning.service.spec.ts +++ b/apps/agent-provisioning/src/agent-provisioning.service.spec.ts @@ -2,8 +2,8 @@ const mockExecFile = jest.fn(); const mockReadFile = jest.fn(); const mockAccess = jest.fn(); -jest.mock('child_process', () => ({ execFile: mockExecFile })); -jest.mock('util', () => ({ promisify: jest.fn(() => mockExecFile) })); +jest.mock('node:child_process', () => ({ execFile: mockExecFile })); +jest.mock('node:util', () => ({ promisify: jest.fn(() => mockExecFile) })); jest.mock('fs', () => ({ promises: { access: mockAccess, readFile: mockReadFile } })); import { AgentType } from '@credebl/enum/enum'; diff --git a/apps/agent-provisioning/src/agent-provisioning.service.ts b/apps/agent-provisioning/src/agent-provisioning.service.ts index 2e674aa1a..4f944dd50 100644 --- a/apps/agent-provisioning/src/agent-provisioning.service.ts +++ b/apps/agent-provisioning/src/agent-provisioning.service.ts @@ -6,8 +6,8 @@ import { Injectable, Logger, NotFoundException } from '@nestjs/common'; import { AgentType } from '@credebl/enum/enum'; import { IWalletProvision } from './interface/agent-provisioning.interfaces'; import { RpcException } from '@nestjs/microservices'; -import { execFile } from 'child_process'; -import { promisify } from 'util'; +import { execFile } from 'node:child_process'; +import { promisify } from 'node:util'; const execFileAsync = promisify(execFile); const SAFE_FILE_IDENTIFIER = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/; @@ -26,113 +26,15 @@ export class AgentProvisioningService { */ async walletProvision(payload: IWalletProvision): Promise { try { - const { - containerName, - externalIp, - orgId, - seed, - walletName, - walletPassword, - walletStorageHost, - walletStoragePassword, - walletStoragePort, - walletStorageUser, - webhookEndpoint, - agentType, - protocol, - credoImage, - tenant, - indyLedger, - inboundEndpoint - } = payload; - if (agentType === AgentType.AFJ) { - this.assertSafeFileIdentifier(orgId, 'orgId'); - const safeContainerName = this.normalizeContainerName(containerName); - - const spinUpScript = process.env.AFJ_AGENT_SPIN_UP; - const endpointDirectory = process.env.AFJ_AGENT_ENDPOINT_PATH; - if (!spinUpScript || !endpointDirectory) { - throw new Error('AFJ_AGENT_SPIN_UP and AFJ_AGENT_ENDPOINT_PATH must be configured'); - } - - const requiredEnvironment = [ - 'SCHEMA_FILE_SERVER_URL', - 'AGENT_API_KEY', - 'AWS_ACCOUNT_ID', - 'S3_BUCKET_ARN', - 'CLUSTER_NAME', - 'TASKDEFINITION_FAMILY', - 'ADMIN_TG_ARN', - 'INBOUND_TG_ARN', - 'FILESYSTEMID', - 'ECS_SUBNET_ID', - 'ECS_SECURITY_GROUP_ID' - ]; - const missingEnvironment = requiredEnvironment.filter((name) => !process.env[name]); - if (missingEnvironment.length) { - throw new Error(`Missing provisioning configuration: ${missingEnvironment.join(', ')}`); - } - - const provisionTimeoutMs = this.getProvisionTimeoutMs(); - await execFileAsync( - `${process.cwd()}${spinUpScript}`, - [ - orgId, - externalIp, - walletName, - walletPassword, - seed, - webhookEndpoint, - walletStorageHost, - walletStoragePort, - walletStorageUser, - walletStoragePassword, - safeContainerName, - protocol, - String(tenant), - credoImage, - indyLedger, - inboundEndpoint, - ...requiredEnvironment.map((name) => process.env[name] as string) - ], - { timeout: provisionTimeoutMs, maxBuffer: 1024 * 1024 } - ).catch((error) => { - const failureDetail = - 'number' === typeof error?.code - ? ` (exit code ${error.code})` - : 'string' === typeof error?.signal - ? ` (signal ${error.signal})` - : ''; - throw new Error(`Agent provisioning script failed${failureDetail}`); - }); - - const agentEndpointPath = `${process.cwd()}${endpointDirectory}${orgId}_${safeContainerName}.json`; - const agentEndPointExists = await this.checkFileExistence(agentEndpointPath); - if (!agentEndPointExists) { - throw new NotFoundException(`Agent endpoint file does not exist: ${agentEndpointPath}`); - } - - const agentEndPoint = await fs.readFile(agentEndpointPath, 'utf8'); - let parsedEndpoint: unknown; - try { - parsedEndpoint = JSON.parse(agentEndPoint); - } catch (parseError) { - this.logger.error(`Failed to parse agent endpoint file: ${parseError.message}`); - throw new Error(`Invalid JSON in agent endpoint file: ${agentEndpointPath}`); - } - - const controllerEndpoint = - null !== parsedEndpoint && 'object' === typeof parsedEndpoint && !Array.isArray(parsedEndpoint) - ? (parsedEndpoint as Record).CONTROLLER_ENDPOINT - : undefined; - if ('string' !== typeof controllerEndpoint || !controllerEndpoint.trim()) { - throw new Error(`Missing CONTROLLER_ENDPOINT in: ${agentEndpointPath}`); - } - - return { agentEndPoint: controllerEndpoint }; - } else if (agentType === AgentType.ACAPY) { + if (payload.agentType === AgentType.AFJ) { + return await this.provisionAfjAgent(payload); + } + + if (payload.agentType === AgentType.ACAPY) { // TODO: ACA-PY Agent Spin-Up } + + throw new RpcException(`Unsupported agent type: ${payload.agentType}`); } catch (error) { this.logger.error( `[walletProvision] - error in wallet provision: ${error instanceof Error ? error.message : 'Unknown error'}` @@ -141,6 +43,131 @@ export class AgentProvisioningService { } } + private async provisionAfjAgent(payload: IWalletProvision): Promise { + this.assertSafeFileIdentifier(payload.orgId, 'orgId'); + const safeContainerName = this.normalizeContainerName(payload.containerName); + + const { spinUpScript, endpointDirectory, requiredEnvironment } = this.validateAfjConfig(); + const provisionTimeoutMs = this.getProvisionTimeoutMs(); + + await this.executeProvisioningScript( + payload, + safeContainerName, + spinUpScript, + requiredEnvironment, + provisionTimeoutMs + ); + + return this.readAgentEndpoint(payload.orgId, safeContainerName, endpointDirectory); + } + + private validateAfjConfig(): { + spinUpScript: string; + endpointDirectory: string; + requiredEnvironment: string[]; + } { + const spinUpScript = process.env.AFJ_AGENT_SPIN_UP; + const endpointDirectory = process.env.AFJ_AGENT_ENDPOINT_PATH; + if (!spinUpScript || !endpointDirectory) { + throw new Error('AFJ_AGENT_SPIN_UP and AFJ_AGENT_ENDPOINT_PATH must be configured'); + } + + const requiredEnvironment = [ + 'SCHEMA_FILE_SERVER_URL', + 'AGENT_API_KEY', + 'AWS_ACCOUNT_ID', + 'S3_BUCKET_ARN', + 'CLUSTER_NAME', + 'TASKDEFINITION_FAMILY', + 'ADMIN_TG_ARN', + 'INBOUND_TG_ARN', + 'FILESYSTEMID', + 'ECS_SUBNET_ID', + 'ECS_SECURITY_GROUP_ID' + ]; + const missingEnvironment = requiredEnvironment.filter((name) => !process.env[name]); + if (missingEnvironment.length) { + throw new Error(`Missing provisioning configuration: ${missingEnvironment.join(', ')}`); + } + + return { spinUpScript, endpointDirectory, requiredEnvironment }; + } + + private async executeProvisioningScript( + payload: IWalletProvision, + safeContainerName: string, + spinUpScript: string, + requiredEnvironment: string[], + provisionTimeoutMs: number + ): Promise { + await execFileAsync( + `${process.cwd()}${spinUpScript}`, + [ + payload.orgId, + payload.externalIp, + payload.walletName, + payload.walletPassword, + payload.seed, + payload.webhookEndpoint, + payload.walletStorageHost, + payload.walletStoragePort, + payload.walletStorageUser, + payload.walletStoragePassword, + safeContainerName, + payload.protocol, + String(payload.tenant), + payload.credoImage, + payload.indyLedger, + payload.inboundEndpoint, + ...requiredEnvironment.map((name) => process.env[name] as string) + ], + { timeout: provisionTimeoutMs, maxBuffer: 1024 * 1024 } + ).catch((error) => { + throw new Error(`Agent provisioning script failed${this.formatScriptFailure(error)}`); + }); + } + + private formatScriptFailure(error: { code?: unknown; signal?: unknown }): string { + if ('number' === typeof error?.code) { + return ` (exit code ${error.code})`; + } + if ('string' === typeof error?.signal) { + return ` (signal ${error.signal})`; + } + return ''; + } + + private async readAgentEndpoint( + orgId: string, + safeContainerName: string, + endpointDirectory: string + ): Promise { + const agentEndpointPath = `${process.cwd()}${endpointDirectory}${orgId}_${safeContainerName}.json`; + const agentEndPointExists = await this.checkFileExistence(agentEndpointPath); + if (!agentEndPointExists) { + throw new NotFoundException(`Agent endpoint file does not exist: ${agentEndpointPath}`); + } + + const agentEndPoint = await fs.readFile(agentEndpointPath, 'utf8'); + let parsedEndpoint: unknown; + try { + parsedEndpoint = JSON.parse(agentEndPoint); + } catch (parseError) { + this.logger.error(`Failed to parse agent endpoint file: ${parseError.message}`); + throw new Error(`Invalid JSON in agent endpoint file: ${agentEndpointPath}`); + } + + const controllerEndpoint = + null !== parsedEndpoint && 'object' === typeof parsedEndpoint && !Array.isArray(parsedEndpoint) + ? (parsedEndpoint as Record).CONTROLLER_ENDPOINT + : undefined; + if ('string' !== typeof controllerEndpoint || !controllerEndpoint.trim()) { + throw new Error(`Missing CONTROLLER_ENDPOINT in: ${agentEndpointPath}`); + } + + return { agentEndPoint: controllerEndpoint }; + } + private getProvisionTimeoutMs(): number { const configuredTimeout = process.env.AFJ_AGENT_PROVISION_TIMEOUT_MS; if (!configuredTimeout) { @@ -157,14 +184,15 @@ export class AgentProvisioningService { private normalizeContainerName(value: unknown): string { if ('string' !== typeof value) { - throw new Error('containerName contains unsafe characters'); + throw new TypeError('containerName contains unsafe characters'); } const normalized = value .normalize('NFKD') - .replace(/[\u0300-\u036f]/g, '') + .replace(/\p{M}/gu, '') .replace(/[^A-Za-z0-9_-]+/g, '_') - .replace(/^_+|_+$/g, '') + .replace(/^_+/, '') + .replace(/_+$/, '') .slice(0, 128); return normalized || 'agent'; From 64c5f6795d26f8df8eba1570c30a13c01adbf12c Mon Sep 17 00:00:00 2001 From: Mark Date: Tue, 15 Sep 2026 20:44:18 +0800 Subject: [PATCH 54/57] fix(agent): preserve ledger JSON during provisioning Signed-off-by: Mark --- .../src/agent-config-generation.spec.ts | 68 +++++++++++++++++++ .../src/agent-provisioning.service.spec.ts | 19 ++++++ .../src/agent-service.service.spec.ts | 60 ++++++++++++++++ .../src/agent-service.service.ts | 5 +- 4 files changed, 150 insertions(+), 2 deletions(-) create mode 100644 apps/agent-provisioning/src/agent-config-generation.spec.ts create mode 100644 apps/agent-service/src/agent-service.service.spec.ts diff --git a/apps/agent-provisioning/src/agent-config-generation.spec.ts b/apps/agent-provisioning/src/agent-config-generation.spec.ts new file mode 100644 index 000000000..a7be5a451 --- /dev/null +++ b/apps/agent-provisioning/src/agent-config-generation.spec.ts @@ -0,0 +1,68 @@ +import { execFileSync } from 'node:child_process'; +import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; + +// Exercise the real configuration template, stopping before container startup. +// No Docker, network calls, or real credentials are needed for this regression. +describe('Indy ledger configuration generation', () => { + const genesisTransactions = [ + JSON.stringify({ txn: { data: { alias: 'Node "One"', path: 'C:\\ledger' } } }), + JSON.stringify({ txn: { data: { alias: 'Node Two' } } }) + ].join('\n'); + const ledger = { genesisTransactions, indyNamespace: 'test:ledger' }; + let directory: string; + let scriptPath: string; + + beforeEach(() => { + directory = mkdtempSync(join(tmpdir(), 'agent-config-test-')); + mkdirSync(join(directory, 'apps/agent-provisioning/AFJ/port-file'), { recursive: true }); + const source = readFileSync(join(__dirname, '../AFJ/scripts/start_agent.sh'), 'utf8'); + const stopBefore = '\nFILE_NAME="docker-compose_'; + expect(source.split(stopBefore)).toHaveLength(2); + const [configurationScript] = source.split(stopBefore); + scriptPath = join(directory, 'generate-config.sh'); + writeFileSync(scriptPath, configurationScript, { mode: 0o700 }); + }); + + afterEach(() => { + rmSync(directory, { recursive: true, force: true }); + }); + + it.each([ + ['empty', []], + ['quoted and multiline genesis', [ledger]], + ['multiple', [ledger, { ...ledger, indyNamespace: 'test:second' }]] + ])('preserves %s ledger data passed without shell escaping', (_name, ledgers) => { + execFileSync( + scriptPath, + [ + 'org-test', + '127.0.0.1', + 'wallet', + 'test-wallet-key', + 'seed', + 'https://webhook.example.test', + 'postgres', + '5432', + 'user', + 'test-storage-key', + 'agent-test', + 'http', + 'false', + 'credo:test', + JSON.stringify(ledgers), + '127.0.0.1', + 'https://schema.example.test', + 'test-api-key' + ], + { cwd: directory, timeout: 5000, stdio: 'pipe' } + ); + + const config = JSON.parse( + readFileSync(join(directory, 'apps/agent-provisioning/AFJ/agent-config/org-test_agent-test.json'), 'utf8') + ); + expect(config.indyLedger).toEqual(ledgers); + expect(config.walletId).toBe('wallet'); + }); +}); diff --git a/apps/agent-provisioning/src/agent-provisioning.service.spec.ts b/apps/agent-provisioning/src/agent-provisioning.service.spec.ts index 31b3b542a..eca48194c 100644 --- a/apps/agent-provisioning/src/agent-provisioning.service.spec.ts +++ b/apps/agent-provisioning/src/agent-provisioning.service.spec.ts @@ -72,6 +72,25 @@ describe('AgentProvisioningService', () => { ); }); + it('passes nonempty ledger JSON unchanged as the fifteenth script argument', async () => { + const ledgers = [ + { + genesisTransactions: '{"txn":{"alias":"Node1"}}\n{"txn":{"alias":"Node2"}}', + indyNamespace: 'test:ledger' + } + ]; + const indyLedger = JSON.stringify(ledgers); + mockExecFile.mockResolvedValue({ stdout: '', stderr: '' }); + mockReadFile.mockResolvedValue('{"CONTROLLER_ENDPOINT":"https://agent.example"}'); + + await service.walletProvision({ ...payload, indyLedger }); + + const [[, args]] = mockExecFile.mock.calls; + expect(args[14]).toBe(indyLedger); + expect(JSON.parse(args[14])).toEqual(ledgers); + expect(args[15]).toBe(payload.inboundEndpoint); + }); + it('normalizes organization-derived container names before executing the script and reading its endpoint', async () => { mockExecFile.mockResolvedValue({ stdout: '', stderr: '' }); mockReadFile.mockResolvedValue('{"CONTROLLER_ENDPOINT":"https://agent.example"}'); diff --git a/apps/agent-service/src/agent-service.service.spec.ts b/apps/agent-service/src/agent-service.service.spec.ts new file mode 100644 index 000000000..51b5d5711 --- /dev/null +++ b/apps/agent-service/src/agent-service.service.spec.ts @@ -0,0 +1,60 @@ +import { ledgers, organisation } from '@prisma/client'; +import { IAgentSpinupDto, IWalletProvision } from './interface/agent-service.interface'; +import { AgentServiceService } from './agent-service.service'; + +describe('AgentServiceService.prepareWalletProvisionPayload', () => { + const service = new AgentServiceService( + null as never, + null as never, + null as never, + null as never, + null as never, + null as never, + null as never + ); + const spinup = { + walletName: 'test-wallet', + walletPassword: 'test-password', + seed: 'test-seed', + keyType: 'ed25519', + method: 'indy' + } as IAgentSpinupDto; + const org = { id: 'org-1', name: 'Test Organization' } as organisation; + + async function provision(ledgerDetails: Pick[]): Promise { + return service.prepareWalletProvisionPayload( + spinup, + '127.0.0.1', + 'http://api.example.test', + 'http://agent.example.test', + ledgerDetails as ledgers[], + org + ); + } + + it('serializes no Indy ledgers as an empty JSON array', async () => { + const payload = await provision([]); + expect(JSON.parse(payload.indyLedger)).toEqual([]); + }); + + it('round-trips a non-empty genesis containing quotes, newlines, and backslashes', async () => { + const genesisTransactions = [ + JSON.stringify({ txn: { data: { alias: 'Node "One"', path: 'C:\\indy\\pool' } } }), + JSON.stringify({ txn: { data: { alias: 'Node Two' } } }) + ].join('\n'); + const indyNamespace = 'test:local'; + const payload = await provision([{ poolConfig: genesisTransactions, indyNamespace }]); + expect(JSON.parse(payload.indyLedger)).toEqual([{ genesisTransactions, indyNamespace }]); + }); + + it('preserves multiple ledgers in their input order', async () => { + const payload = await provision([ + { poolConfig: 'first genesis', indyNamespace: 'test:first' }, + { poolConfig: 'second genesis', indyNamespace: 'test:second' } + ]); + expect(JSON.parse(payload.indyLedger)).toEqual([ + { genesisTransactions: 'first genesis', indyNamespace: 'test:first' }, + { genesisTransactions: 'second genesis', indyNamespace: 'test:second' } + ]); + }); +}); diff --git a/apps/agent-service/src/agent-service.service.ts b/apps/agent-service/src/agent-service.service.ts index 0c09d315c..39c3e0e30 100644 --- a/apps/agent-service/src/agent-service.service.ts +++ b/apps/agent-service/src/agent-service.service.ts @@ -403,7 +403,8 @@ export class AgentServiceService { indyNamespace: ledger.indyNamespace })); - const escapedJsonString = JSON.stringify(ledgerArray).replace(/"/g, '\\"'); + // execFile passes arguments directly; the ledger payload must remain valid JSON. + const indyLedger = JSON.stringify(ledgerArray); const walletProvisionPayload: IWalletProvision = { orgId: orgData?.id, @@ -420,7 +421,7 @@ export class AgentServiceService { containerName: orgData.name.split(' ').join('_'), agentType: AgentType.AFJ, orgName: orgData?.name, - indyLedger: escapedJsonString, + indyLedger, credoImage: process.env.AFJ_VERSION || '', protocol: process.env.AGENT_PROTOCOL || '', tenant: agentSpinupDto.tenant || false, From a409d666d0e56528b529aad22c0ed29a27743c1f Mon Sep 17 00:00:00 2001 From: Mark Date: Tue, 15 Sep 2026 20:53:21 +0800 Subject: [PATCH 55/57] fix(agent): trim container suffix without regex backtracking Signed-off-by: Mark --- .../src/agent-provisioning.service.spec.ts | 22 +++++++++++++++++++ .../src/agent-provisioning.service.ts | 11 ++++++---- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/apps/agent-provisioning/src/agent-provisioning.service.spec.ts b/apps/agent-provisioning/src/agent-provisioning.service.spec.ts index eca48194c..163d5757f 100644 --- a/apps/agent-provisioning/src/agent-provisioning.service.spec.ts +++ b/apps/agent-provisioning/src/agent-provisioning.service.spec.ts @@ -106,6 +106,28 @@ describe('AgentProvisioningService', () => { expect(mockReadFile).toHaveBeenCalledWith(expect.stringContaining('org-123_Credit_Agricole_Inc.json'), 'utf8'); }); + it.each([ + ['', 'agent'], + ['____', 'agent'], + ['___agent___', 'agent'], + ['agent__name___', 'agent__name'], + ['-agent-', '-agent-'], + [' Crédit Agricole, Inc. ', 'Credit_Agricole_Inc'], + [`${'a'.repeat(128)}___`, 'a'.repeat(128)], + [`${'a'.repeat(127)}_b`, `${'a'.repeat(127)}_`], + [`a${'_'.repeat(100_000)}b`, `a${'_'.repeat(127)}`], + [`a${'_'.repeat(100_000)}`, 'a'] + ])('preserves container normalization for case %#', async (containerName, expected) => { + mockExecFile.mockResolvedValue({ stdout: '', stderr: '' }); + mockReadFile.mockResolvedValue('{"CONTROLLER_ENDPOINT":"https://agent.example"}'); + + await service.walletProvision({ ...payload, containerName }); + + const [[, args]] = mockExecFile.mock.calls; + expect(args[10]).toBe(expected); + expect(mockReadFile).toHaveBeenCalledWith(expect.stringContaining(`org-123_${expected}.json`), 'utf8'); + }); + it('rejects non-string identifiers before executing a script', async () => { await expect(service.walletProvision({ ...payload, orgId: 123 as unknown as string })).rejects.toThrow( 'orgId contains unsafe characters' diff --git a/apps/agent-provisioning/src/agent-provisioning.service.ts b/apps/agent-provisioning/src/agent-provisioning.service.ts index 4f944dd50..c79a07dbe 100644 --- a/apps/agent-provisioning/src/agent-provisioning.service.ts +++ b/apps/agent-provisioning/src/agent-provisioning.service.ts @@ -191,11 +191,14 @@ export class AgentProvisioningService { .normalize('NFKD') .replace(/\p{M}/gu, '') .replace(/[^A-Za-z0-9_-]+/g, '_') - .replace(/^_+/, '') - .replace(/_+$/, '') - .slice(0, 128); + .replace(/^_+/, ''); - return normalized || 'agent'; + let end = normalized.length; + while (0 < end && '_' === normalized[end - 1]) { + end--; + } + + return normalized.slice(0, Math.min(end, 128)) || 'agent'; } async checkFileExistence(filePath: string): Promise { From 91f873496812a675cf8f238f6e2a3d78193c9f38 Mon Sep 17 00:00:00 2001 From: Mark Date: Tue, 15 Sep 2026 21:16:07 +0800 Subject: [PATCH 56/57] fix(agent): complete provisioning failure handling Signed-off-by: Mark --- .env.demo | 5 +- README.md | 8 + .../src/agent-config-generation.spec.ts | 3 +- .../src/agent-provisioning-timeout.spec.ts | 77 +++++++ .../src/agent-provisioning.service.spec.ts | 208 ++++++++++-------- .../src/agent-provisioning.service.ts | 143 +++++++----- .../src/agent-service.service.ts | 2 +- jest.ci.config.js | 4 +- 8 files changed, 300 insertions(+), 150 deletions(-) create mode 100644 apps/agent-provisioning/src/agent-provisioning-timeout.spec.ts diff --git a/.env.demo b/.env.demo index 102d61508..d3c79cb17 100644 --- a/.env.demo +++ b/.env.demo @@ -176,7 +176,7 @@ KEYCLOAK_MANAGEMENT_CLIENT_ID=credeblClient KEYCLOAK_MANAGEMENT_CLIENT_SECRET= KEYCLOAK_REALM=credebl-platform -# Provide schema server URL and token +# Provide schema server URL and token. SCHEMA_FILE_SERVER_URL is required for agent provisioning. SCHEMA_FILE_SERVER_URL= SCHEMA_FILE_SERVER_TOKEN= @@ -197,6 +197,9 @@ AFJ_AGENT_ENDPOINT_PATH=/agent-provisioning/AFJ/endpoints/ # AFJ_AGENT_SPIN_UP=/apps/agent-provisioning/AFJ/scripts/start_agent.sh # AFJ_AGENT_ENDPOINT_PATH=/apps/agent-provisioning/AFJ/endpoints/ +# Provisioning timeout in milliseconds (default: 300000). Existing containers/cloud resources are not rolled back. +# AFJ_AGENT_PROVISION_TIMEOUT_MS=300000 + # Protocol used by the agent service AGENT_PROTOCOL=http diff --git a/README.md b/README.md index eca6e4304..d3ab53477 100644 --- a/README.md +++ b/README.md @@ -156,6 +156,14 @@ nest start agent-provisioning [--watch] nest start agent-service [--watch] ``` +### Agent provisioning + +Set `AFJ_AGENT_SPIN_UP`, `AFJ_AGENT_ENDPOINT_PATH`, `SCHEMA_FILE_SERVER_URL`, and `AGENT_API_KEY` before provisioning. Local and Docker scripts do not require AWS settings. `start_agent_ecs.sh` also requires the ECS settings through `FILESYSTEMID` listed in `.env.demo`; `fargate.sh` additionally requires `ECS_SUBNET_ID` and `ECS_SECURITY_GROUP_ID`. Custom scripts must validate their own additional settings. + +Deploy `agent-service` and `agent-provisioning` from the same release: ledger JSON is passed directly as a process argument, without shell escaping. Pause new provisioning requests while updating the pair. + +`AFJ_AGENT_PROVISION_TIMEOUT_MS` defaults to 300000 (five minutes). On failure or timeout, the service stops the script's process group. This does not remove containers or cloud resources already created; inspect those before retrying a failed request. + ## Testing Tests are written with [Jest](https://jestjs.io) and run via pnpm (the repo's package manager): diff --git a/apps/agent-provisioning/src/agent-config-generation.spec.ts b/apps/agent-provisioning/src/agent-config-generation.spec.ts index a7be5a451..92ca88ec6 100644 --- a/apps/agent-provisioning/src/agent-config-generation.spec.ts +++ b/apps/agent-provisioning/src/agent-config-generation.spec.ts @@ -31,8 +31,7 @@ describe('Indy ledger configuration generation', () => { it.each([ ['empty', []], - ['quoted and multiline genesis', [ledger]], - ['multiple', [ledger, { ...ledger, indyNamespace: 'test:second' }]] + ['multiple with quoted and multiline genesis', [ledger, { ...ledger, indyNamespace: 'test:second' }]] ])('preserves %s ledger data passed without shell escaping', (_name, ledgers) => { execFileSync( scriptPath, diff --git a/apps/agent-provisioning/src/agent-provisioning-timeout.spec.ts b/apps/agent-provisioning/src/agent-provisioning-timeout.spec.ts new file mode 100644 index 000000000..741d37f05 --- /dev/null +++ b/apps/agent-provisioning/src/agent-provisioning-timeout.spec.ts @@ -0,0 +1,77 @@ +import { mkdtempSync, writeFileSync, existsSync, rmSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join, relative } from 'node:path'; +import { Logger } from '@nestjs/common'; +import { AgentType } from '@credebl/enum/enum'; +import { AgentProvisioningService } from './agent-provisioning.service'; +import { IWalletProvision } from './interface/agent-provisioning.interfaces'; + +describe('Provisioning timeout', () => { + it('stops the script and its child before the child can write after a timeout', async () => { + const directory = mkdtempSync(join(tmpdir(), 'provisioning-timeout-')); + const savedEnv = { ...process.env }; + const started = join(directory, 'started'); + const completed = join(directory, 'completed'); + const childPath = join(directory, 'child.cjs'); + const scriptPath = join(directory, 'run.sh'); + writeFileSync( + childPath, + ` + const fs = require('node:fs'); + fs.writeFileSync(${JSON.stringify(started)}, 'started'); + setTimeout(() => { + if (fs.existsSync(${JSON.stringify(directory)})) { + fs.writeFileSync(${JSON.stringify(completed)}, 'completed'); + } + }, 3000); + ` + ); + writeFileSync( + scriptPath, + `#!/bin/sh +trap '' TERM +"$PROVISIONING_TEST_NODE" "$PROVISIONING_TEST_CHILD" & +wait +`, + { mode: 0o700 } + ); + process.env.PROVISIONING_TEST_NODE = process.execPath; + process.env.PROVISIONING_TEST_CHILD = childPath; + process.env.AFJ_AGENT_SPIN_UP = `/${relative(process.cwd(), scriptPath)}`; + process.env.AFJ_AGENT_ENDPOINT_PATH = '/unused/'; + process.env.AFJ_AGENT_PROVISION_TIMEOUT_MS = '1000'; + process.env.SCHEMA_FILE_SERVER_URL = 'https://schema.example.test'; + process.env.AGENT_API_KEY = 'test-key'; + const logger = { error: jest.fn() }; + const service = new AgentProvisioningService(logger as unknown as Logger); + const payload = { + orgId: 'org-test', + containerName: 'agent', + agentType: AgentType.AFJ, + externalIp: '127.0.0.1', + walletName: 'wallet', + walletPassword: 'test-key', + seed: 'seed', + webhookEndpoint: 'https://webhook.example.test', + walletStorageHost: 'postgres', + walletStoragePort: '5432', + walletStorageUser: 'user', + walletStoragePassword: 'test-key', + protocol: 'http', + tenant: false, + credoImage: 'credo:test', + indyLedger: '[]', + inboundEndpoint: '127.0.0.1' + } as IWalletProvision; + + try { + await expect(service.walletProvision(payload)).rejects.toThrow('signal SIGKILL'); + expect(existsSync(started)).toBe(true); + await new Promise((resolve) => setTimeout(resolve, 3300)); + expect(existsSync(completed)).toBe(false); + } finally { + process.env = savedEnv; + rmSync(directory, { recursive: true, force: true }); + } + }, 10_000); +}); diff --git a/apps/agent-provisioning/src/agent-provisioning.service.spec.ts b/apps/agent-provisioning/src/agent-provisioning.service.spec.ts index 163d5757f..73c2ecc70 100644 --- a/apps/agent-provisioning/src/agent-provisioning.service.spec.ts +++ b/apps/agent-provisioning/src/agent-provisioning.service.spec.ts @@ -1,11 +1,11 @@ -const mockExecFile = jest.fn(); +const mockSpawn = jest.fn(); const mockReadFile = jest.fn(); const mockAccess = jest.fn(); -jest.mock('node:child_process', () => ({ execFile: mockExecFile })); -jest.mock('node:util', () => ({ promisify: jest.fn(() => mockExecFile) })); +jest.mock('node:child_process', () => ({ spawn: mockSpawn })); jest.mock('fs', () => ({ promises: { access: mockAccess, readFile: mockReadFile } })); +import { EventEmitter } from 'node:events'; import { AgentType } from '@credebl/enum/enum'; import { AgentProvisioningService } from './agent-provisioning.service'; @@ -30,6 +30,30 @@ const payload = { tenant: false, inboundEndpoint: '127.0.0.1' }; +const awsEnvironment = { + AWS_ACCOUNT_ID: 'account', + S3_BUCKET_ARN: 'bucket', + CLUSTER_NAME: 'cluster', + TASKDEFINITION_FAMILY: 'family', + ADMIN_TG_ARN: 'admin-tg', + INBOUND_TG_ARN: 'inbound-tg', + FILESYSTEMID: 'filesystem', + ECS_SUBNET_ID: 'subnet', + ECS_SECURITY_GROUP_ID: 'security-group' +}; +const awsArguments = Object.values(awsEnvironment); + +function closeChild(code = 0): EventEmitter { + const child = Object.assign(new EventEmitter(), { pid: undefined, kill: jest.fn() }); + queueMicrotask(() => child.emit('close', code, null)); + return child; +} + +function errorChild(error: Error): EventEmitter { + const child = Object.assign(new EventEmitter(), { pid: undefined, kill: jest.fn() }); + queueMicrotask(() => child.emit('error', error)); + return child; +} describe('AgentProvisioningService', () => { const logger = { log: jest.fn(), error: jest.fn() }; @@ -43,36 +67,40 @@ describe('AgentProvisioningService', () => { AFJ_AGENT_SPIN_UP: '/apps/agent-provisioning/AFJ/scripts/start_agent.sh', AFJ_AGENT_ENDPOINT_PATH: '/apps/agent-provisioning/AFJ/endpoints/', SCHEMA_FILE_SERVER_URL: 'https://schema.example', - AGENT_API_KEY: 'agent-key', - AWS_ACCOUNT_ID: 'account', - S3_BUCKET_ARN: 'bucket', - CLUSTER_NAME: 'cluster', - TASKDEFINITION_FAMILY: 'family', - ADMIN_TG_ARN: 'admin-tg', - INBOUND_TG_ARN: 'inbound-tg', - FILESYSTEMID: 'filesystem', - ECS_SUBNET_ID: 'subnet', - ECS_SECURITY_GROUP_ID: 'security-group' + AGENT_API_KEY: 'agent-key' }; + for (const name of Object.keys(awsEnvironment)) { + delete process.env[name]; + } + mockSpawn.mockImplementation(() => closeChild()); + mockReadFile.mockResolvedValue('{"CONTROLLER_ENDPOINT":"https://agent.example"}'); }); - afterAll(() => { process.env = savedEnv; }); - it('executes the provisioning script without a shell and returns the generated endpoint', async () => { - mockExecFile.mockResolvedValue({ stdout: '', stderr: '' }); - mockReadFile.mockResolvedValue('{"CONTROLLER_ENDPOINT":"https://agent.example"}'); - - await expect(service.walletProvision(payload)).resolves.toEqual({ agentEndPoint: 'https://agent.example' }); - expect(mockExecFile).toHaveBeenCalledWith( - expect.stringContaining('/apps/agent-provisioning/AFJ/scripts/start_agent.sh'), - expect.arrayContaining([payload.orgId, payload.containerName, payload.walletPassword]), - expect.objectContaining({ timeout: 300000 }) - ); - }); - - it('passes nonempty ledger JSON unchanged as the fifteenth script argument', async () => { + it.each(['start_agent.sh', 'docker_start_agent.sh'])( + 'provisions with local %s using only common configuration and positional placeholders', + async (script) => { + process.env.AFJ_AGENT_SPIN_UP = `/apps/agent-provisioning/AFJ/scripts/${script}`; + await expect(service.walletProvision(payload)).resolves.toEqual({ agentEndPoint: 'https://agent.example' }); + const [[scriptPath, args, options]] = mockSpawn.mock.calls; + expect(scriptPath).toContain(script); + expect(args).toHaveLength(27); + expect(args.slice(16, 18)).toEqual(['https://schema.example', 'agent-key']); + expect(args.slice(18)).toEqual(Array(9).fill('')); + expect(options).toEqual( + expect.objectContaining({ + timeout: 300000, + detached: true, + killSignal: 'SIGKILL', + stdio: 'ignore' + }) + ); + } + ); + + it('retains nonempty ledger JSON in the fifteenth argument', async () => { const ledgers = [ { genesisTransactions: '{"txn":{"alias":"Node1"}}\n{"txn":{"alias":"Node2"}}', @@ -80,101 +108,103 @@ describe('AgentProvisioningService', () => { } ]; const indyLedger = JSON.stringify(ledgers); - mockExecFile.mockResolvedValue({ stdout: '', stderr: '' }); - mockReadFile.mockResolvedValue('{"CONTROLLER_ENDPOINT":"https://agent.example"}'); - await service.walletProvision({ ...payload, indyLedger }); - - const [[, args]] = mockExecFile.mock.calls; + const [[, args]] = mockSpawn.mock.calls; expect(args[14]).toBe(indyLedger); expect(JSON.parse(args[14])).toEqual(ledgers); expect(args[15]).toBe(payload.inboundEndpoint); }); - it('normalizes organization-derived container names before executing the script and reading its endpoint', async () => { - mockExecFile.mockResolvedValue({ stdout: '', stderr: '' }); - mockReadFile.mockResolvedValue('{"CONTROLLER_ENDPOINT":"https://agent.example"}'); + it('requires ECS configuration while leaving unused positional arguments empty', async () => { + process.env.AFJ_AGENT_SPIN_UP = '/apps/agent-provisioning/AFJ/scripts/start_agent_ecs.sh'; + Object.assign(process.env, awsEnvironment); + delete process.env.ECS_SUBNET_ID; + delete process.env.ECS_SECURITY_GROUP_ID; + await service.walletProvision(payload); + const [[, args]] = mockSpawn.mock.calls; + expect(args).toHaveLength(27); + expect(args.slice(18, 25)).toEqual(awsArguments.slice(0, 7)); + expect(args.slice(25)).toEqual(['', '']); + + mockSpawn.mockClear(); + delete process.env.FILESYSTEMID; + await expect(service.walletProvision(payload)).rejects.toThrow('FILESYSTEMID'); + expect(mockSpawn).not.toHaveBeenCalled(); + }); - await expect(service.walletProvision({ ...payload, containerName: 'Crédit Agricole, Inc.' })).resolves.toEqual({ - agentEndPoint: 'https://agent.example' - }); - expect(mockExecFile).toHaveBeenCalledWith( - expect.any(String), - expect.arrayContaining(['Credit_Agricole_Inc']), - expect.any(Object) + it('requires all Fargate configuration in the original argument positions', async () => { + process.env.AFJ_AGENT_SPIN_UP = '/apps/agent-provisioning/AFJ/scripts/fargate.sh'; + Object.assign(process.env, awsEnvironment); + await service.walletProvision(payload); + const [[, args]] = mockSpawn.mock.calls; + expect(args).toHaveLength(27); + expect(args.slice(18)).toEqual(awsArguments); + + mockSpawn.mockClear(); + delete process.env.ECS_SECURITY_GROUP_ID; + await expect(service.walletProvision(payload)).rejects.toThrow('ECS_SECURITY_GROUP_ID'); + expect(mockSpawn).not.toHaveBeenCalled(); + }); + + it('rejects unsafe organization identifiers before executing a script', async () => { + await expect(service.walletProvision({ ...payload, orgId: '../another-org' })).rejects.toThrow( + 'orgId contains unsafe characters' ); - expect(mockReadFile).toHaveBeenCalledWith(expect.stringContaining('org-123_Credit_Agricole_Inc.json'), 'utf8'); + expect(mockSpawn).not.toHaveBeenCalled(); }); it.each([ ['', 'agent'], ['____', 'agent'], - ['___agent___', 'agent'], - ['agent__name___', 'agent__name'], + ['___agent__name___', 'agent__name'], ['-agent-', '-agent-'], - [' Crédit Agricole, Inc. ', 'Credit_Agricole_Inc'], - [`${'a'.repeat(128)}___`, 'a'.repeat(128)], - [`${'a'.repeat(127)}_b`, `${'a'.repeat(127)}_`], + [`a${'_'.repeat(100_000)}`, 'a'], + ['Crédit Agricole, Inc.', 'Credit_Agricole_Inc'], [`a${'_'.repeat(100_000)}b`, `a${'_'.repeat(127)}`], - [`a${'_'.repeat(100_000)}`, 'a'] - ])('preserves container normalization for case %#', async (containerName, expected) => { - mockExecFile.mockResolvedValue({ stdout: '', stderr: '' }); - mockReadFile.mockResolvedValue('{"CONTROLLER_ENDPOINT":"https://agent.example"}'); - + [`${'a'.repeat(127)}_b`, `${'a'.repeat(127)}_`] + ])('preserves container name normalization (case %#)', async (containerName, expected) => { await service.walletProvision({ ...payload, containerName }); - - const [[, args]] = mockExecFile.mock.calls; + const [[, args]] = mockSpawn.mock.calls; expect(args[10]).toBe(expected); expect(mockReadFile).toHaveBeenCalledWith(expect.stringContaining(`org-123_${expected}.json`), 'utf8'); }); - it('rejects non-string identifiers before executing a script', async () => { - await expect(service.walletProvision({ ...payload, orgId: 123 as unknown as string })).rejects.toThrow( - 'orgId contains unsafe characters' - ); - expect(mockExecFile).not.toHaveBeenCalled(); - }); - - it('uses a configured provisioning timeout', async () => { - process.env.AFJ_AGENT_PROVISION_TIMEOUT_MS = '600000'; - mockExecFile.mockResolvedValue({ stdout: '', stderr: '' }); - mockReadFile.mockResolvedValue('{"CONTROLLER_ENDPOINT":"https://agent.example"}'); - - await service.walletProvision(payload); - expect(mockExecFile).toHaveBeenCalledWith( - expect.any(String), - expect.any(Array), - expect.objectContaining({ timeout: 600000 }) - ); + it('rejects an invalid endpoint JSON document', async () => { + mockReadFile.mockResolvedValue('not JSON'); + await expect(service.walletProvision(payload)).rejects.toThrow('Invalid JSON in agent endpoint file'); }); - it.each([{}, 1, [], '', ' '])('rejects invalid CONTROLLER_ENDPOINT values', async (endpoint) => { - mockExecFile.mockResolvedValue({ stdout: '', stderr: '' }); + it.each([{}, 1, [], '', ' '])('rejects an invalid controller endpoint', async (endpoint) => { mockReadFile.mockResolvedValue(JSON.stringify({ CONTROLLER_ENDPOINT: endpoint })); - await expect(service.walletProvision(payload)).rejects.toThrow('Missing CONTROLLER_ENDPOINT'); }); it('rejects a non-object endpoint document', async () => { - mockExecFile.mockResolvedValue({ stdout: '', stderr: '' }); mockReadFile.mockResolvedValue('null'); - await expect(service.walletProvision(payload)).rejects.toThrow('Missing CONTROLLER_ENDPOINT'); }); - it('propagates a provisioning script failure instead of attempting to read an endpoint file', async () => { - const failure = Object.assign(new Error('script failed'), { - code: 17, - stdout: 'stdout-secret', - stderr: 'stderr-secret' - }); - mockExecFile.mockRejectedValue(failure); + it('uses the configured timeout', async () => { + process.env.AFJ_AGENT_PROVISION_TIMEOUT_MS = '600000'; + await service.walletProvision(payload); + const [[, , options]] = mockSpawn.mock.calls; + expect(options.timeout).toBe(600000); + }); + it('reports a script exit code without logging script output or credentials', async () => { + mockSpawn.mockImplementation(() => closeChild(17)); await expect(service.walletProvision(payload)).rejects.toThrow('Agent provisioning script failed'); expect(mockReadFile).not.toHaveBeenCalled(); - expect(logger.error).toHaveBeenCalled(); - expect(JSON.stringify(logger.error.mock.calls)).not.toContain('stdout-secret'); - expect(JSON.stringify(logger.error.mock.calls)).not.toContain('stderr-secret'); - expect(JSON.stringify(logger.error.mock.calls)).toContain('exit code 17'); + const logs = JSON.stringify(logger.error.mock.calls); + expect(logs).toContain('exit code 17'); + expect(logs).not.toContain('wallet-secret'); + }); + + it('reports ENOENT without exposing a spawn error message', async () => { + const failure = Object.assign(new Error('spawn wallet-secret ENOENT'), { code: 'ENOENT' }); + mockSpawn.mockImplementation(() => errorChild(failure)); + await expect(service.walletProvision(payload)).rejects.toThrow('ENOENT'); + expect(JSON.stringify(logger.error.mock.calls)).not.toContain('wallet-secret'); + expect(mockReadFile).not.toHaveBeenCalled(); }); }); diff --git a/apps/agent-provisioning/src/agent-provisioning.service.ts b/apps/agent-provisioning/src/agent-provisioning.service.ts index c79a07dbe..556ef67c8 100644 --- a/apps/agent-provisioning/src/agent-provisioning.service.ts +++ b/apps/agent-provisioning/src/agent-provisioning.service.ts @@ -6,12 +6,25 @@ import { Injectable, Logger, NotFoundException } from '@nestjs/common'; import { AgentType } from '@credebl/enum/enum'; import { IWalletProvision } from './interface/agent-provisioning.interfaces'; import { RpcException } from '@nestjs/microservices'; -import { execFile } from 'node:child_process'; -import { promisify } from 'node:util'; +import { spawn } from 'node:child_process'; +import { basename } from 'node:path'; -const execFileAsync = promisify(execFile); const SAFE_FILE_IDENTIFIER = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/; const DEFAULT_AGENT_PROVISION_TIMEOUT_MS = 300_000; +// Keep the script's positional arguments fixed, including unused cloud settings. +const PROVISIONING_ENVIRONMENT = [ + 'SCHEMA_FILE_SERVER_URL', + 'AGENT_API_KEY', + 'AWS_ACCOUNT_ID', + 'S3_BUCKET_ARN', + 'CLUSTER_NAME', + 'TASKDEFINITION_FAMILY', + 'ADMIN_TG_ARN', + 'INBOUND_TG_ARN', + 'FILESYSTEMID', + 'ECS_SUBNET_ID', + 'ECS_SECURITY_GROUP_ID' +]; dotenv.config(); @@ -47,90 +60,110 @@ export class AgentProvisioningService { this.assertSafeFileIdentifier(payload.orgId, 'orgId'); const safeContainerName = this.normalizeContainerName(payload.containerName); - const { spinUpScript, endpointDirectory, requiredEnvironment } = this.validateAfjConfig(); + const { spinUpScript, endpointDirectory } = this.validateAfjConfig(); const provisionTimeoutMs = this.getProvisionTimeoutMs(); - await this.executeProvisioningScript( - payload, - safeContainerName, - spinUpScript, - requiredEnvironment, - provisionTimeoutMs - ); + await this.executeProvisioningScript(payload, safeContainerName, spinUpScript, provisionTimeoutMs); return this.readAgentEndpoint(payload.orgId, safeContainerName, endpointDirectory); } - private validateAfjConfig(): { - spinUpScript: string; - endpointDirectory: string; - requiredEnvironment: string[]; - } { + private validateAfjConfig(): { spinUpScript: string; endpointDirectory: string } { const spinUpScript = process.env.AFJ_AGENT_SPIN_UP; const endpointDirectory = process.env.AFJ_AGENT_ENDPOINT_PATH; if (!spinUpScript || !endpointDirectory) { throw new Error('AFJ_AGENT_SPIN_UP and AFJ_AGENT_ENDPOINT_PATH must be configured'); } - const requiredEnvironment = [ - 'SCHEMA_FILE_SERVER_URL', - 'AGENT_API_KEY', - 'AWS_ACCOUNT_ID', - 'S3_BUCKET_ARN', - 'CLUSTER_NAME', - 'TASKDEFINITION_FAMILY', - 'ADMIN_TG_ARN', - 'INBOUND_TG_ARN', - 'FILESYSTEMID', - 'ECS_SUBNET_ID', - 'ECS_SECURITY_GROUP_ID' - ]; + let requiredEnvironment = PROVISIONING_ENVIRONMENT.slice(0, 2); + switch (basename(spinUpScript)) { + case 'start_agent_ecs.sh': + requiredEnvironment = PROVISIONING_ENVIRONMENT.slice(0, 9); + break; + case 'fargate.sh': + requiredEnvironment = PROVISIONING_ENVIRONMENT; + break; + default: + break; + } const missingEnvironment = requiredEnvironment.filter((name) => !process.env[name]); if (missingEnvironment.length) { throw new Error(`Missing provisioning configuration: ${missingEnvironment.join(', ')}`); } - return { spinUpScript, endpointDirectory, requiredEnvironment }; + return { spinUpScript, endpointDirectory }; } private async executeProvisioningScript( payload: IWalletProvision, safeContainerName: string, spinUpScript: string, - requiredEnvironment: string[], provisionTimeoutMs: number ): Promise { - await execFileAsync( - `${process.cwd()}${spinUpScript}`, - [ - payload.orgId, - payload.externalIp, - payload.walletName, - payload.walletPassword, - payload.seed, - payload.webhookEndpoint, - payload.walletStorageHost, - payload.walletStoragePort, - payload.walletStorageUser, - payload.walletStoragePassword, - safeContainerName, - payload.protocol, - String(payload.tenant), - payload.credoImage, - payload.indyLedger, - payload.inboundEndpoint, - ...requiredEnvironment.map((name) => process.env[name] as string) - ], - { timeout: provisionTimeoutMs, maxBuffer: 1024 * 1024 } - ).catch((error) => { + let childPid: number | undefined; + try { + const child = spawn( + `${process.cwd()}${spinUpScript}`, + [ + payload.orgId, + payload.externalIp, + payload.walletName, + payload.walletPassword, + payload.seed, + payload.webhookEndpoint, + payload.walletStorageHost, + payload.walletStoragePort, + payload.walletStorageUser, + payload.walletStoragePassword, + safeContainerName, + payload.protocol, + String(payload.tenant), + payload.credoImage, + payload.indyLedger, + payload.inboundEndpoint, + ...PROVISIONING_ENVIRONMENT.map((name) => process.env[name] || '') + ], + { detached: true, timeout: provisionTimeoutMs, killSignal: 'SIGKILL', stdio: 'ignore', env: process.env } + ); + childPid = child.pid; + await new Promise((resolve, reject) => { + child.once('error', reject); + child.once('close', (code, signal) => { + if (0 === code) { + resolve(); + } else { + reject(Object.assign(new Error('Provisioning process failed'), { code, signal })); + } + }); + }); + } catch (error) { + this.stopProvisioningProcessGroup(childPid); throw new Error(`Agent provisioning script failed${this.formatScriptFailure(error)}`); - }); + } + } + + private stopProvisioningProcessGroup(pid: number | undefined): void { + if ('win32' === process.platform || !pid || !Number.isInteger(pid) || 0 >= pid) { + return; + } + try { + // Provisioning uses POSIX shell scripts; detached gives each run its own group. + process.kill(-pid, 'SIGKILL'); + } catch (error) { + if ('ESRCH' !== (error as NodeJS.ErrnoException).code) { + this.logger.error('Failed to terminate provisioning process group'); + } + } } private formatScriptFailure(error: { code?: unknown; signal?: unknown }): string { if ('number' === typeof error?.code) { return ` (exit code ${error.code})`; } + const safeCodes = ['ENOENT', 'EACCES', 'ENOEXEC', 'E2BIG']; + if ('string' === typeof error?.code && safeCodes.includes(error.code)) { + return ` (${error.code})`; + } if ('string' === typeof error?.signal) { return ` (signal ${error.signal})`; } diff --git a/apps/agent-service/src/agent-service.service.ts b/apps/agent-service/src/agent-service.service.ts index 39c3e0e30..713392ea9 100644 --- a/apps/agent-service/src/agent-service.service.ts +++ b/apps/agent-service/src/agent-service.service.ts @@ -403,7 +403,7 @@ export class AgentServiceService { indyNamespace: ledger.indyNamespace })); - // execFile passes arguments directly; the ledger payload must remain valid JSON. + // Provisioning passes arguments directly; keep the ledger payload as valid JSON. const indyLedger = JSON.stringify(ledgerArray); const walletProvisionPayload: IWalletProvision = { diff --git a/jest.ci.config.js b/jest.ci.config.js index 961ad1a24..d476464cf 100644 --- a/jest.ci.config.js +++ b/jest.ci.config.js @@ -23,7 +23,7 @@ module.exports = { '/apps/oid4vc-verification/', '/apps/oid4vc-issuance/', '/apps/user/test/', - '/apps/agent-provisioning/', - '/apps/agent-service/' + '/apps/agent-provisioning/test/', + '/apps/agent-service/test/' ] }; From e85befd4890bf8f94465268e66393880a4de2244 Mon Sep 17 00:00:00 2001 From: Mark Date: Tue, 15 Sep 2026 21:25:30 +0800 Subject: [PATCH 57/57] refactor(agent): keep provisioning hardening focused Signed-off-by: Mark --- .env.demo | 5 +- README.md | 6 +- .../src/agent-provisioning-timeout.spec.ts | 77 ---------------- .../src/agent-provisioning.service.spec.ts | 89 ++++++------------- .../src/agent-provisioning.service.ts | 85 ++++++------------ .../src/agent-service.service.spec.ts | 9 +- 6 files changed, 58 insertions(+), 213 deletions(-) delete mode 100644 apps/agent-provisioning/src/agent-provisioning-timeout.spec.ts diff --git a/.env.demo b/.env.demo index d3c79cb17..102d61508 100644 --- a/.env.demo +++ b/.env.demo @@ -176,7 +176,7 @@ KEYCLOAK_MANAGEMENT_CLIENT_ID=credeblClient KEYCLOAK_MANAGEMENT_CLIENT_SECRET= KEYCLOAK_REALM=credebl-platform -# Provide schema server URL and token. SCHEMA_FILE_SERVER_URL is required for agent provisioning. +# Provide schema server URL and token SCHEMA_FILE_SERVER_URL= SCHEMA_FILE_SERVER_TOKEN= @@ -197,9 +197,6 @@ AFJ_AGENT_ENDPOINT_PATH=/agent-provisioning/AFJ/endpoints/ # AFJ_AGENT_SPIN_UP=/apps/agent-provisioning/AFJ/scripts/start_agent.sh # AFJ_AGENT_ENDPOINT_PATH=/apps/agent-provisioning/AFJ/endpoints/ -# Provisioning timeout in milliseconds (default: 300000). Existing containers/cloud resources are not rolled back. -# AFJ_AGENT_PROVISION_TIMEOUT_MS=300000 - # Protocol used by the agent service AGENT_PROTOCOL=http diff --git a/README.md b/README.md index d3ab53477..a590a113a 100644 --- a/README.md +++ b/README.md @@ -158,11 +158,7 @@ nest start agent-service [--watch] ### Agent provisioning -Set `AFJ_AGENT_SPIN_UP`, `AFJ_AGENT_ENDPOINT_PATH`, `SCHEMA_FILE_SERVER_URL`, and `AGENT_API_KEY` before provisioning. Local and Docker scripts do not require AWS settings. `start_agent_ecs.sh` also requires the ECS settings through `FILESYSTEMID` listed in `.env.demo`; `fargate.sh` additionally requires `ECS_SUBNET_ID` and `ECS_SECURITY_GROUP_ID`. Custom scripts must validate their own additional settings. - -Deploy `agent-service` and `agent-provisioning` from the same release: ledger JSON is passed directly as a process argument, without shell escaping. Pause new provisioning requests while updating the pair. - -`AFJ_AGENT_PROVISION_TIMEOUT_MS` defaults to 300000 (five minutes). On failure or timeout, the service stops the script's process group. This does not remove containers or cloud resources already created; inspect those before retrying a failed request. +Deploy `agent-service` and `agent-provisioning` together: ledger JSON is now passed directly as a process argument. Local and Docker scripts need `SCHEMA_FILE_SERVER_URL` and `AGENT_API_KEY`; AWS settings are required only for the corresponding ECS/Fargate scripts. ## Testing diff --git a/apps/agent-provisioning/src/agent-provisioning-timeout.spec.ts b/apps/agent-provisioning/src/agent-provisioning-timeout.spec.ts deleted file mode 100644 index 741d37f05..000000000 --- a/apps/agent-provisioning/src/agent-provisioning-timeout.spec.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { mkdtempSync, writeFileSync, existsSync, rmSync } from 'node:fs'; -import { tmpdir } from 'node:os'; -import { join, relative } from 'node:path'; -import { Logger } from '@nestjs/common'; -import { AgentType } from '@credebl/enum/enum'; -import { AgentProvisioningService } from './agent-provisioning.service'; -import { IWalletProvision } from './interface/agent-provisioning.interfaces'; - -describe('Provisioning timeout', () => { - it('stops the script and its child before the child can write after a timeout', async () => { - const directory = mkdtempSync(join(tmpdir(), 'provisioning-timeout-')); - const savedEnv = { ...process.env }; - const started = join(directory, 'started'); - const completed = join(directory, 'completed'); - const childPath = join(directory, 'child.cjs'); - const scriptPath = join(directory, 'run.sh'); - writeFileSync( - childPath, - ` - const fs = require('node:fs'); - fs.writeFileSync(${JSON.stringify(started)}, 'started'); - setTimeout(() => { - if (fs.existsSync(${JSON.stringify(directory)})) { - fs.writeFileSync(${JSON.stringify(completed)}, 'completed'); - } - }, 3000); - ` - ); - writeFileSync( - scriptPath, - `#!/bin/sh -trap '' TERM -"$PROVISIONING_TEST_NODE" "$PROVISIONING_TEST_CHILD" & -wait -`, - { mode: 0o700 } - ); - process.env.PROVISIONING_TEST_NODE = process.execPath; - process.env.PROVISIONING_TEST_CHILD = childPath; - process.env.AFJ_AGENT_SPIN_UP = `/${relative(process.cwd(), scriptPath)}`; - process.env.AFJ_AGENT_ENDPOINT_PATH = '/unused/'; - process.env.AFJ_AGENT_PROVISION_TIMEOUT_MS = '1000'; - process.env.SCHEMA_FILE_SERVER_URL = 'https://schema.example.test'; - process.env.AGENT_API_KEY = 'test-key'; - const logger = { error: jest.fn() }; - const service = new AgentProvisioningService(logger as unknown as Logger); - const payload = { - orgId: 'org-test', - containerName: 'agent', - agentType: AgentType.AFJ, - externalIp: '127.0.0.1', - walletName: 'wallet', - walletPassword: 'test-key', - seed: 'seed', - webhookEndpoint: 'https://webhook.example.test', - walletStorageHost: 'postgres', - walletStoragePort: '5432', - walletStorageUser: 'user', - walletStoragePassword: 'test-key', - protocol: 'http', - tenant: false, - credoImage: 'credo:test', - indyLedger: '[]', - inboundEndpoint: '127.0.0.1' - } as IWalletProvision; - - try { - await expect(service.walletProvision(payload)).rejects.toThrow('signal SIGKILL'); - expect(existsSync(started)).toBe(true); - await new Promise((resolve) => setTimeout(resolve, 3300)); - expect(existsSync(completed)).toBe(false); - } finally { - process.env = savedEnv; - rmSync(directory, { recursive: true, force: true }); - } - }, 10_000); -}); diff --git a/apps/agent-provisioning/src/agent-provisioning.service.spec.ts b/apps/agent-provisioning/src/agent-provisioning.service.spec.ts index 73c2ecc70..2c90ca4c7 100644 --- a/apps/agent-provisioning/src/agent-provisioning.service.spec.ts +++ b/apps/agent-provisioning/src/agent-provisioning.service.spec.ts @@ -1,11 +1,11 @@ -const mockSpawn = jest.fn(); +const mockExecFile = jest.fn(); const mockReadFile = jest.fn(); const mockAccess = jest.fn(); -jest.mock('node:child_process', () => ({ spawn: mockSpawn })); +jest.mock('node:child_process', () => ({ execFile: mockExecFile })); +jest.mock('node:util', () => ({ promisify: jest.fn(() => mockExecFile) })); jest.mock('fs', () => ({ promises: { access: mockAccess, readFile: mockReadFile } })); -import { EventEmitter } from 'node:events'; import { AgentType } from '@credebl/enum/enum'; import { AgentProvisioningService } from './agent-provisioning.service'; @@ -43,18 +43,6 @@ const awsEnvironment = { }; const awsArguments = Object.values(awsEnvironment); -function closeChild(code = 0): EventEmitter { - const child = Object.assign(new EventEmitter(), { pid: undefined, kill: jest.fn() }); - queueMicrotask(() => child.emit('close', code, null)); - return child; -} - -function errorChild(error: Error): EventEmitter { - const child = Object.assign(new EventEmitter(), { pid: undefined, kill: jest.fn() }); - queueMicrotask(() => child.emit('error', error)); - return child; -} - describe('AgentProvisioningService', () => { const logger = { log: jest.fn(), error: jest.fn() }; const service = new AgentProvisioningService(logger as never); @@ -72,7 +60,7 @@ describe('AgentProvisioningService', () => { for (const name of Object.keys(awsEnvironment)) { delete process.env[name]; } - mockSpawn.mockImplementation(() => closeChild()); + mockExecFile.mockResolvedValue({ stdout: '', stderr: '' }); mockReadFile.mockResolvedValue('{"CONTROLLER_ENDPOINT":"https://agent.example"}'); }); afterAll(() => { @@ -83,18 +71,21 @@ describe('AgentProvisioningService', () => { 'provisions with local %s using only common configuration and positional placeholders', async (script) => { process.env.AFJ_AGENT_SPIN_UP = `/apps/agent-provisioning/AFJ/scripts/${script}`; - await expect(service.walletProvision(payload)).resolves.toEqual({ agentEndPoint: 'https://agent.example' }); - const [[scriptPath, args, options]] = mockSpawn.mock.calls; + const walletPassword = 'secret; $(echo injected)'; + await expect(service.walletProvision({ ...payload, walletPassword })).resolves.toEqual({ + agentEndPoint: 'https://agent.example' + }); + const [[scriptPath, args, options]] = mockExecFile.mock.calls; expect(scriptPath).toContain(script); + expect(args[3]).toBe(walletPassword); + expect(options.shell).toBeUndefined(); expect(args).toHaveLength(27); expect(args.slice(16, 18)).toEqual(['https://schema.example', 'agent-key']); expect(args.slice(18)).toEqual(Array(9).fill('')); expect(options).toEqual( expect.objectContaining({ timeout: 300000, - detached: true, - killSignal: 'SIGKILL', - stdio: 'ignore' + maxBuffer: 1024 * 1024 }) ); } @@ -109,7 +100,7 @@ describe('AgentProvisioningService', () => { ]; const indyLedger = JSON.stringify(ledgers); await service.walletProvision({ ...payload, indyLedger }); - const [[, args]] = mockSpawn.mock.calls; + const [[, args]] = mockExecFile.mock.calls; expect(args[14]).toBe(indyLedger); expect(JSON.parse(args[14])).toEqual(ledgers); expect(args[15]).toBe(payload.inboundEndpoint); @@ -121,90 +112,64 @@ describe('AgentProvisioningService', () => { delete process.env.ECS_SUBNET_ID; delete process.env.ECS_SECURITY_GROUP_ID; await service.walletProvision(payload); - const [[, args]] = mockSpawn.mock.calls; + const [[, args]] = mockExecFile.mock.calls; expect(args).toHaveLength(27); expect(args.slice(18, 25)).toEqual(awsArguments.slice(0, 7)); expect(args.slice(25)).toEqual(['', '']); - mockSpawn.mockClear(); + mockExecFile.mockClear(); delete process.env.FILESYSTEMID; await expect(service.walletProvision(payload)).rejects.toThrow('FILESYSTEMID'); - expect(mockSpawn).not.toHaveBeenCalled(); + expect(mockExecFile).not.toHaveBeenCalled(); }); it('requires all Fargate configuration in the original argument positions', async () => { process.env.AFJ_AGENT_SPIN_UP = '/apps/agent-provisioning/AFJ/scripts/fargate.sh'; Object.assign(process.env, awsEnvironment); await service.walletProvision(payload); - const [[, args]] = mockSpawn.mock.calls; + const [[, args]] = mockExecFile.mock.calls; expect(args).toHaveLength(27); expect(args.slice(18)).toEqual(awsArguments); - mockSpawn.mockClear(); + mockExecFile.mockClear(); delete process.env.ECS_SECURITY_GROUP_ID; await expect(service.walletProvision(payload)).rejects.toThrow('ECS_SECURITY_GROUP_ID'); - expect(mockSpawn).not.toHaveBeenCalled(); + expect(mockExecFile).not.toHaveBeenCalled(); }); it('rejects unsafe organization identifiers before executing a script', async () => { await expect(service.walletProvision({ ...payload, orgId: '../another-org' })).rejects.toThrow( 'orgId contains unsafe characters' ); - expect(mockSpawn).not.toHaveBeenCalled(); + expect(mockExecFile).not.toHaveBeenCalled(); }); it.each([ - ['', 'agent'], - ['____', 'agent'], ['___agent__name___', 'agent__name'], - ['-agent-', '-agent-'], - [`a${'_'.repeat(100_000)}`, 'a'], ['Crédit Agricole, Inc.', 'Credit_Agricole_Inc'], - [`a${'_'.repeat(100_000)}b`, `a${'_'.repeat(127)}`], - [`${'a'.repeat(127)}_b`, `${'a'.repeat(127)}_`] + [`a${'_'.repeat(100_000)}b`, `a${'_'.repeat(127)}`] ])('preserves container name normalization (case %#)', async (containerName, expected) => { await service.walletProvision({ ...payload, containerName }); - const [[, args]] = mockSpawn.mock.calls; + const [[, args]] = mockExecFile.mock.calls; expect(args[10]).toBe(expected); expect(mockReadFile).toHaveBeenCalledWith(expect.stringContaining(`org-123_${expected}.json`), 'utf8'); }); - it('rejects an invalid endpoint JSON document', async () => { - mockReadFile.mockResolvedValue('not JSON'); - await expect(service.walletProvision(payload)).rejects.toThrow('Invalid JSON in agent endpoint file'); - }); - - it.each([{}, 1, [], '', ' '])('rejects an invalid controller endpoint', async (endpoint) => { - mockReadFile.mockResolvedValue(JSON.stringify({ CONTROLLER_ENDPOINT: endpoint })); - await expect(service.walletProvision(payload)).rejects.toThrow('Missing CONTROLLER_ENDPOINT'); - }); - it('rejects a non-object endpoint document', async () => { mockReadFile.mockResolvedValue('null'); await expect(service.walletProvision(payload)).rejects.toThrow('Missing CONTROLLER_ENDPOINT'); }); - it('uses the configured timeout', async () => { - process.env.AFJ_AGENT_PROVISION_TIMEOUT_MS = '600000'; - await service.walletProvision(payload); - const [[, , options]] = mockSpawn.mock.calls; - expect(options.timeout).toBe(600000); - }); - it('reports a script exit code without logging script output or credentials', async () => { - mockSpawn.mockImplementation(() => closeChild(17)); + mockExecFile.mockRejectedValue( + Object.assign(new Error('wallet-secret'), { code: 17, stdout: 'stdout-secret', stderr: 'stderr-secret' }) + ); await expect(service.walletProvision(payload)).rejects.toThrow('Agent provisioning script failed'); expect(mockReadFile).not.toHaveBeenCalled(); const logs = JSON.stringify(logger.error.mock.calls); expect(logs).toContain('exit code 17'); expect(logs).not.toContain('wallet-secret'); - }); - - it('reports ENOENT without exposing a spawn error message', async () => { - const failure = Object.assign(new Error('spawn wallet-secret ENOENT'), { code: 'ENOENT' }); - mockSpawn.mockImplementation(() => errorChild(failure)); - await expect(service.walletProvision(payload)).rejects.toThrow('ENOENT'); - expect(JSON.stringify(logger.error.mock.calls)).not.toContain('wallet-secret'); - expect(mockReadFile).not.toHaveBeenCalled(); + expect(logs).not.toContain('stdout-secret'); + expect(logs).not.toContain('stderr-secret'); }); }); diff --git a/apps/agent-provisioning/src/agent-provisioning.service.ts b/apps/agent-provisioning/src/agent-provisioning.service.ts index 556ef67c8..790d907ea 100644 --- a/apps/agent-provisioning/src/agent-provisioning.service.ts +++ b/apps/agent-provisioning/src/agent-provisioning.service.ts @@ -6,9 +6,11 @@ import { Injectable, Logger, NotFoundException } from '@nestjs/common'; import { AgentType } from '@credebl/enum/enum'; import { IWalletProvision } from './interface/agent-provisioning.interfaces'; import { RpcException } from '@nestjs/microservices'; -import { spawn } from 'node:child_process'; +import { execFile } from 'node:child_process'; +import { promisify } from 'node:util'; import { basename } from 'node:path'; +const execFileAsync = promisify(execFile); const SAFE_FILE_IDENTIFIER = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/; const DEFAULT_AGENT_PROVISION_TIMEOUT_MS = 300_000; // Keep the script's positional arguments fixed, including unused cloud settings. @@ -100,70 +102,37 @@ export class AgentProvisioningService { spinUpScript: string, provisionTimeoutMs: number ): Promise { - let childPid: number | undefined; - try { - const child = spawn( - `${process.cwd()}${spinUpScript}`, - [ - payload.orgId, - payload.externalIp, - payload.walletName, - payload.walletPassword, - payload.seed, - payload.webhookEndpoint, - payload.walletStorageHost, - payload.walletStoragePort, - payload.walletStorageUser, - payload.walletStoragePassword, - safeContainerName, - payload.protocol, - String(payload.tenant), - payload.credoImage, - payload.indyLedger, - payload.inboundEndpoint, - ...PROVISIONING_ENVIRONMENT.map((name) => process.env[name] || '') - ], - { detached: true, timeout: provisionTimeoutMs, killSignal: 'SIGKILL', stdio: 'ignore', env: process.env } - ); - childPid = child.pid; - await new Promise((resolve, reject) => { - child.once('error', reject); - child.once('close', (code, signal) => { - if (0 === code) { - resolve(); - } else { - reject(Object.assign(new Error('Provisioning process failed'), { code, signal })); - } - }); - }); - } catch (error) { - this.stopProvisioningProcessGroup(childPid); + await execFileAsync( + `${process.cwd()}${spinUpScript}`, + [ + payload.orgId, + payload.externalIp, + payload.walletName, + payload.walletPassword, + payload.seed, + payload.webhookEndpoint, + payload.walletStorageHost, + payload.walletStoragePort, + payload.walletStorageUser, + payload.walletStoragePassword, + safeContainerName, + payload.protocol, + String(payload.tenant), + payload.credoImage, + payload.indyLedger, + payload.inboundEndpoint, + ...PROVISIONING_ENVIRONMENT.map((name) => process.env[name] || '') + ], + { timeout: provisionTimeoutMs, maxBuffer: 1024 * 1024 } + ).catch((error) => { throw new Error(`Agent provisioning script failed${this.formatScriptFailure(error)}`); - } - } - - private stopProvisioningProcessGroup(pid: number | undefined): void { - if ('win32' === process.platform || !pid || !Number.isInteger(pid) || 0 >= pid) { - return; - } - try { - // Provisioning uses POSIX shell scripts; detached gives each run its own group. - process.kill(-pid, 'SIGKILL'); - } catch (error) { - if ('ESRCH' !== (error as NodeJS.ErrnoException).code) { - this.logger.error('Failed to terminate provisioning process group'); - } - } + }); } private formatScriptFailure(error: { code?: unknown; signal?: unknown }): string { if ('number' === typeof error?.code) { return ` (exit code ${error.code})`; } - const safeCodes = ['ENOENT', 'EACCES', 'ENOEXEC', 'E2BIG']; - if ('string' === typeof error?.code && safeCodes.includes(error.code)) { - return ` (${error.code})`; - } if ('string' === typeof error?.signal) { return ` (signal ${error.signal})`; } diff --git a/apps/agent-service/src/agent-service.service.spec.ts b/apps/agent-service/src/agent-service.service.spec.ts index 51b5d5711..8809c6ee3 100644 --- a/apps/agent-service/src/agent-service.service.spec.ts +++ b/apps/agent-service/src/agent-service.service.spec.ts @@ -43,17 +43,12 @@ describe('AgentServiceService.prepareWalletProvisionPayload', () => { JSON.stringify({ txn: { data: { alias: 'Node Two' } } }) ].join('\n'); const indyNamespace = 'test:local'; - const payload = await provision([{ poolConfig: genesisTransactions, indyNamespace }]); - expect(JSON.parse(payload.indyLedger)).toEqual([{ genesisTransactions, indyNamespace }]); - }); - - it('preserves multiple ledgers in their input order', async () => { const payload = await provision([ - { poolConfig: 'first genesis', indyNamespace: 'test:first' }, + { poolConfig: genesisTransactions, indyNamespace }, { poolConfig: 'second genesis', indyNamespace: 'test:second' } ]); expect(JSON.parse(payload.indyLedger)).toEqual([ - { genesisTransactions: 'first genesis', indyNamespace: 'test:first' }, + { genesisTransactions, indyNamespace }, { genesisTransactions: 'second genesis', indyNamespace: 'test:second' } ]); });