Skip to content
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"@credo-ts/anoncreds": "0.6.1",
"@credo-ts/askar": "0.6.1",
"@credo-ts/core": "0.6.1",
"@credo-ts/didcomm": "0.6.1",
"@credo-ts/indy-vdr": "0.6.1",
"@credo-ts/node": "0.6.1",
"@credo-ts/openid4vc": "0.6.1",
Expand Down
136 changes: 89 additions & 47 deletions src/cliAgent.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Note: For now we need to import askar-nodejs at the top to handle the undefined askar issue
// Refer from: https://github.com/credebl/mobile-sdk/blob/main/packages/ssi/src/wallet/wallet.ts
import '@openwallet-foundation/askar-nodejs'
import type { AskarModuleConfigStoreOptions } from '@credo-ts/askar'
import type { InitConfig } from '@credo-ts/core'
import type { IndyVdrPoolConfig } from '@credo-ts/indy-vdr'

Expand All @@ -14,7 +15,7 @@ import {
DidCommCredentialV1Protocol,
DidCommProofV1Protocol,
} from '@credo-ts/anoncreds'
import { AskarModule, AskarModuleConfigStoreOptions, AskarMultiWalletDatabaseScheme } from '@credo-ts/askar'
import { AskarModule, AskarMultiWalletDatabaseScheme } from '@credo-ts/askar'
import {
DidsModule,
W3cCredentialsModule,
Expand All @@ -26,6 +27,8 @@ import {
LogLevel,
Agent,
X509Module,
JwkDidRegistrar,
JwkDidResolver,
} from '@credo-ts/core'
import {
DidCommHttpOutboundTransport,
Expand All @@ -46,29 +49,22 @@ import {
IndyVdrIndyDidRegistrar,
} from '@credo-ts/indy-vdr'
import { agentDependencies, DidCommHttpInboundTransport, DidCommWsInboundTransport } from '@credo-ts/node'
import { OpenId4VcHolderModule, OpenId4VcModule } from '@credo-ts/openid4vc'
import { QuestionAnswerModule } from '@credo-ts/question-answer'
import { TenantsModule } from '@credo-ts/tenants'
import { anoncreds } from '@hyperledger/anoncreds-nodejs'
import { askar } from '@openwallet-foundation/askar-nodejs'
import { indyVdr } from '@hyperledger/indy-vdr-nodejs'
import { askar } from '@openwallet-foundation/askar-nodejs'
import axios from 'axios'
import bodyParser from 'body-parser'
import express from 'express'
import { readFile } from 'fs/promises'

import { IndicioAcceptanceMechanism, IndicioTransactionAuthorAgreement, Network, NetworkName } from './enums'
import { setupServer } from './server'
import { generateSecretKey } from './utils/helpers'
import { TsLogger } from './utils/logger'
import { OpenId4VcHolderModule, OpenId4VcIssuerModule, OpenId4VcVerifierModule } from '@credo-ts/openid4vc'
import {
getCredentialRequestToCredentialMapper,
getTrustedCerts,
} from './utils/oid4vc-agent'
import bodyParser from 'body-parser'

import express from 'express'

const openId4VpApp = express()
const openId4VcApp = express()
import { getMixedCredentialRequestToCredentialMapper, getTrustedCerts } from './utils/oid4vc-agent'

export type Transports = 'ws' | 'http'
export type InboundTransport = {
Expand Down Expand Up @@ -128,7 +124,15 @@ export async function readRestConfig(path: string) {
export type RestMultiTenantAgentModules = Awaited<ReturnType<typeof getWithTenantModules>>

export type RestAgentModules = Awaited<ReturnType<typeof getModules>>

function requireEnv(name: string): string {
const value = process.env[name]
if (!value) {
throw new Error(`Missing environment variable: ${name}`)
}
return value
}
const expressApp = express()
expressApp.disable('x-powered-by')
// TODO: add object
const getModules = (
networkConfig: [IndyVdrPoolConfig, ...IndyVdrPoolConfig[]],
Expand All @@ -141,34 +145,41 @@ const getModules = (
autoAcceptCredentials: DidCommAutoAcceptCredential,
autoAcceptProofs: DidCommAutoAcceptProof,
walletScheme: AskarMultiWalletDatabaseScheme,
storeOptions: AskarModuleConfigStoreOptions
storeOptions: AskarModuleConfigStoreOptions,
) => {
const legacyIndyCredentialFormat = new LegacyIndyDidCommCredentialFormatService()
const legacyIndyProofFormat = new LegacyIndyDidCommProofFormatService()
const jsonLdCredentialFormatService = new DidCommJsonLdCredentialFormatService()
const anonCredsCredentialFormatService = new AnonCredsDidCommCredentialFormatService()
const anonCredsProofFormatService = new AnonCredsDidCommProofFormatService()
const presentationExchangeProofFormatService = new DidCommDifPresentationExchangeProofFormatService()

return {
askar: new AskarModule({
askar,
store: {
...storeOptions
...storeOptions,
},
multiWalletDatabaseScheme: walletScheme || AskarMultiWalletDatabaseScheme.ProfilePerWallet,

}),

indyVdr: new IndyVdrModule({
indyVdr,
networks: networkConfig,
}),

dids: new DidsModule({
registrars: [new IndyVdrIndyDidRegistrar(), new KeyDidRegistrar()
registrars: [
new IndyVdrIndyDidRegistrar(),
new KeyDidRegistrar(),
new JwkDidRegistrar(),
// , new PolygonDidRegistrar()
],
resolvers: [new IndyVdrIndyDidResolver(), new KeyDidResolver(), new WebDidResolver()
resolvers: [
new IndyVdrIndyDidResolver(),
new KeyDidResolver(),
new WebDidResolver(),
new JwkDidResolver(),
new KeyDidResolver(),
// , new PolygonDidResolver()
],
}),
Expand Down Expand Up @@ -210,7 +221,11 @@ const getModules = (
indyCredentialFormat: legacyIndyCredentialFormat,
}),
new DidCommCredentialV2Protocol({
credentialFormats: [legacyIndyCredentialFormat, jsonLdCredentialFormatService, anonCredsCredentialFormatService],
credentialFormats: [
legacyIndyCredentialFormat,
jsonLdCredentialFormatService,
anonCredsCredentialFormatService,
],
}),
],
},
Expand All @@ -220,6 +235,8 @@ const getModules = (
}),

questionAnswer: new QuestionAnswerModule(),
// openid4vc: new OpenId4VcModule({}),
// Todo: We can remove this polygon module for time being
// polygon: new PolygonModule({
// didContractAddress: didRegistryContractAddress
// ? didRegistryContractAddress
Expand All @@ -230,27 +247,51 @@ const getModules = (
// rpcUrl: rpcUrl ? rpcUrl : (process.env.RPC_URL as string),
// serverUrl: fileServerUrl ? fileServerUrl : (process.env.SERVER_URL as string),
// }),
openId4VcVerifier: new OpenId4VcVerifierModule({
baseUrl:
process.env.NODE_ENV === 'PROD'
? `https://${process.env.APP_URL}/oid4vp`
: `${process.env.AGENT_HTTP_URL}/oid4vp`,
app: openId4VpApp,
authorizationRequestExpirationInSeconds: Number(process.env.OID4VP_AUTH_REQUEST_PROOF_REQUEST_EXPIRY) || 3600,
}),
openId4VcIssuer: new OpenId4VcIssuerModule({
baseUrl:
process.env.NODE_ENV === 'PROD'
? `https://${process.env.APP_URL}/oid4vci`
: `${process.env.AGENT_HTTP_URL}/oid4vci`,
app: openId4VcApp,
statefulCredentialOfferExpirationInSeconds: Number(process.env.OID4VCI_CRED_OFFER_EXPIRY) || 3600,
accessTokenExpiresInSeconds: Number(process.env.OID4VCI_ACCESS_TOKEN_EXPIRY) || 3600,
authorizationCodeExpiresInSeconds: Number(process.env.OID4VCI_AUTH_CODE_EXPIRY) || 3600,
cNonceExpiresInSeconds: Number(process.env.OID4VCI_CNONCE_EXPIRY) || 3600,
dpopRequired: false,
credentialRequestToCredentialMapper: (...args) => getCredentialRequestToCredentialMapper()(...args),
openid4vc: new OpenId4VcModule({
app: expressApp,
issuer: {
baseUrl:
process.env.NODE_ENV === 'PROD'
? `https://${requireEnv('APP_URL')}/oid4vci`
: `${requireEnv('AGENT_HTTP_URL')}/oid4vci`,
app: expressApp,
statefulCredentialOfferExpirationInSeconds: Number(process.env.OID4VCI_CRED_OFFER_EXPIRY) || 3600,
accessTokenExpiresInSeconds: Number(process.env.OID4VCI_ACCESS_TOKEN_EXPIRY) || 3600,
authorizationCodeExpiresInSeconds: Number(process.env.OID4VCI_AUTH_CODE_EXPIRY) || 3600,
cNonceExpiresInSeconds: Number(process.env.OID4VCI_CNONCE_EXPIRY) || 3600,
dpopRequired: false,
credentialRequestToCredentialMapper: (...args) => getMixedCredentialRequestToCredentialMapper()(...args),
},
verifier: {
baseUrl:
process.env.NODE_ENV === 'PROD'
? `https://${requireEnv('APP_URL')}/oid4vp`
: `${requireEnv('AGENT_HTTP_URL')}/oid4vp`,
// app: openId4VpApp,
authorizationRequestExpirationInSeconds: Number(process.env.OID4VP_AUTH_REQUEST_PROOF_REQUEST_EXPIRY) || 3600,
},
}),
// openId4VcVerifier: new OpenId4VcVerifierModule({
// baseUrl:
// process.env.NODE_ENV === 'PROD'
// ? `https://${process.env.APP_URL}/oid4vp`
// : `${process.env.AGENT_HTTP_URL}/oid4vp`,
// app: openId4VpApp,
// authorizationRequestExpirationInSeconds: Number(process.env.OID4VP_AUTH_REQUEST_PROOF_REQUEST_EXPIRY) || 3600,
// }),
// openId4VcIssuer: new OpenId4VcIssuerModule({
// baseUrl:
// process.env.NODE_ENV === 'PROD'
// ? `https://${process.env.APP_URL}/oid4vci`
// : `${process.env.AGENT_HTTP_URL}/oid4vci`,
// app: openId4VcApp,
// statefulCredentialOfferExpirationInSeconds: Number(process.env.OID4VCI_CRED_OFFER_EXPIRY) || 3600,
// accessTokenExpiresInSeconds: Number(process.env.OID4VCI_ACCESS_TOKEN_EXPIRY) || 3600,
// authorizationCodeExpiresInSeconds: Number(process.env.OID4VCI_AUTH_CODE_EXPIRY) || 3600,
// cNonceExpiresInSeconds: Number(process.env.OID4VCI_CNONCE_EXPIRY) || 3600,
// dpopRequired: false,
// credentialRequestToCredentialMapper: (...args) => getCredentialRequestToCredentialMapper()(...args),
// }),
openId4VcHolderModule: new OpenId4VcHolderModule(),
x509: new X509Module({
getTrustedCertificatesForVerification: async (_agentContext, { certificateChain, verification }) => {
Expand All @@ -275,7 +316,7 @@ const getWithTenantModules = (
autoAcceptCredentials: DidCommAutoAcceptCredential,
autoAcceptProofs: DidCommAutoAcceptProof,
walletScheme: AskarMultiWalletDatabaseScheme,
walletConfig: AskarModuleConfigStoreOptions
walletConfig: AskarModuleConfigStoreOptions,
) => {
const modules = getModules(
networkConfig,
Expand All @@ -288,7 +329,7 @@ const getWithTenantModules = (
autoAcceptCredentials,
autoAcceptProofs,
walletScheme,
walletConfig
walletConfig,
)
return {
tenants: new TenantsModule<typeof modules>({
Expand Down Expand Up @@ -418,7 +459,7 @@ export async function runRestAgent(restConfig: AriesRestConfig) {
autoAcceptCredentials || DidCommAutoAcceptCredential.Always,
autoAcceptProofs || DidCommAutoAcceptProof.ContentApproved,
walletScheme || AskarMultiWalletDatabaseScheme.ProfilePerWallet,
walletConfig
walletConfig,
)
const modules = getModules(
networkConfig,
Expand All @@ -431,7 +472,7 @@ export async function runRestAgent(restConfig: AriesRestConfig) {
autoAcceptCredentials || DidCommAutoAcceptCredential.Always,
autoAcceptProofs || DidCommAutoAcceptProof.ContentApproved,
walletScheme || AskarMultiWalletDatabaseScheme.ProfilePerWallet,
walletConfig
walletConfig,
)
const agent = new Agent({
config: agentConfig,
Expand Down Expand Up @@ -468,8 +509,8 @@ export async function runRestAgent(restConfig: AriesRestConfig) {
)
transport.app.use(bodyParser.json({ limit: process.env.APP_JSON_BODY_SIZE ?? '5mb' }))

transport.app.use('/oid4vci', modules.openId4VcIssuer.config.app)
transport.app.use('/oid4vp', modules.openId4VcVerifier.config.app)
// transport.app.use('/oid4vci', modules.openid4vc.issuer?.config.app.routes ?? express.Router())
// transport.app.use('/oid4vp', modules.openid4vc.verifier?.config.app.routes ?? express.Router())
Comment on lines +512 to +513
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Check the current state of route mounting in cliAgent.ts around those lines
cat -n src/cliAgent.ts | sed -n '500,530p'

Repository: credebl/credo-controller

Length of output: 1558


🏁 Script executed:

# Search for how OpenId4VcModule handles routes
rg -n "OpenId4VcModule|openid4vc" src/cliAgent.ts -A 2 -B 2

Repository: credebl/credo-controller

Length of output: 1088


🏁 Script executed:

# Check if routes are being mounted elsewhere
rg -n "oid4vci|oid4vp" --type ts

Repository: credebl/credo-controller

Length of output: 924


🏁 Script executed:

# Look for the module configuration and route setup
rg -n "config.app.routes" --type ts

Repository: credebl/credo-controller

Length of output: 316


🏁 Script executed:

# Check the full OpenId4VcModule configuration
sed -n '251,290p' src/cliAgent.ts

Repository: credebl/credo-controller

Length of output: 2071


🏁 Script executed:

# Search for any route registration or setup in the OpenId4VcModule configuration
rg -n "issuer.*config\|verifier.*config" src/cliAgent.ts -A 5 -B 1

Repository: credebl/credo-controller

Length of output: 50


🏁 Script executed:

# Look for any initialization or setup that might be handling routes
rg -n "app.use\|app.get\|app.post" src/cliAgent.ts | head -20

Repository: credebl/credo-controller

Length of output: 50


🏁 Script executed:

# Get the exact verifier configuration
sed -n '268,280p' src/cliAgent.ts

Repository: credebl/credo-controller

Length of output: 642


🏁 Script executed:

# Search for any initialization logging or setup that might indicate routes being registered
rg -n "router|routes|register" src/cliAgent.ts | grep -v "node_modules"

Repository: credebl/credo-controller

Length of output: 501


🏁 Script executed:

# Check if there are any other files that might configure OID4VC routing
fd -e ts -e js src/ | xargs grep -l "oid4v" | head -5

Repository: credebl/credo-controller

Length of output: 389


Verifier routes will not be registered: missing app property in verifier config.

The issuer configuration includes app: expressApp at lines 261-262, enabling route registration for /oid4vci. However, the verifier configuration (lines 266-272) is missing the corresponding app property—the line app: openId4VpApp is commented out with no replacement. This will prevent the /oid4vp routes from being properly mounted on the express application. Add app: expressApp to the verifier configuration to ensure routes are registered alongside the issuer routes.

🤖 Prompt for AI Agents
In src/cliAgent.ts around lines 513-514, the verifier config is missing the app
property so its routes won't be mounted (the issuer has app: expressApp); add
app: expressApp to the verifier configuration object (or otherwise reference the
express app used for issuer) so transport.app.use('/oid4vp',
modules.openid4vc.verifier?.config.app.routes ?? express.Router()) can register
the verifier routes correctly.

}
}

Expand Down Expand Up @@ -505,6 +546,7 @@ export async function runRestAgent(restConfig: AriesRestConfig) {
webhookUrl,
port: adminPort,
schemaFileServerURL,
app: expressApp,
},
apiKey,
)
Expand Down
Loading