From 0b575f29af117d48a243e4256f7aab49f4090296 Mon Sep 17 00:00:00 2001 From: Thushani Jayasekera Date: Fri, 3 Apr 2026 22:23:56 +0530 Subject: [PATCH 1/2] Enhance key manager handling by fetching production and sandbox key managers in parallel --- sample_config.json | 7 +- .../applicationsContentController.js | 116 ++++++++++-------- .../application/partials/manage-keys.hbs | 4 + src/services/adminService.js | 19 +-- 4 files changed, 88 insertions(+), 58 deletions(-) diff --git a/sample_config.json b/sample_config.json index 7932937c9..0fe3c3fb6 100644 --- a/sample_config.json +++ b/sample_config.json @@ -26,7 +26,12 @@ "url": "https://sts.preview-dv.choreo.dev/api/am/devportal/v2", "graphqlURL": "https://app.preview-dv.choreo.dev/graphql", "disableCertValidation": true, - "pathToCertificate": "./resources/security/client-truststore.pem" + "pathToCertificate": "./resources/security/client-truststore.pem", + "residentKeyManager": { + "tokenEndpoint": "https://sts.choreo.dev/oauth2/token", + "authorizeEndpoint": "https://sts.choreo.dev/oauth2/authorize", + "revokeEndpoint": "https://sts.choreo.dev/oauth2/revoke" + } }, "aiSDKService": { "url": "http://localhost:5001", diff --git a/src/controllers/applicationsContentController.js b/src/controllers/applicationsContentController.js index 34e21fe77..74d5736ce 100644 --- a/src/controllers/applicationsContentController.js +++ b/src/controllers/applicationsContentController.js @@ -173,35 +173,24 @@ const loadApplicationData = async (req, orgName, applicationId, viewName) => { api.subscriptionPolicyDetails = await util.appendSubscriptionPlanDetails(orgID, api.subscriptionPolicies); })); - let kMmetaData = await getAPIMKeyManagers(req); + // Fetch prod and sandbox key managers in parallel + const [rawProdKeyManagers, rawSandboxKeyManagers] = await Promise.all([ + getAPIMKeyManagers(req, 'prod'), + getAPIMKeyManagers(req, 'sandbox') + ]); - // Ensure kMmetaData is an array before filtering - if (!Array.isArray(kMmetaData)) { - kMmetaData = []; - } + const prodKeyManagers = filterKeyManagers(rawProdKeyManagers, '_prod'); + const sandboxKeyManagers = filterKeyManagers(rawSandboxKeyManagers, '_sandbox'); - kMmetaData = kMmetaData.filter(keyManager => keyManager.enabled); - - // TODO: Instead of using priority-based filtering, we should identify the key manager - // configured for the production environment from the Bijira console configuration. - // This temporary priority-based approach should be replaced with a proper configuration-based selection. - if (Array.isArray(kMmetaData) && kMmetaData.length > 1) { - kMmetaData = kMmetaData.filter(keyManager => - keyManager.name.includes("_internal_key_manager_") || - (!kMmetaData.some(km => km.name.includes("_internal_key_manager_")) && keyManager.name.includes("Resident Key Manager")) || - (!kMmetaData.some(km => km.name.includes("_internal_key_manager_") || km.name.includes("Resident Key Manager")) && keyManager.name.includes("_appdev_sts_key_manager_") && keyManager.name.endsWith("_prod")) - ); - } + // Enrich all key managers with endpoints, grant types, and configuration + await Promise.all([ + ...prodKeyManagers.map(enrichKeyManager), + ...sandboxKeyManagers.map(enrichKeyManager) + ]); - for (const keyManager of kMmetaData) { - if (keyManager.name === 'Resident Key Manager') { - keyManager.tokenEndpoint = 'https://sts.choreo.dev/oauth2/token'; - keyManager.authorizeEndpoint = 'https://sts.choreo.dev/oauth2/authorize'; - keyManager.revokeEndpoint = 'https://sts.choreo.dev/oauth2/revoke'; - } - keyManager.availableGrantTypes = await mapGrants(keyManager.availableGrantTypes); - keyManager.applicationConfiguration = await mapDefaultValues(keyManager.applicationConfiguration); - } + // Tag each key manager with its environment for template-side filtering + prodKeyManagers.forEach(km => { km.devPortalAppEnv = 'PROD'; }); + sandboxKeyManagers.forEach(km => { km.devPortalAppEnv = 'SANDBOX'; }); let productionKeys = []; let sandboxKeys = []; @@ -231,30 +220,24 @@ const loadApplicationData = async (req, orgName, applicationId, viewName) => { return keyData; }) || []; - kMmetaData.forEach(keyManager => { - productionKeys.forEach(productionKey => { - if (productionKey.keyManager === keyManager.name) { - keyManager.productionKeys = productionKey; - } + // Match production keys to prod key managers; each prod KM only holds production applicationKeys + prodKeyManagers.forEach(km => { + productionKeys.forEach(pk => { + if (pk.keyManager === km.name) km.productionKeys = pk; }); - sandboxKeys.forEach(sandboxKey => { - if (sandboxKey.keyManager === keyManager.name) { - keyManager.sandboxKeys = sandboxKey; - } + km.applicationKeys = [{ keys: km.productionKeys || {}, keyType: constants.KEY_TYPE.PRODUCTION }]; + }); + + // Match sandbox keys to sandbox key managers; each sandbox KM only holds sandbox applicationKeys + sandboxKeyManagers.forEach(km => { + sandboxKeys.forEach(sk => { + if (sk.keyManager === km.name) km.sandboxKeys = sk; }); - // Build applicationKeys per keyManager with single objects (not arrays) - keyManager.applicationKeys = [ - { - keys: keyManager.productionKeys || {}, - keyType: 'PRODUCTION' - }, - { - keys: keyManager.sandboxKeys || {}, - keyType: 'SANDBOX' - } - ]; + km.applicationKeys = [{ keys: km.sandboxKeys || {}, keyType: constants.KEY_TYPE.SANDBOX }]; }); + const kMmetaData = [...prodKeyManagers, ...sandboxKeyManagers]; + let subscriptionScopes = []; if (applicationReference) { let cpApplication = await getAPIMApplication(req, applicationReference); @@ -776,9 +759,44 @@ async function getAPIMApplication(req, applicationId) { return responseData; } -async function getAPIMKeyManagers(req) { - const responseData = await invokeApiRequest(req, 'GET', controlPlaneUrl + '/key-managers?devPortalAppEnv=prod', null, null); - return responseData.list; +/** + * Selects the appropriate key manager from a list using a priority-based approach: + * 1. Internal key manager (highest priority) + * 2. Resident Key Manager + * 3. AppDev STS key manager matching the given environment suffix (e.g. '_prod' or '_sandbox') + */ +function filterKeyManagers(keyManagers, envSuffix) { + if (!Array.isArray(keyManagers) || keyManagers.length === 0) return []; + + const enabled = keyManagers.filter(km => km.enabled); + if (enabled.length <= 1) return enabled; + + const hasInternal = enabled.some(km => km.name.includes(constants.KEY_MANAGERS.INTERNAL_KEY_MANAGER)); + const hasResident = enabled.some(km => km.name.includes(constants.KEY_MANAGERS.RESIDENT_KEY_MANAGER)); + + return enabled.filter(km => + km.name.includes(constants.KEY_MANAGERS.INTERNAL_KEY_MANAGER) || + (!hasInternal && km.name.includes(constants.KEY_MANAGERS.RESIDENT_KEY_MANAGER)) || + (!hasInternal && !hasResident && + km.name.includes(constants.KEY_MANAGERS.APP_DEV_STS_KEY_MANAGER) && + km.name.endsWith(envSuffix)) + ); +} + +async function enrichKeyManager(keyManager) { + if (keyManager.name === constants.KEY_MANAGERS.RESIDENT_KEY_MANAGER) { + const residentKMConfig = config.controlPlane?.residentKeyManager || {}; + keyManager.tokenEndpoint = residentKMConfig.tokenEndpoint || 'https://sts.choreo.dev/oauth2/token'; + keyManager.authorizeEndpoint = residentKMConfig.authorizeEndpoint || 'https://sts.choreo.dev/oauth2/authorize'; + keyManager.revokeEndpoint = residentKMConfig.revokeEndpoint || 'https://sts.choreo.dev/oauth2/revoke'; + } + keyManager.availableGrantTypes = await mapGrants(keyManager.availableGrantTypes); + keyManager.applicationConfiguration = await mapDefaultValues(keyManager.applicationConfiguration); +} + +async function getAPIMKeyManagers(req, env) { + const responseData = await invokeApiRequest(req, 'GET', `${controlPlaneUrl}/key-managers?devPortalAppEnv=${env}`, null, null); + return Array.isArray(responseData.list) ? responseData.list : []; } async function getAPIDetails(req, apiId) { diff --git a/src/pages/application/partials/manage-keys.hbs b/src/pages/application/partials/manage-keys.hbs index 29ea67d5f..61e129da0 100644 --- a/src/pages/application/partials/manage-keys.hbs +++ b/src/pages/application/partials/manage-keys.hbs @@ -44,6 +44,7 @@
{{#if keyManagersMetadata}} {{#each keyManagersMetadata}} + {{#if (eq devPortalAppEnv 'PROD')}} {{#in name values="_internal_key_manager_,Resident Key Manager,_appdev_sts_key_manager_"}} {{#if enabled}} {{#let "keys" productionKeys}} @@ -259,6 +260,7 @@ + {{/if}} {{/each}} {{else}}
@@ -609,6 +611,7 @@
{{#if keyManagersMetadata}} {{#each keyManagersMetadata}} + {{#if (eq devPortalAppEnv 'SANDBOX')}} {{#in name values="_internal_key_manager_,Resident Key Manager,_appdev_sts_key_manager_"}} {{#if enabled}} {{#let "keys" sandboxKeys}} @@ -826,6 +829,7 @@ + {{/if}} {{/each}} {{else}}
diff --git a/src/services/adminService.js b/src/services/adminService.js index 85ad62080..378e91db0 100644 --- a/src/services/adminService.js +++ b/src/services/adminService.js @@ -1597,16 +1597,19 @@ const createAppKeyMappingOnBehalfOfUser = async (cpAppID, keymanager, clientId, } const getAPIMKeyManagersBehalfOfUser = async (cpOrgId, patToken) => { - - let headers = { + const headers = { 'Content-Type': 'application/json', Authorization: `Bearer ${patToken}` - } - let url = `${controlPlaneGwUrl}/key-managers?devPortalAppEnv=prod`; - const keymanagersResponse = await util.apiRequest('GET', url, headers, null, cpOrgId); - - return keymanagersResponse.data.list; -} + }; + const [prodResponse, sandboxResponse] = await Promise.all([ + util.apiRequest('GET', `${controlPlaneGwUrl}/key-managers?devPortalAppEnv=prod`, headers, null, cpOrgId), + util.apiRequest('GET', `${controlPlaneGwUrl}/key-managers?devPortalAppEnv=sandbox`, headers, null, cpOrgId) + ]); + return [ + ...(prodResponse.data.list || []), + ...(sandboxResponse.data.list || []) + ]; +}; const createCPApplication = async (req, cpApplicationName) => { logger.info('Creating control plane application', { From 5617f0c11ade348097589ceb118e324574a681c8 Mon Sep 17 00:00:00 2001 From: Thushani Jayasekera Date: Sat, 4 Apr 2026 13:38:34 +0530 Subject: [PATCH 2/2] Refactor key manager handling to use Promise.allSettled for independent error handling --- .../applicationsContentController.js | 18 ++++++++++++++++-- src/pages/application/partials/manage-keys.hbs | 4 ++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/controllers/applicationsContentController.js b/src/controllers/applicationsContentController.js index 74d5736ce..fd6cd6eaa 100644 --- a/src/controllers/applicationsContentController.js +++ b/src/controllers/applicationsContentController.js @@ -173,11 +173,13 @@ const loadApplicationData = async (req, orgName, applicationId, viewName) => { api.subscriptionPolicyDetails = await util.appendSubscriptionPlanDetails(orgID, api.subscriptionPolicies); })); - // Fetch prod and sandbox key managers in parallel - const [rawProdKeyManagers, rawSandboxKeyManagers] = await Promise.all([ + // Fetch prod and sandbox key managers in parallel; each falls back to [] independently + const [prodResult, sandboxResult] = await Promise.allSettled([ getAPIMKeyManagers(req, 'prod'), getAPIMKeyManagers(req, 'sandbox') ]); + const rawProdKeyManagers = prodResult.status === 'fulfilled' ? prodResult.value : []; + const rawSandboxKeyManagers = sandboxResult.status === 'fulfilled' ? sandboxResult.value : []; const prodKeyManagers = filterKeyManagers(rawProdKeyManagers, '_prod'); const sandboxKeyManagers = filterKeyManagers(rawSandboxKeyManagers, '_sandbox'); @@ -237,6 +239,8 @@ const loadApplicationData = async (req, orgName, applicationId, viewName) => { }); const kMmetaData = [...prodKeyManagers, ...sandboxKeyManagers]; + const hasProdKeyManagers = prodKeyManagers.length > 0; + const hasSandboxKeyManagers = sandboxKeyManagers.length > 0; let subscriptionScopes = []; if (applicationReference) { @@ -400,6 +404,8 @@ const loadApplicationData = async (req, orgName, applicationId, viewName) => { orgID, applicationList, keyManagersMetadata: kMmetaData, + hasProdKeyManagers, + hasSandboxKeyManagers, subAPIs: subList, subAPIsForApplicationKeys, platformSubscriptionsForApplicationKeys: [], @@ -512,6 +518,8 @@ const loadApplication = async (req, res) => { templateContent = { applicationMetadata: metaData, keyManagersMetadata: kMmetaData, + hasProdKeyManagers: kMmetaData.some(km => km.devPortalAppEnv === 'PROD'), + hasSandboxKeyManagers: kMmetaData.some(km => km.devPortalAppEnv === 'SANDBOX'), baseUrl: baseURLDev + viewName, features: { sdkGeneration: config.features?.sdkGeneration?.enabled || false @@ -530,6 +538,8 @@ const loadApplication = async (req, res) => { subscriptionCount: data.subAPIs.length }, keyManagersMetadata: kMmetaData, + hasProdKeyManagers: data.hasProdKeyManagers, + hasSandboxKeyManagers: data.hasSandboxKeyManagers, baseUrl: '/' + orgName + constants.ROUTE.VIEWS_PATH + viewName, subAPIs: data.subAPIs, nonSubAPIs: data.nonSubAPIs, @@ -607,6 +617,8 @@ const loadApplicationKeys = async (req, res) => { templateContent = { applicationMetadata: metaData, keyManagersMetadata: kMmetaData, + hasProdKeyManagers: kMmetaData.some(km => km.devPortalAppEnv === 'PROD'), + hasSandboxKeyManagers: kMmetaData.some(km => km.devPortalAppEnv === 'SANDBOX'), baseUrl: baseURLDev + viewName, productionKeys: [], sandboxKeys: [], @@ -635,6 +647,8 @@ const loadApplicationKeys = async (req, res) => { subscriptionCount: data.subAPIs.length }, keyManagersMetadata: kMmetaData, + hasProdKeyManagers: data.hasProdKeyManagers, + hasSandboxKeyManagers: data.hasSandboxKeyManagers, baseUrl: '/' + orgName + constants.ROUTE.VIEWS_PATH + viewName, subAPIs: data.subAPIs, nonSubAPIs: data.nonSubAPIs, diff --git a/src/pages/application/partials/manage-keys.hbs b/src/pages/application/partials/manage-keys.hbs index 61e129da0..a3d7debbb 100644 --- a/src/pages/application/partials/manage-keys.hbs +++ b/src/pages/application/partials/manage-keys.hbs @@ -42,7 +42,7 @@
- {{#if keyManagersMetadata}} + {{#if hasProdKeyManagers}} {{#each keyManagersMetadata}} {{#if (eq devPortalAppEnv 'PROD')}} {{#in name values="_internal_key_manager_,Resident Key Manager,_appdev_sts_key_manager_"}} @@ -609,7 +609,7 @@
- {{#if keyManagersMetadata}} + {{#if hasSandboxKeyManagers}} {{#each keyManagersMetadata}} {{#if (eq devPortalAppEnv 'SANDBOX')}} {{#in name values="_internal_key_manager_,Resident Key Manager,_appdev_sts_key_manager_"}}