From 9fdd296d70a6f302ef25038cd0a6b23df6ef6089 Mon Sep 17 00:00:00 2001 From: sujitaw Date: Tue, 16 Jun 2026 13:57:25 +0530 Subject: [PATCH 01/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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 0ae3dc4d6d5dcb1b4179c1e27bf4e954e7c37122 Mon Sep 17 00:00:00 2001 From: sujitaw Date: Tue, 23 Jun 2026 17:37:04 +0530 Subject: [PATCH 08/18] 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 09/18] 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 10/18] 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 11/18] 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 12/18] 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 13/18] 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 14/18] 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 15/18] 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 16/18] 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 17/18] 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 18/18] 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}`); } }