From af95d1b1336b17b9fb3a636b5ddea27e8836fe31 Mon Sep 17 00:00:00 2001 From: Tipu_Singh Date: Tue, 3 Mar 2026 15:56:09 +0530 Subject: [PATCH 1/2] feat: added x509 certificates logic with auth Signed-off-by: Tipu_Singh --- .env.sample | 10 ++ src/cliAgent.ts | 7 +- src/controllers/auth/AuthController.ts | 64 ++++++++++ src/routes/routes.ts | 161 +++++++++++++++++++++++++ src/routes/swagger.json | 146 ++++++++++++++++++++++ src/utils/helpers.ts | 138 +++++++++++++++++++++ src/utils/oid4vc-agent.ts | 104 ++++++++++++++-- 7 files changed, 620 insertions(+), 10 deletions(-) create mode 100644 src/controllers/auth/AuthController.ts diff --git a/.env.sample b/.env.sample index 8bf4e733..3983fe58 100644 --- a/.env.sample +++ b/.env.sample @@ -42,6 +42,16 @@ INDICIO_TEST_GENESIS=`{"reqSignature":{},"txn":{"data":{"data":{"alias":"OpsNode {"reqSignature":{},"txn":{"data":{"data":{"alias":"lorica-identity-node1","blskey":"wUh24sVCQ8PHDgSb343g2eLxjD5vwxsrETfuV2sbwMNnYon9nhbaK5jcWTekvXtyiwxHxuiCCoZwKS97MQEAeC2oLbbMeKjYm212QwSnm7aKLEqTStXht35VqZvZLT7Q3mPQRYLjMGixdn4ocNHrBTMwPUQYycEqwaHWgE1ncDueXY","blskey_pop":"R2sMwF7UW6AaD4ALa1uB1YVPuP6JsdJ7LsUoViM9oySFqFt34C1x1tdHDysS9wwruzaaEFui6xNPqJ8eu3UBqcFKkoWhdsMqCALwe63ytxPwvtLtCffJLhHAcgrPC7DorXYdqhdG2cevdqc5oqFEAaKoFDBf12p5SsbbM4PYWCmVCb","client_ip":"35.225.220.151","client_port":"9702","node_ip":"35.224.26.110","node_port":"9701","services":["VALIDATOR"]},"dest":"k74ZsZuUaJEcB8RRxMwkCwdE5g1r9yzA3nx41qvYqYf"},"metadata":{"from":"Ex6hzsJFYzNJ7kzbfncNeU"},"type":"0"},"txnMetadata":{"seqNo":6,"txnId":"6880673ce4ae4a2352f103d2a6ae20469dd070f2027283a1da5e62a64a59d688"},"ver":"1"} {"reqSignature":{},"txn":{"data":{"data":{"alias":"cysecure-itn","blskey":"GdCvMLkkBYevRFi93b6qaj9G2u1W6Vnbg8QhRD1chhrWR8vRE8x9x7KXVeUBPFf6yW5qq2JCfA2frc8SGni2RwjtTagezfwAwnorLhVJqS5ZxTi4pgcw6smebnt4zWVhTkh6ugDHEypHwNQBcw5WhBZcEJKgNbyVLnHok9ob6cfr3u","blskey_pop":"RbH9mY7M5p3UB3oj4sT1skYwMkxjoUnja8eTYfcm83VcNbxC9zR9pCiRhk4q1dJT3wkDBPGNKnk2p83vaJYLcgMuJtzoWoJAWAxjb3Mcq8Agf6cgQpBuzBq2uCzFPuQCAhDS4Kv9iwA6FsRnfvoeFTs1hhgSJVxQzDWMVTVAD9uCqu","client_ip":"35.169.19.171","client_port":"9702","node_ip":"54.225.56.21","node_port":"9701","services":["VALIDATOR"]},"dest":"4ETBDmHzx8iDQB6Xygmo9nNXtMgq9f6hxGArNhQ6Hh3u"},"metadata":{"from":"uSXXXEdBicPHMMhr3ddNF"},"type":"0"},"txnMetadata":{"seqNo":7,"txnId":"3c21718b07806b2f193b35953dda5b68b288efd551dce4467ce890703d5ba549"},"ver":"1"}` +PLATFORM_BASE_URL= #CREDEBL BASE URL +#if the agent is dedicated +PLATFORM_DEDICATED_CLIENT_ID= +PLATFORM_DEDICATED_CLIENT_SECRET= +#If the agent is shared +PLATFORM_SHARED_AGENT_CLIENT_ID= +PLATFORM_SHARED_AGENT_CLIENT_SECRET= +#Trust service url to fetch trusted certificates for TLS pinning +TRUST_SERVICE_URL= + APP_URL= AGENT_HTTP_URL= HOLDER_REDIRECT= diff --git a/src/cliAgent.ts b/src/cliAgent.ts index 5e92cd16..54d93054 100644 --- a/src/cliAgent.ts +++ b/src/cliAgent.ts @@ -274,9 +274,12 @@ const getModules = ( }), openId4VcHolderModule: new OpenId4VcHolderModule(), x509: new X509Module({ - getTrustedCertificatesForVerification: async (_agentContext, { certificateChain, verification }) => { + getTrustedCertificatesForVerification: async (agentContext, { certificateChain: _certificateChain, verification: _verification }) => { //TODO: We need to trust the certificate tenant wise, for that we need to fetch those details from platform - const certs: string[] = await getTrustedCerts() + const tenantId = agentContext.contextCorrelationId + console.log('[getTrustedCertificatesForVerification] tenantId from agentContext:', tenantId) + const certs: string[] = await getTrustedCerts(tenantId) + return certs }, }), diff --git a/src/controllers/auth/AuthController.ts b/src/controllers/auth/AuthController.ts new file mode 100644 index 00000000..2a26cacb --- /dev/null +++ b/src/controllers/auth/AuthController.ts @@ -0,0 +1,64 @@ +import axios from 'axios' +import { Request as Req } from 'express' +import { Body, Controller, Get, Path, Post, Request, Route, Tags } from 'tsoa' +import { injectable } from 'tsyringe' + +import { BadRequestError } from '../../errors' +import { fetchDedicatedX509Certificates, fetchSharedAgentX509Certificates } from '../../utils/helpers' +import { getTrustedCerts } from '../../utils/oid4vc-agent' + +interface OrgTokenRequest { + clientId: string + clientSecret: string +} + +interface OrgTokenResponse { + token: string +} + +@Tags('Auth') +@Route('/v1/orgs') +@injectable() +export class AuthController extends Controller { + /** + * Generate an organization token by forwarding credentials to the platform + */ + // @Security('jwt', [SCOPES.UNPROTECTED]) + @Post('/{orgId}/token') + public async getOrgToken( + @Request() _request: Req, + @Path('orgId') orgId: string, + @Body() body: OrgTokenRequest, + ): Promise { + const platformBaseUrl = process.env.PLATFORM_BASE_URL + if (!platformBaseUrl) { + throw new BadRequestError('PLATFORM_BASE_URL is not configured') + } + + const response = await axios.post( + `${platformBaseUrl}/v1/orgs/${orgId}/token`, + { clientId: body.clientId, clientSecret: body.clientSecret }, + { headers: { 'Content-Type': 'application/json', accept: 'application/json' } }, + ) + + return response.data + } +// TODO: Remove these test endpoints after manual testing is done + @Get('/test/dedicated-x509-certificates') + public async testFetchDedicatedX509Certificates(@Request() _request: Req): Promise { + return fetchDedicatedX509Certificates() + } + + @Get('/test/shared-agent-x509-certificates') + public async testFetchSharedAgentX509Certificates(@Request() _request: Req): Promise { + return fetchSharedAgentX509Certificates() + } + + /** + * [TEMP] Manually trigger getTrustedCerts to test agent type detection and trust list fetch + */ + @Get('/test/trusted-certs') + public async testGetTrustedCerts(@Request() _request: Req): Promise { + return getTrustedCerts() + } +} diff --git a/src/routes/routes.ts b/src/routes/routes.ts index a504929e..496f0dbe 100644 --- a/src/routes/routes.ts +++ b/src/routes/routes.ts @@ -24,6 +24,8 @@ import { ConnectionController } from './../controllers/didcomm/connections/Conne // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa import { DidController } from './../controllers/did/DidController'; // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { AuthController } from './../controllers/auth/AuthController'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa import { AgentController } from './../controllers/agent/AgentController'; // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa import { VerifierController } from './../controllers/openid4vc/verifiers/verifier.Controller'; @@ -1330,6 +1332,23 @@ const models: TsoaRoute.Models = { "type": {"ref":"Record_string.unknown_","validators":{}}, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "OrgTokenResponse": { + "dataType": "refObject", + "properties": { + "token": {"dataType":"string","required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "OrgTokenRequest": { + "dataType": "refObject", + "properties": { + "clientId": {"dataType":"string","required":true}, + "clientSecret": {"dataType":"string","required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "AgentInfo": { "dataType": "refObject", "properties": { @@ -3618,6 +3637,148 @@ export function RegisterRoutes(app: Router) { } }); // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsAuthController_getOrgToken: Record = { + _request: {"in":"request","name":"_request","required":true,"dataType":"object"}, + orgId: {"in":"path","name":"orgId","required":true,"dataType":"string"}, + body: {"in":"body","name":"body","required":true,"ref":"OrgTokenRequest"}, + }; + app.post('/v1/orgs/:orgId/token', + ...(fetchMiddlewares(AuthController)), + ...(fetchMiddlewares(AuthController.prototype.getOrgToken)), + + async function AuthController_getOrgToken(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsAuthController_getOrgToken, request, response }); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(AuthController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + await templateService.apiHandler({ + methodName: 'getOrgToken', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsAuthController_testFetchDedicatedX509Certificates: Record = { + _request: {"in":"request","name":"_request","required":true,"dataType":"object"}, + }; + app.get('/v1/orgs/test/dedicated-x509-certificates', + ...(fetchMiddlewares(AuthController)), + ...(fetchMiddlewares(AuthController.prototype.testFetchDedicatedX509Certificates)), + + async function AuthController_testFetchDedicatedX509Certificates(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsAuthController_testFetchDedicatedX509Certificates, request, response }); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(AuthController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + await templateService.apiHandler({ + methodName: 'testFetchDedicatedX509Certificates', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsAuthController_testFetchSharedAgentX509Certificates: Record = { + _request: {"in":"request","name":"_request","required":true,"dataType":"object"}, + }; + app.get('/v1/orgs/test/shared-agent-x509-certificates', + ...(fetchMiddlewares(AuthController)), + ...(fetchMiddlewares(AuthController.prototype.testFetchSharedAgentX509Certificates)), + + async function AuthController_testFetchSharedAgentX509Certificates(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsAuthController_testFetchSharedAgentX509Certificates, request, response }); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(AuthController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + await templateService.apiHandler({ + methodName: 'testFetchSharedAgentX509Certificates', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsAuthController_testGetTrustedCerts: Record = { + _request: {"in":"request","name":"_request","required":true,"dataType":"object"}, + }; + app.get('/v1/orgs/test/trusted-certs', + ...(fetchMiddlewares(AuthController)), + ...(fetchMiddlewares(AuthController.prototype.testGetTrustedCerts)), + + async function AuthController_testGetTrustedCerts(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsAuthController_testGetTrustedCerts, request, response }); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(AuthController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + await templateService.apiHandler({ + methodName: 'testGetTrustedCerts', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa const argsAgentController_getAgentInfo: Record = { request: {"in":"request","name":"request","required":true,"dataType":"object"}, }; diff --git a/src/routes/swagger.json b/src/routes/swagger.json index f8a0220b..0995acc3 100644 --- a/src/routes/swagger.json +++ b/src/routes/swagger.json @@ -2982,6 +2982,34 @@ "DidRecord": { "$ref": "#/components/schemas/Record_string.unknown_" }, + "OrgTokenResponse": { + "properties": { + "token": { + "type": "string" + } + }, + "required": [ + "token" + ], + "type": "object", + "additionalProperties": false + }, + "OrgTokenRequest": { + "properties": { + "clientId": { + "type": "string" + }, + "clientSecret": { + "type": "string" + } + }, + "required": [ + "clientId", + "clientSecret" + ], + "type": "object", + "additionalProperties": false + }, "AgentInfo": { "properties": { "label": { @@ -6857,6 +6885,124 @@ "parameters": [] } }, + "/v1/orgs/{orgId}/token": { + "post": { + "operationId": "GetOrgToken", + "responses": { + "200": { + "description": "Ok", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrgTokenResponse" + } + } + } + } + }, + "description": "Generate an organization token by forwarding credentials to the platform", + "tags": [ + "Auth" + ], + "security": [], + "parameters": [ + { + "in": "path", + "name": "orgId", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrgTokenRequest" + } + } + } + } + } + }, + "/v1/orgs/test/dedicated-x509-certificates": { + "get": { + "operationId": "TestFetchDedicatedX509Certificates", + "responses": { + "200": { + "description": "Ok", + "content": { + "application/json": { + "schema": { + "items": { + "type": "string" + }, + "type": "array" + } + } + } + } + }, + "tags": [ + "Auth" + ], + "security": [], + "parameters": [] + } + }, + "/v1/orgs/test/shared-agent-x509-certificates": { + "get": { + "operationId": "TestFetchSharedAgentX509Certificates", + "responses": { + "200": { + "description": "Ok", + "content": { + "application/json": { + "schema": { + "items": { + "type": "string" + }, + "type": "array" + } + } + } + } + }, + "tags": [ + "Auth" + ], + "security": [], + "parameters": [] + } + }, + "/v1/orgs/test/trusted-certs": { + "get": { + "operationId": "TestGetTrustedCerts", + "responses": { + "200": { + "description": "Ok", + "content": { + "application/json": { + "schema": { + "items": { + "type": "string" + }, + "type": "array" + } + } + } + } + }, + "description": "[TEMP] Manually trigger getTrustedCerts to test agent type detection and trust list fetch", + "tags": [ + "Auth" + ], + "security": [], + "parameters": [] + } + }, "/agent": { "get": { "operationId": "GetAgentInfo", diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index 47218a85..97938e92 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -2,6 +2,7 @@ import type { Curve, EcCurve, EcType, OkpCurve, OkpType } from '../controllers/t import type { KeyAlgorithm } from '@openwallet-foundation/askar-nodejs' import { JsonEncoder, JsonTransformer } from '@credo-ts/core' +import axios from 'axios' import { randomBytes } from 'crypto' import { curveToKty, keyAlgorithmToCurve } from './constant' @@ -92,3 +93,140 @@ export function getTypeFromCurve(key: Curve | KeyAlgorithm): OkpType | EcType { } return keyTypeInfo } + +async function fetchPlatformToken(platformBaseUrl: string, clientId: string, clientSecret: string, label: string): Promise { + const tokenUrl = `${platformBaseUrl}/v1/orgs/${clientId}/token` + console.log(`[${label}] fetching token from:`, tokenUrl) + + try { + const tokenResponse = await axios.post( + tokenUrl, + { clientSecret }, + { headers: { 'Content-Type': 'application/json', accept: 'application/json' } }, + ) + + console.log(`[${label}] token response status:`, tokenResponse.status) + console.log(`[${label}] token response data:`, JSON.stringify(tokenResponse.data, null, 2)) + + const token: string = tokenResponse.data.data.access_token + if (!token) throw new Error('Token not found in platform response') + + return token + } catch (error) { + if (axios.isAxiosError(error)) { + console.error(`[${label}] token request failed:`, { + status: error.response?.status, + statusText: error.response?.statusText, + data: error.response?.data, + message: error.message, + }) + throw new Error(`Failed to fetch token from platform: ${error.response?.status} ${JSON.stringify(error.response?.data)}`) + } + throw error + } +} + +async function fetchTrustServiceCertificates(trustServiceUrl: string, token: string, ecosystemIds: string[], label: string): Promise { + const certsUrl = `${trustServiceUrl}/api/x509-certificates/ecosystems` + console.log(`[${label}] fetching certificates from:`, certsUrl, 'ecosystemIds:', ecosystemIds) + + try { + const certResponse = await axios.get(certsUrl, { + params: { ecosystemIds: ecosystemIds.join(',') }, + headers: { accept: 'application/json', Authorization: `Bearer ${token}` }, + }) + + console.log(`[${label}] certificates response status:`, certResponse.status) + console.log(`[${label}] certificates response data:`, JSON.stringify(certResponse.data, null, 2)) + + if (!Array.isArray(certResponse.data) || certResponse.data.length === 0) { + throw new Error('No certificates returned from trust-service') + } + + const certificates: string[] = certResponse.data.map((cert: { certificateData: string }) => cert.certificateData) + console.log(`[${label}] extracted certificates count:`, certificates.length) + + return certificates + } catch (error) { + if (axios.isAxiosError(error)) { + console.error(`[${label}] certificates request failed:`, { + status: error.response?.status, + statusText: error.response?.statusText, + data: error.response?.data, + message: error.message, + }) + throw new Error(`Failed to fetch certificates from trust-service: ${error.response?.status} ${JSON.stringify(error.response?.data)}`) + } + throw error + } +} + +export async function fetchDedicatedX509Certificates(): Promise { + const platformBaseUrl = process.env.PLATFORM_BASE_URL + const clientId = process.env.PLATFORM_DEDICATED_CLIENT_ID + const clientSecret = process.env.PLATFORM_DEDICATED_CLIENT_SECRET + const trustServiceUrl = process.env.TRUST_SERVICE_URL + + if (!platformBaseUrl) throw new Error('PLATFORM_BASE_URL is not configured') + if (!clientId) throw new Error('PLATFORM_DEDICATED_CLIENT_ID is not configured') + if (!clientSecret) throw new Error('PLATFORM_DEDICATED_CLIENT_SECRET is not configured') + if (!trustServiceUrl) throw new Error('TRUST_SERVICE_URL is not configured') + + const token = await fetchPlatformToken(platformBaseUrl, clientId, clientSecret, 'fetchDedicatedX509Certificates') + return fetchTrustServiceCertificates(trustServiceUrl, token, [], 'fetchDedicatedX509Certificates') +} + +export async function fetchSharedAgentX509Certificates(tenantId?: string): Promise { + const label = 'fetchSharedAgentX509Certificates' + + const platformBaseUrl = process.env.PLATFORM_BASE_URL + const clientId = process.env.PLATFORM_SHARED_AGENT_CLIENT_ID + const clientSecret = process.env.PLATFORM_SHARED_AGENT_CLIENT_SECRET + const resolvedTenantId = tenantId ?? process.env.PLATFORM_SHARED_AGENT_TENANT_ID + const trustServiceUrl = process.env.TRUST_SERVICE_URL + + if (!platformBaseUrl) throw new Error('PLATFORM_BASE_URL is not configured') + if (!clientId) throw new Error('PLATFORM_SHARED_AGENT_CLIENT_ID is not configured') + if (!clientSecret) throw new Error('PLATFORM_SHARED_AGENT_CLIENT_SECRET is not configured') + if (!resolvedTenantId) throw new Error('tenantId not provided and PLATFORM_SHARED_AGENT_TENANT_ID is not configured') + if (!trustServiceUrl) throw new Error('TRUST_SERVICE_URL is not configured') + console.log(`[${label}] starting certificate fetch for tenantId:`, resolvedTenantId) + + console.log(`[${label}] using tenantId:`, resolvedTenantId, tenantId ? '(from agent context)' : '(from .env)') + + const token = await fetchPlatformToken(platformBaseUrl, clientId, clientSecret, label) + + const ecosystemsUrl = `${platformBaseUrl}/v1/orgs/tenant/${resolvedTenantId}/ecosystems` + console.log(`[${label}] fetching ecosystem IDs from:`, ecosystemsUrl) + + let ecosystemIds: string[] + try { + const ecosystemResponse = await axios.get<{ statusCode: number; message: string; data: string[] }>( + ecosystemsUrl, + { headers: { accept: 'application/json', Authorization: `Bearer ${token}` } }, + ) + + console.log(`[${label}] ecosystem response status:`, ecosystemResponse.status) + console.log(`[${label}] ecosystem response data:`, JSON.stringify(ecosystemResponse.data, null, 2)) + + ecosystemIds = ecosystemResponse.data.data + if (!Array.isArray(ecosystemIds) || ecosystemIds.length === 0) { + throw new Error(`No ecosystem IDs found for tenant: ${resolvedTenantId}`) + } + + console.log(`[${label}] ecosystem IDs:`, ecosystemIds) + } catch (error) { + if (axios.isAxiosError(error)) { + console.error(`[${label}] ecosystem IDs request failed:`, { + status: error.response?.status, + statusText: error.response?.statusText, + data: error.response?.data, + message: error.message, + }) + throw new Error(`Failed to fetch ecosystem IDs from platform: ${error.response?.status} ${JSON.stringify(error.response?.data)}`) + } + throw error + } + + return fetchTrustServiceCertificates(trustServiceUrl, token, ecosystemIds, label) +} diff --git a/src/utils/oid4vc-agent.ts b/src/utils/oid4vc-agent.ts index 05ffef75..6acbf823 100644 --- a/src/utils/oid4vc-agent.ts +++ b/src/utils/oid4vc-agent.ts @@ -1,5 +1,12 @@ import type { SdJwtVcHolderBinding } from '@credo-ts/core' +import type { DisclosureFrame } from '../controllers/types' +import { Agent, CredoError } from '@credo-ts/core' +import { container } from 'tsyringe' + +import { fetchDedicatedX509Certificates, fetchSharedAgentX509Certificates } from './helpers' import type { + OpenId4VcCredentialHolderBinding, + OpenId4VcCredentialHolderDidBinding, OpenId4VciCredentialRequestToCredentialMapper, OpenId4VciSignMdocCredentials, OpenId4VciSignSdJwtCredentials, @@ -163,17 +170,98 @@ export function getMixedCredentialRequestToCredentialMapper(): OpenId4VciCredent } } -export async function getTrustedCerts() { +function assertDidBasedHolderBinding( + holderBinding: OpenId4VcCredentialHolderBinding, +): asserts holderBinding is OpenId4VcCredentialHolderDidBinding { + if (holderBinding.method !== 'did') { + throw new CredoError('Only did based holder bindings supported for this credential type') + } +} + +export interface OpenId4VcIssuanceSessionCreateOfferSdJwtCredentialOptions { + /** + * The id of the `credential_supported` entry that is present in the issuer + * metadata. This id is used to identify the credential that is being offered. + * + * @example "ExampleCredentialSdJwtVc" + */ + credentialSupportedId: string + + /** + * The format of the credential that is being offered. + * MUST match the format of the `credential_supported` entry. + * + * @example {@link OpenId4VciCredentialFormatProfile.SdJwtVc} + */ + format: OpenId4VciCredentialFormatProfile + + /** + * The payload of the credential that will be issued. + * + * If `vct` claim is included, it MUST match the `vct` claim from the issuer metadata. + * If `vct` claim is not included, it will be added automatically. + * + * @example + * { + * "first_name": "John", + * "last_name": "Doe", + * "age": { + * "over_18": true, + * "over_21": true, + * "over_65": false + * } + * } + */ + payload: { + vct?: string + [key: string]: unknown + } + + /** + * Disclosure frame indicating which fields of the credential can be selectively disclosed. + * + * @example + * { + * "first_name": false, + * "last_name": false, + * "age": { + * "over_18": true, + * "over_21": true, + * "over_65": true + * } + * } + */ + disclosureFrame: DisclosureFrame +} + +export async function getTrustedCerts(tenantId?: string): Promise { try { - const response = await fetch(`${process.env.TRUST_LIST_URL}`) - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`) + const agent = container.resolve(Agent) + if (!agent) { + console.error('[getTrustedCerts] agent not available in container') + return [] + } + + const isDedicated = !('tenants' in agent.modules) + console.log('[getTrustedCerts] agent type:', isDedicated ? 'dedicated' : 'shared') + + let certs: string[] + if (isDedicated) { + certs = await fetchDedicatedX509Certificates() + } else { + certs = await fetchSharedAgentX509Certificates(tenantId) + } + + if (!Array.isArray(certs) || certs.length === 0) { + console.warn('[getTrustedCerts] no certificates returned') + return [] } - const data = await response.json() - return data as string[] +// Remove this log after testing to avoid logging sensitive certificate information + console.log('[getTrustedCerts] fetched certificates count:', certs.length) + console.log("certs::::::::::::::::::::::::::", certs) + return certs } catch (error) { - // eslint-disable-next-line no-console - console.error('Error fetching data:', error) + console.error('[getTrustedCerts] failed:', error instanceof Error ? error.message : error) return [] } } From 73c5e1836e5bbb7365ce5402b3ab4dd25c089cf8 Mon Sep 17 00:00:00 2001 From: Tipu_Singh Date: Tue, 3 Mar 2026 18:49:33 +0530 Subject: [PATCH 2/2] refactor: resolved PR comments Signed-off-by: Tipu_Singh --- src/utils/helpers.ts | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index 97938e92..8338592a 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -95,35 +95,46 @@ export function getTypeFromCurve(key: Curve | KeyAlgorithm): OkpType | EcType { } async function fetchPlatformToken(platformBaseUrl: string, clientId: string, clientSecret: string, label: string): Promise { + if (!platformBaseUrl) throw new Error(`[${label}] platformBaseUrl is required`) + if (!clientId) throw new Error(`[${label}] clientId is required`) + if (!clientSecret) throw new Error(`[${label}] clientSecret is required`) + const tokenUrl = `${platformBaseUrl}/v1/orgs/${clientId}/token` console.log(`[${label}] fetching token from:`, tokenUrl) + let tokenResponse try { - const tokenResponse = await axios.post( + tokenResponse = await axios.post( tokenUrl, { clientSecret }, { headers: { 'Content-Type': 'application/json', accept: 'application/json' } }, ) - - console.log(`[${label}] token response status:`, tokenResponse.status) - console.log(`[${label}] token response data:`, JSON.stringify(tokenResponse.data, null, 2)) - - const token: string = tokenResponse.data.data.access_token - if (!token) throw new Error('Token not found in platform response') - - return token } catch (error) { if (axios.isAxiosError(error)) { console.error(`[${label}] token request failed:`, { + url: tokenUrl, status: error.response?.status, statusText: error.response?.statusText, data: error.response?.data, message: error.message, }) - throw new Error(`Failed to fetch token from platform: ${error.response?.status} ${JSON.stringify(error.response?.data)}`) + throw new Error( + `[${label}] platform token request failed with status ${error.response?.status ?? 'no response'}: ${JSON.stringify(error.response?.data ?? error.message)}`, + ) } throw error } + + console.log(`[${label}] token response status:`, tokenResponse.status) + console.log(`[${label}] token response data:`, JSON.stringify(tokenResponse.data, null, 2)) + + const token: string = tokenResponse.data?.data?.access_token + if (!token) { + console.error(`[${label}] unexpected token response shape:`, JSON.stringify(tokenResponse.data, null, 2)) + throw new Error(`[${label}] access_token not found in platform response`) + } + + return token } async function fetchTrustServiceCertificates(trustServiceUrl: string, token: string, ecosystemIds: string[], label: string): Promise {