@credo-ts/openid4vc 0.6.2-alpha-20251222120740 → 0.6.2-pr-2610-20260107224500
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/index.d.mts +2 -2
- package/build/openid4vc-holder/OpenId4VcHolderApi.d.mts +4 -1
- package/build/openid4vc-holder/OpenId4VcHolderApi.d.mts.map +1 -1
- package/build/openid4vc-holder/OpenId4VcHolderApi.mjs +3 -0
- package/build/openid4vc-holder/OpenId4VcHolderApi.mjs.map +1 -1
- package/build/openid4vc-holder/OpenId4VciHolderService.d.mts +4 -1
- package/build/openid4vc-holder/OpenId4VciHolderService.d.mts.map +1 -1
- package/build/openid4vc-holder/OpenId4VciHolderService.mjs +16 -4
- package/build/openid4vc-holder/OpenId4VciHolderService.mjs.map +1 -1
- package/build/openid4vc-holder/OpenId4VciHolderServiceOptions.d.mts +5 -1
- package/build/openid4vc-holder/OpenId4VciHolderServiceOptions.d.mts.map +1 -1
- package/build/openid4vc-holder/OpenId4VciHolderServiceOptions.mjs.map +1 -1
- package/build/openid4vc-holder/index.d.mts +1 -1
- package/build/openid4vc-issuer/OpenId4VcIssuerApi.d.mts +2 -0
- package/build/openid4vc-issuer/OpenId4VcIssuerApi.d.mts.map +1 -1
- package/build/openid4vc-issuer/OpenId4VcIssuerService.d.mts +4 -3
- package/build/openid4vc-issuer/OpenId4VcIssuerService.d.mts.map +1 -1
- package/build/openid4vc-issuer/OpenId4VcIssuerService.mjs +5 -4
- package/build/openid4vc-issuer/OpenId4VcIssuerService.mjs.map +1 -1
- package/build/openid4vc-issuer/router/credentialEndpoint.mjs +2 -2
- package/build/openid4vc-issuer/router/credentialEndpoint.mjs.map +1 -1
- package/build/openid4vc-issuer/router/redirectEndpoint.mjs +12 -4
- package/build/openid4vc-issuer/router/redirectEndpoint.mjs.map +1 -1
- package/package.json +7 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"credentialEndpoint.mjs","names":["issuanceSession: OpenId4VcIssuanceSessionRecord | null","configurationsForToken: CredentialConfigurationsSupportedWithFormats"],"sources":["../../../src/openid4vc-issuer/router/credentialEndpoint.ts"],"sourcesContent":["import { joinUriParts, utils } from '@credo-ts/core'\nimport type { HttpMethod } from '@openid4vc/oauth2'\nimport { Oauth2ErrorCodes, Oauth2ResourceUnauthorizedError, Oauth2ServerErrorResponseError } from '@openid4vc/oauth2'\nimport {\n type CredentialConfigurationsSupportedWithFormats,\n getCredentialConfigurationsMatchingRequestFormat,\n Openid4vciDraftVersion,\n} from '@openid4vc/openid4vci'\nimport type { Response, Router } from 'express'\nimport { getCredentialConfigurationsSupportedForScopes } from '../../shared'\nimport {\n getRequestContext,\n sendJsonResponse,\n sendOauth2ErrorResponse,\n sendUnauthorizedError,\n sendUnknownServerErrorResponse,\n} from '../../shared/router'\nimport { OpenId4VcIssuanceSessionState } from '../OpenId4VcIssuanceSessionState'\nimport type { OpenId4VcIssuerModuleConfig } from '../OpenId4VcIssuerModuleConfig'\nimport { OpenId4VcIssuerService } from '../OpenId4VcIssuerService'\nimport { OpenId4VcIssuanceSessionRecord, OpenId4VcIssuanceSessionRepository } from '../repository'\nimport type { OpenId4VcIssuanceRequest } from './requestContext'\n\nexport function configureCredentialEndpoint(router: Router, config: OpenId4VcIssuerModuleConfig) {\n router.post(config.credentialEndpointPath, async (request: OpenId4VcIssuanceRequest, response: Response, next) => {\n const { agentContext, issuer } = getRequestContext(request)\n const openId4VcIssuerService = agentContext.dependencyManager.resolve(OpenId4VcIssuerService)\n const issuerMetadata = await openId4VcIssuerService.getIssuerMetadata(agentContext, issuer, true)\n const vcIssuer = openId4VcIssuerService.getIssuer(agentContext)\n const resourceServer = openId4VcIssuerService.getResourceServer(agentContext, issuer)\n\n const fullRequestUrl = joinUriParts(issuerMetadata.credentialIssuer.credential_issuer, [\n config.credentialEndpointPath,\n ])\n const resourceRequestResult = await resourceServer\n .verifyResourceRequest({\n authorizationServers: issuerMetadata.authorizationServers,\n resourceServer: issuerMetadata.credentialIssuer.credential_issuer,\n request: {\n headers: new Headers(request.headers as Record<string, string>),\n method: request.method as HttpMethod,\n url: fullRequestUrl,\n },\n })\n .catch((error) => {\n sendUnauthorizedError(response, next, agentContext.config.logger, error)\n })\n if (!resourceRequestResult) return\n const { tokenPayload, accessToken, scheme, authorizationServer } = resourceRequestResult\n\n const credentialRequest = request.body\n const issuanceSessionRepository = agentContext.dependencyManager.resolve(OpenId4VcIssuanceSessionRepository)\n\n const parsedCredentialRequest = vcIssuer.parseCredentialRequest({\n credentialRequest,\n issuerMetadata,\n })\n\n let issuanceSession: OpenId4VcIssuanceSessionRecord | null = null\n const preAuthorizedCode =\n typeof tokenPayload['pre-authorized_code'] === 'string' ? tokenPayload['pre-authorized_code'] : undefined\n const issuerState = typeof tokenPayload.issuer_state === 'string' ? tokenPayload.issuer_state : undefined\n\n const subject = tokenPayload.sub\n if (!subject) {\n return sendOauth2ErrorResponse(\n response,\n next,\n agentContext.config.logger,\n new Oauth2ServerErrorResponseError(\n {\n error: Oauth2ErrorCodes.ServerError,\n },\n {\n internalMessage: `Received token without 'sub' claim. Subject is required for binding issuance session`,\n }\n )\n )\n }\n\n // Already handle request without format/credential_configuration_id. Simplifies next code sections\n if (!parsedCredentialRequest.format && !parsedCredentialRequest.credentialConfiguration) {\n return sendOauth2ErrorResponse(\n response,\n next,\n agentContext.config.logger,\n new Oauth2ServerErrorResponseError({\n error: parsedCredentialRequest.credentialIdentifier\n ? Oauth2ErrorCodes.InvalidCredentialRequest\n : Oauth2ErrorCodes.UnsupportedCredentialFormat,\n error_description: parsedCredentialRequest.credentialIdentifier\n ? `Credential request containing 'credential_identifier' not supported`\n : parsedCredentialRequest.credentialConfigurationId\n ? `Credential configuration '${parsedCredentialRequest.credentialConfigurationId}' not supported`\n : `Credential format '${parsedCredentialRequest.credentialRequest.format}' not supported`,\n })\n )\n }\n\n if (preAuthorizedCode || issuerState) {\n issuanceSession = await issuanceSessionRepository.findSingleByQuery(agentContext, {\n issuerId: issuer.issuerId,\n preAuthorizedCode,\n issuerState,\n })\n\n if (!issuanceSession) {\n agentContext.config.logger.warn(\n `No issuance session found for incoming credential request for issuer ${\n issuer.issuerId\n } but access token data has ${\n issuerState ? 'issuer_state' : 'pre-authorized_code'\n }. Returning error response`,\n {\n tokenPayload,\n }\n )\n\n return sendOauth2ErrorResponse(\n response,\n next,\n agentContext.config.logger,\n new Oauth2ServerErrorResponseError(\n {\n error: Oauth2ErrorCodes.CredentialRequestDenied,\n },\n {\n internalMessage: `No issuance session found for incoming credential request for issuer ${issuer.issuerId} and access token data`,\n }\n )\n )\n }\n\n // Use issuance session dpop config\n if (issuanceSession.dpop?.required && !resourceRequestResult.dpop) {\n return sendUnauthorizedError(\n response,\n next,\n agentContext.config.logger,\n new Oauth2ResourceUnauthorizedError('Missing required DPoP proof', {\n scheme,\n error: Oauth2ErrorCodes.InvalidDpopProof,\n })\n )\n }\n\n const expiresAt =\n issuanceSession.expiresAt ??\n utils.addSecondsToDate(issuanceSession.createdAt, config.statefulCredentialOfferExpirationInSeconds)\n\n // Verify the issuance session subject\n if (issuanceSession.authorization?.subject) {\n if (issuanceSession.authorization.subject !== tokenPayload.sub) {\n return sendOauth2ErrorResponse(\n response,\n next,\n agentContext.config.logger,\n new Oauth2ServerErrorResponseError(\n {\n error: Oauth2ErrorCodes.CredentialRequestDenied,\n },\n {\n internalMessage: `Issuance session authorization subject does not match with the token payload subject for issuance session '${issuanceSession.id}'. Returning error response`,\n }\n )\n )\n }\n }\n\n // Stateful session expired\n else if (Date.now() > expiresAt.getTime()) {\n issuanceSession.errorMessage = 'Credential offer has expired'\n await openId4VcIssuerService.updateState(agentContext, issuanceSession, OpenId4VcIssuanceSessionState.Error)\n return sendOauth2ErrorResponse(\n response,\n next,\n agentContext.config.logger,\n new Oauth2ServerErrorResponseError({\n // What is the best error here?\n error: Oauth2ErrorCodes.CredentialRequestDenied,\n error_description: 'Session expired',\n })\n )\n } else {\n issuanceSession.authorization = {\n ...issuanceSession.authorization,\n subject: tokenPayload.sub,\n }\n await issuanceSessionRepository.update(agentContext, issuanceSession)\n }\n }\n\n if (!issuanceSession && config.allowDynamicIssuanceSessions) {\n agentContext.config.logger.warn(\n `No issuance session found for incoming credential request for issuer ${issuer.issuerId} and access token data has no issuer_state or pre-authorized_code. Creating on-demand issuance session`,\n {\n tokenPayload,\n }\n )\n\n // Use global config when creating a dynamic session\n if (config.dpopRequired && !resourceRequestResult.dpop) {\n return sendUnauthorizedError(\n response,\n next,\n agentContext.config.logger,\n new Oauth2ResourceUnauthorizedError('Missing required DPoP proof', {\n scheme,\n error: Oauth2ErrorCodes.InvalidDpopProof,\n })\n )\n }\n\n const configurationsForScope = getCredentialConfigurationsSupportedForScopes(\n issuerMetadata.credentialIssuer.credential_configurations_supported,\n tokenPayload.scope?.split(' ') ?? []\n )\n\n // All credential configurations that match the request scope and credential request\n // This is just so we don't create an issuance session that will fail immediately after\n let configurationsForToken: CredentialConfigurationsSupportedWithFormats = {}\n\n if (parsedCredentialRequest.credentialConfigurationId && parsedCredentialRequest.credentialConfiguration) {\n if (configurationsForScope[parsedCredentialRequest.credentialConfigurationId]) {\n configurationsForToken = {\n [parsedCredentialRequest.credentialConfigurationId]: parsedCredentialRequest.credentialConfiguration,\n }\n }\n } else if (parsedCredentialRequest.format) {\n configurationsForToken = getCredentialConfigurationsMatchingRequestFormat({\n issuerMetadata,\n requestFormat: parsedCredentialRequest.format,\n })\n }\n\n if (Object.keys(configurationsForToken).length === 0) {\n return sendUnauthorizedError(\n response,\n next,\n agentContext.config.logger,\n new Oauth2ResourceUnauthorizedError(\n 'No credential configurations match credential request and access token scope',\n {\n scheme,\n error: Oauth2ErrorCodes.InsufficientScope,\n }\n ),\n // Forbidden for InsufficientScope\n 403\n )\n }\n\n const createdAt = new Date()\n const expiresAt = utils.addSecondsToDate(createdAt, config.statefulCredentialOfferExpirationInSeconds)\n\n issuanceSession = new OpenId4VcIssuanceSessionRecord({\n createdAt,\n expiresAt,\n credentialOfferPayload: {\n credential_configuration_ids: Object.keys(configurationsForToken),\n credential_issuer: issuerMetadata.credentialIssuer.credential_issuer,\n },\n credentialOfferId: utils.uuid(),\n issuerId: issuer.issuerId,\n state: OpenId4VcIssuanceSessionState.CredentialRequestReceived,\n clientId: tokenPayload.client_id,\n dpop: config.dpopRequired\n ? {\n required: true,\n }\n : undefined,\n authorization: {\n subject: tokenPayload.sub,\n },\n openId4VciVersion:\n issuerMetadata.originalDraftVersion === Openid4vciDraftVersion.V1\n ? 'v1'\n : issuerMetadata.originalDraftVersion === Openid4vciDraftVersion.Draft15\n ? 'v1.draft15'\n : 'v1.draft11-14',\n })\n\n // Save and update\n await issuanceSessionRepository.save(agentContext, issuanceSession)\n openId4VcIssuerService.emitStateChangedEvent(agentContext, issuanceSession, null)\n } else if (!issuanceSession) {\n return sendOauth2ErrorResponse(\n response,\n next,\n agentContext.config.logger,\n new Oauth2ServerErrorResponseError(\n {\n error: Oauth2ErrorCodes.CredentialRequestDenied,\n },\n {\n internalMessage: `Access token without 'issuer_state' or 'pre-authorized_code' issued by external authorization server provided, but 'allowDynamicIssuanceSessions' is disabled. Either bind the access token to a stateful credential offer, or enable 'allowDynamicIssuanceSessions'.`,\n }\n )\n )\n }\n\n try {\n const { credentialResponse } = await openId4VcIssuerService.createCredentialResponse(agentContext, {\n issuanceSession,\n credentialRequest,\n authorization: {\n authorizationServer,\n accessToken: {\n payload: tokenPayload,\n value: accessToken,\n },\n },\n })\n\n return sendJsonResponse(\n response,\n next,\n credentialResponse,\n undefined,\n credentialResponse.transaction_id ? 202 : 200\n )\n } catch (error) {\n if (error instanceof Oauth2ServerErrorResponseError) {\n return sendOauth2ErrorResponse(response, next, agentContext.config.logger, error)\n }\n if (error instanceof Oauth2ResourceUnauthorizedError) {\n return sendUnauthorizedError(response, next, agentContext.config.logger, error)\n }\n\n return sendUnknownServerErrorResponse(response, next, agentContext.config.logger, error)\n }\n })\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"credentialEndpoint.mjs","names":["issuanceSession: OpenId4VcIssuanceSessionRecord | null","configurationsForToken: CredentialConfigurationsSupportedWithFormats"],"sources":["../../../src/openid4vc-issuer/router/credentialEndpoint.ts"],"sourcesContent":["import { joinUriParts, utils } from '@credo-ts/core'\nimport type { HttpMethod } from '@openid4vc/oauth2'\nimport { Oauth2ErrorCodes, Oauth2ResourceUnauthorizedError, Oauth2ServerErrorResponseError } from '@openid4vc/oauth2'\nimport {\n type CredentialConfigurationsSupportedWithFormats,\n getCredentialConfigurationsMatchingRequestFormat,\n Openid4vciVersion,\n} from '@openid4vc/openid4vci'\nimport type { Response, Router } from 'express'\nimport { getCredentialConfigurationsSupportedForScopes } from '../../shared'\nimport {\n getRequestContext,\n sendJsonResponse,\n sendOauth2ErrorResponse,\n sendUnauthorizedError,\n sendUnknownServerErrorResponse,\n} from '../../shared/router'\nimport { OpenId4VcIssuanceSessionState } from '../OpenId4VcIssuanceSessionState'\nimport type { OpenId4VcIssuerModuleConfig } from '../OpenId4VcIssuerModuleConfig'\nimport { OpenId4VcIssuerService } from '../OpenId4VcIssuerService'\nimport { OpenId4VcIssuanceSessionRecord, OpenId4VcIssuanceSessionRepository } from '../repository'\nimport type { OpenId4VcIssuanceRequest } from './requestContext'\n\nexport function configureCredentialEndpoint(router: Router, config: OpenId4VcIssuerModuleConfig) {\n router.post(config.credentialEndpointPath, async (request: OpenId4VcIssuanceRequest, response: Response, next) => {\n const { agentContext, issuer } = getRequestContext(request)\n const openId4VcIssuerService = agentContext.dependencyManager.resolve(OpenId4VcIssuerService)\n const issuerMetadata = await openId4VcIssuerService.getIssuerMetadata(agentContext, issuer, true)\n const vcIssuer = openId4VcIssuerService.getIssuer(agentContext)\n const resourceServer = openId4VcIssuerService.getResourceServer(agentContext, issuer)\n\n const fullRequestUrl = joinUriParts(issuerMetadata.credentialIssuer.credential_issuer, [\n config.credentialEndpointPath,\n ])\n const resourceRequestResult = await resourceServer\n .verifyResourceRequest({\n authorizationServers: issuerMetadata.authorizationServers,\n resourceServer: issuerMetadata.credentialIssuer.credential_issuer,\n request: {\n headers: new Headers(request.headers as Record<string, string>),\n method: request.method as HttpMethod,\n url: fullRequestUrl,\n },\n })\n .catch((error) => {\n sendUnauthorizedError(response, next, agentContext.config.logger, error)\n })\n if (!resourceRequestResult) return\n const { tokenPayload, accessToken, scheme, authorizationServer } = resourceRequestResult\n\n const credentialRequest = request.body\n const issuanceSessionRepository = agentContext.dependencyManager.resolve(OpenId4VcIssuanceSessionRepository)\n\n const parsedCredentialRequest = vcIssuer.parseCredentialRequest({\n credentialRequest,\n issuerMetadata,\n })\n\n let issuanceSession: OpenId4VcIssuanceSessionRecord | null = null\n const preAuthorizedCode =\n typeof tokenPayload['pre-authorized_code'] === 'string' ? tokenPayload['pre-authorized_code'] : undefined\n const issuerState = typeof tokenPayload.issuer_state === 'string' ? tokenPayload.issuer_state : undefined\n\n const subject = tokenPayload.sub\n if (!subject) {\n return sendOauth2ErrorResponse(\n response,\n next,\n agentContext.config.logger,\n new Oauth2ServerErrorResponseError(\n {\n error: Oauth2ErrorCodes.ServerError,\n },\n {\n internalMessage: `Received token without 'sub' claim. Subject is required for binding issuance session`,\n }\n )\n )\n }\n\n // Already handle request without format/credential_configuration_id. Simplifies next code sections\n if (!parsedCredentialRequest.format && !parsedCredentialRequest.credentialConfiguration) {\n return sendOauth2ErrorResponse(\n response,\n next,\n agentContext.config.logger,\n new Oauth2ServerErrorResponseError({\n error: parsedCredentialRequest.credentialIdentifier\n ? Oauth2ErrorCodes.InvalidCredentialRequest\n : Oauth2ErrorCodes.UnsupportedCredentialFormat,\n error_description: parsedCredentialRequest.credentialIdentifier\n ? `Credential request containing 'credential_identifier' not supported`\n : parsedCredentialRequest.credentialConfigurationId\n ? `Credential configuration '${parsedCredentialRequest.credentialConfigurationId}' not supported`\n : `Credential format '${parsedCredentialRequest.credentialRequest.format}' not supported`,\n })\n )\n }\n\n if (preAuthorizedCode || issuerState) {\n issuanceSession = await issuanceSessionRepository.findSingleByQuery(agentContext, {\n issuerId: issuer.issuerId,\n preAuthorizedCode,\n issuerState,\n })\n\n if (!issuanceSession) {\n agentContext.config.logger.warn(\n `No issuance session found for incoming credential request for issuer ${\n issuer.issuerId\n } but access token data has ${\n issuerState ? 'issuer_state' : 'pre-authorized_code'\n }. Returning error response`,\n {\n tokenPayload,\n }\n )\n\n return sendOauth2ErrorResponse(\n response,\n next,\n agentContext.config.logger,\n new Oauth2ServerErrorResponseError(\n {\n error: Oauth2ErrorCodes.CredentialRequestDenied,\n },\n {\n internalMessage: `No issuance session found for incoming credential request for issuer ${issuer.issuerId} and access token data`,\n }\n )\n )\n }\n\n // Use issuance session dpop config\n if (issuanceSession.dpop?.required && !resourceRequestResult.dpop) {\n return sendUnauthorizedError(\n response,\n next,\n agentContext.config.logger,\n new Oauth2ResourceUnauthorizedError('Missing required DPoP proof', {\n scheme,\n error: Oauth2ErrorCodes.InvalidDpopProof,\n })\n )\n }\n\n const expiresAt =\n issuanceSession.expiresAt ??\n utils.addSecondsToDate(issuanceSession.createdAt, config.statefulCredentialOfferExpirationInSeconds)\n\n // Verify the issuance session subject\n if (issuanceSession.authorization?.subject) {\n if (issuanceSession.authorization.subject !== tokenPayload.sub) {\n return sendOauth2ErrorResponse(\n response,\n next,\n agentContext.config.logger,\n new Oauth2ServerErrorResponseError(\n {\n error: Oauth2ErrorCodes.CredentialRequestDenied,\n },\n {\n internalMessage: `Issuance session authorization subject does not match with the token payload subject for issuance session '${issuanceSession.id}'. Returning error response`,\n }\n )\n )\n }\n }\n\n // Stateful session expired\n else if (Date.now() > expiresAt.getTime()) {\n issuanceSession.errorMessage = 'Credential offer has expired'\n await openId4VcIssuerService.updateState(agentContext, issuanceSession, OpenId4VcIssuanceSessionState.Error)\n return sendOauth2ErrorResponse(\n response,\n next,\n agentContext.config.logger,\n new Oauth2ServerErrorResponseError({\n // What is the best error here?\n error: Oauth2ErrorCodes.CredentialRequestDenied,\n error_description: 'Session expired',\n })\n )\n } else {\n issuanceSession.authorization = {\n ...issuanceSession.authorization,\n subject: tokenPayload.sub,\n }\n await issuanceSessionRepository.update(agentContext, issuanceSession)\n }\n }\n\n if (!issuanceSession && config.allowDynamicIssuanceSessions) {\n agentContext.config.logger.warn(\n `No issuance session found for incoming credential request for issuer ${issuer.issuerId} and access token data has no issuer_state or pre-authorized_code. Creating on-demand issuance session`,\n {\n tokenPayload,\n }\n )\n\n // Use global config when creating a dynamic session\n if (config.dpopRequired && !resourceRequestResult.dpop) {\n return sendUnauthorizedError(\n response,\n next,\n agentContext.config.logger,\n new Oauth2ResourceUnauthorizedError('Missing required DPoP proof', {\n scheme,\n error: Oauth2ErrorCodes.InvalidDpopProof,\n })\n )\n }\n\n const configurationsForScope = getCredentialConfigurationsSupportedForScopes(\n issuerMetadata.credentialIssuer.credential_configurations_supported,\n tokenPayload.scope?.split(' ') ?? []\n )\n\n // All credential configurations that match the request scope and credential request\n // This is just so we don't create an issuance session that will fail immediately after\n let configurationsForToken: CredentialConfigurationsSupportedWithFormats = {}\n\n if (parsedCredentialRequest.credentialConfigurationId && parsedCredentialRequest.credentialConfiguration) {\n if (configurationsForScope[parsedCredentialRequest.credentialConfigurationId]) {\n configurationsForToken = {\n [parsedCredentialRequest.credentialConfigurationId]: parsedCredentialRequest.credentialConfiguration,\n }\n }\n } else if (parsedCredentialRequest.format) {\n configurationsForToken = getCredentialConfigurationsMatchingRequestFormat({\n issuerMetadata,\n requestFormat: parsedCredentialRequest.format,\n })\n }\n\n if (Object.keys(configurationsForToken).length === 0) {\n return sendUnauthorizedError(\n response,\n next,\n agentContext.config.logger,\n new Oauth2ResourceUnauthorizedError(\n 'No credential configurations match credential request and access token scope',\n {\n scheme,\n error: Oauth2ErrorCodes.InsufficientScope,\n }\n ),\n // Forbidden for InsufficientScope\n 403\n )\n }\n\n const createdAt = new Date()\n const expiresAt = utils.addSecondsToDate(createdAt, config.statefulCredentialOfferExpirationInSeconds)\n\n issuanceSession = new OpenId4VcIssuanceSessionRecord({\n createdAt,\n expiresAt,\n credentialOfferPayload: {\n credential_configuration_ids: Object.keys(configurationsForToken),\n credential_issuer: issuerMetadata.credentialIssuer.credential_issuer,\n },\n credentialOfferId: utils.uuid(),\n issuerId: issuer.issuerId,\n state: OpenId4VcIssuanceSessionState.CredentialRequestReceived,\n clientId: tokenPayload.client_id,\n dpop: config.dpopRequired\n ? {\n required: true,\n }\n : undefined,\n authorization: {\n subject: tokenPayload.sub,\n },\n openId4VciVersion:\n issuerMetadata.originalDraftVersion === Openid4vciVersion.V1\n ? 'v1'\n : issuerMetadata.originalDraftVersion === Openid4vciVersion.Draft15\n ? 'v1.draft15'\n : 'v1.draft11-14',\n })\n\n // Save and update\n await issuanceSessionRepository.save(agentContext, issuanceSession)\n openId4VcIssuerService.emitStateChangedEvent(agentContext, issuanceSession, null)\n } else if (!issuanceSession) {\n return sendOauth2ErrorResponse(\n response,\n next,\n agentContext.config.logger,\n new Oauth2ServerErrorResponseError(\n {\n error: Oauth2ErrorCodes.CredentialRequestDenied,\n },\n {\n internalMessage: `Access token without 'issuer_state' or 'pre-authorized_code' issued by external authorization server provided, but 'allowDynamicIssuanceSessions' is disabled. Either bind the access token to a stateful credential offer, or enable 'allowDynamicIssuanceSessions'.`,\n }\n )\n )\n }\n\n try {\n const { credentialResponse } = await openId4VcIssuerService.createCredentialResponse(agentContext, {\n issuanceSession,\n credentialRequest,\n authorization: {\n authorizationServer,\n accessToken: {\n payload: tokenPayload,\n value: accessToken,\n },\n },\n })\n\n return sendJsonResponse(\n response,\n next,\n credentialResponse,\n undefined,\n credentialResponse.transaction_id ? 202 : 200\n )\n } catch (error) {\n if (error instanceof Oauth2ServerErrorResponseError) {\n return sendOauth2ErrorResponse(response, next, agentContext.config.logger, error)\n }\n if (error instanceof Oauth2ResourceUnauthorizedError) {\n return sendUnauthorizedError(response, next, agentContext.config.logger, error)\n }\n\n return sendUnknownServerErrorResponse(response, next, agentContext.config.logger, error)\n }\n })\n}\n"],"mappings":"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"}
|
|
@@ -6,7 +6,7 @@ import "../../shared/index.mjs";
|
|
|
6
6
|
import { OpenId4VcIssuanceSessionState } from "../OpenId4VcIssuanceSessionState.mjs";
|
|
7
7
|
import { OpenId4VcIssuerService } from "../OpenId4VcIssuerService.mjs";
|
|
8
8
|
import { Kms, TypedArrayEncoder, joinUriParts } from "@credo-ts/core";
|
|
9
|
-
import { Oauth2ClientErrorResponseError, Oauth2ErrorCodes, Oauth2ServerErrorResponseError,
|
|
9
|
+
import { Oauth2ClientErrorResponseError, Oauth2ErrorCodes, Oauth2ServerErrorResponseError, verifyIdTokenJwt } from "@openid4vc/oauth2";
|
|
10
10
|
import { addSecondsToDate } from "@openid4vc/utils";
|
|
11
11
|
|
|
12
12
|
//#region src/openid4vc-issuer/router/redirectEndpoint.ts
|
|
@@ -15,9 +15,12 @@ function configureRedirectEndpoint(router, config) {
|
|
|
15
15
|
const { agentContext, issuer } = getRequestContext(request);
|
|
16
16
|
const openId4VcIssuerService = agentContext.dependencyManager.resolve(OpenId4VcIssuerService);
|
|
17
17
|
const issuerMetadata = await openId4VcIssuerService.getIssuerMetadata(agentContext, issuer);
|
|
18
|
+
const oauth2Client = openId4VcIssuerService.getOauth2Client(agentContext, issuer);
|
|
19
|
+
const authorizationServerIssuer = issuerMetadata.authorizationServers[0].issuer;
|
|
18
20
|
let issuanceSession = null;
|
|
19
21
|
try {
|
|
20
|
-
const
|
|
22
|
+
const fullRequestUrl = joinUriParts(issuerMetadata.credentialIssuer.credential_issuer, [request.originalUrl]);
|
|
23
|
+
const authorizationResponse = oauth2Client.parseAuthorizationResponseRedirectUrl({ url: fullRequestUrl });
|
|
21
24
|
if (!authorizationResponse.state) throw new Oauth2ServerErrorResponseError({
|
|
22
25
|
error: Oauth2ErrorCodes.ServerError,
|
|
23
26
|
error_description: `Missing required 'state' parameter`
|
|
@@ -30,16 +33,19 @@ function configureRedirectEndpoint(router, config) {
|
|
|
30
33
|
error: Oauth2ErrorCodes.InvalidRequest,
|
|
31
34
|
error_description: `Invalid 'state' parameter`
|
|
32
35
|
}, { internalMessage: !issuanceSession ? `Issuance session not found for identity chaining 'state' parameter '${authorizationResponse.state}'` : `Issuance session '${issuanceSession.id}' has state '${issuanceSession.state}' but expected ${OpenId4VcIssuanceSessionState.AuthorizationInitiated}` });
|
|
33
|
-
if (!issuanceSession.chainedIdentity?.externalAuthorizationServerUrl || !issuanceSession.chainedIdentity.redirectUri) throw new Oauth2ServerErrorResponseError({
|
|
36
|
+
if (!issuanceSession.chainedIdentity?.externalAuthorizationServerUrl || !issuanceSession.chainedIdentity?.externalAuthorizationServerMetadata || !issuanceSession.chainedIdentity.redirectUri) throw new Oauth2ServerErrorResponseError({
|
|
34
37
|
error: Oauth2ErrorCodes.InvalidRequest,
|
|
35
38
|
error_description: "The session is invalid or has expired."
|
|
36
39
|
}, { internalMessage: `Issuance session '${issuanceSession.id}' does not have identity chaining configured, so it's not compatible with the redirect endpoint.` });
|
|
40
|
+
oauth2Client.verifyAuthorizationResponse({
|
|
41
|
+
authorizationResponse,
|
|
42
|
+
authorizationServerMetadata: issuanceSession.chainedIdentity.externalAuthorizationServerMetadata
|
|
43
|
+
});
|
|
37
44
|
if (authorizationResponse.error) throw new Oauth2ServerErrorResponseError(authorizationResponse);
|
|
38
45
|
if (!authorizationResponse.code) throw new Oauth2ServerErrorResponseError({
|
|
39
46
|
error: Oauth2ErrorCodes.ServerError,
|
|
40
47
|
error_description: `Missing required 'error' or 'code' parameter`
|
|
41
48
|
});
|
|
42
|
-
const oauth2Client = openId4VcIssuerService.getOauth2Client(agentContext, issuer);
|
|
43
49
|
const authorizationServerUrl = issuanceSession.chainedIdentity.externalAuthorizationServerUrl;
|
|
44
50
|
const authorizationServerConfig = issuer.chainedAuthorizationServerConfigs?.find((config$1) => config$1.issuer === authorizationServerUrl);
|
|
45
51
|
if (!authorizationServerConfig) throw new Oauth2ServerErrorResponseError({ error: Oauth2ErrorCodes.ServerError }, { internalMessage: `Issuer '${issuer.issuerId}' does not have a chained authorization server config for issuer '${authorizationServerUrl}'` });
|
|
@@ -89,6 +95,7 @@ function configureRedirectEndpoint(router, config) {
|
|
|
89
95
|
const authorizationCode = TypedArrayEncoder.toBase64URL(kms.randomBytes({ length: 32 }));
|
|
90
96
|
const authorizationCodeExpiresAt = addSecondsToDate(/* @__PURE__ */ new Date(), config.authorizationCodeExpiresInSeconds);
|
|
91
97
|
const redirectUri = new URL(issuanceSession.chainedIdentity.redirectUri);
|
|
98
|
+
redirectUri.searchParams.set("iss", authorizationServerIssuer);
|
|
92
99
|
redirectUri.searchParams.set("code", authorizationCode);
|
|
93
100
|
if (issuanceSession.chainedIdentity.state) redirectUri.searchParams.set("state", issuanceSession.chainedIdentity.state);
|
|
94
101
|
issuanceSession.authorization = {
|
|
@@ -107,6 +114,7 @@ function configureRedirectEndpoint(router, config) {
|
|
|
107
114
|
if (issuanceSession?.chainedIdentity?.redirectUri) {
|
|
108
115
|
const redirectUri = new URL(issuanceSession.chainedIdentity.redirectUri);
|
|
109
116
|
redirectUri.searchParams.set("error", error.errorResponse.error);
|
|
117
|
+
redirectUri.searchParams.set("iss", authorizationServerIssuer);
|
|
110
118
|
if (error.errorResponse.error_description) redirectUri.searchParams.set("error_description", error.errorResponse.error_description);
|
|
111
119
|
if (issuanceSession.chainedIdentity.state) redirectUri.searchParams.set("state", issuanceSession.chainedIdentity.state);
|
|
112
120
|
agentContext.config.logger.warn(`[OID4VC] Sending oauth2 error response: ${JSON.stringify(error.message)}`, { error });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redirectEndpoint.mjs","names":["issuanceSession: OpenId4VcIssuanceSessionRecord | null","config"],"sources":["../../../src/openid4vc-issuer/router/redirectEndpoint.ts"],"sourcesContent":["import { joinUriParts, Kms, TypedArrayEncoder } from '@credo-ts/core'\nimport {\n Oauth2ClientErrorResponseError,\n Oauth2ErrorCodes,\n Oauth2ServerErrorResponseError,\n parseAuthorizationResponseRedirectUrl,\n verifyIdTokenJwt,\n} from '@openid4vc/oauth2'\nimport { addSecondsToDate } from '@openid4vc/utils'\nimport type { NextFunction, Response, Router } from 'express'\nimport { getOid4vcCallbacks } from '../../shared'\nimport { getRequestContext, sendOauth2ErrorResponse, sendUnknownServerErrorResponse } from '../../shared/router'\nimport { OpenId4VcIssuanceSessionState } from '../OpenId4VcIssuanceSessionState'\nimport { OpenId4VcIssuerModuleConfig } from '../OpenId4VcIssuerModuleConfig'\nimport { OpenId4VcIssuerService } from '../OpenId4VcIssuerService'\nimport type { OpenId4VcIssuanceSessionRecord } from '../repository'\nimport type { OpenId4VcIssuanceRequest } from './requestContext'\n\nexport function configureRedirectEndpoint(router: Router, config: OpenId4VcIssuerModuleConfig) {\n router.get(\n config.redirectEndpoint,\n async (request: OpenId4VcIssuanceRequest, response: Response, next: NextFunction) => {\n const requestContext = getRequestContext(request)\n const { agentContext, issuer } = requestContext\n const openId4VcIssuerService = agentContext.dependencyManager.resolve(OpenId4VcIssuerService)\n const issuerMetadata = await openId4VcIssuerService.getIssuerMetadata(agentContext, issuer)\n\n let issuanceSession: OpenId4VcIssuanceSessionRecord | null = null\n try {\n const fullRequestUrl = joinUriParts(issuerMetadata.credentialIssuer.credential_issuer, [request.originalUrl])\n const authorizationResponse = parseAuthorizationResponseRedirectUrl({\n url: fullRequestUrl,\n })\n\n if (!authorizationResponse.state) {\n throw new Oauth2ServerErrorResponseError({\n // Server error because it's an error of the external IDP\n error: Oauth2ErrorCodes.ServerError,\n error_description: `Missing required 'state' parameter`,\n })\n }\n\n issuanceSession = await openId4VcIssuerService.findSingleIssuanceSessionByQuery(agentContext, {\n issuerId: issuer.issuerId,\n chainedIdentityState: authorizationResponse.state,\n })\n\n if (!issuanceSession || issuanceSession.state !== OpenId4VcIssuanceSessionState.AuthorizationInitiated) {\n throw new Oauth2ServerErrorResponseError(\n {\n error: Oauth2ErrorCodes.InvalidRequest,\n error_description: `Invalid 'state' parameter`,\n },\n {\n internalMessage: !issuanceSession\n ? `Issuance session not found for identity chaining 'state' parameter '${authorizationResponse.state}'`\n : `Issuance session '${issuanceSession.id}' has state '${\n issuanceSession.state\n }' but expected ${OpenId4VcIssuanceSessionState.AuthorizationInitiated}`,\n }\n )\n }\n\n if (\n !issuanceSession.chainedIdentity?.externalAuthorizationServerUrl ||\n !issuanceSession.chainedIdentity.redirectUri\n ) {\n throw new Oauth2ServerErrorResponseError(\n {\n error: Oauth2ErrorCodes.InvalidRequest,\n error_description: 'The session is invalid or has expired.',\n },\n {\n internalMessage: `Issuance session '${issuanceSession.id}' does not have identity chaining configured, so it's not compatible with the redirect endpoint.`,\n }\n )\n }\n\n // Throw the error. This will be caught and processed below.\n if (authorizationResponse.error) {\n throw new Oauth2ServerErrorResponseError(authorizationResponse)\n }\n\n if (!authorizationResponse.code) {\n throw new Oauth2ServerErrorResponseError({\n error: Oauth2ErrorCodes.ServerError,\n error_description: `Missing required 'error' or 'code' parameter`,\n })\n }\n\n const oauth2Client = openId4VcIssuerService.getOauth2Client(agentContext, issuer)\n const authorizationServerUrl = issuanceSession.chainedIdentity.externalAuthorizationServerUrl\n const authorizationServerConfig = issuer.chainedAuthorizationServerConfigs?.find(\n (config) => config.issuer === authorizationServerUrl\n )\n if (!authorizationServerConfig) {\n throw new Oauth2ServerErrorResponseError(\n {\n error: Oauth2ErrorCodes.ServerError,\n },\n {\n internalMessage: `Issuer '${issuer.issuerId}' does not have a chained authorization server config for issuer '${authorizationServerUrl}'`,\n }\n )\n }\n\n const authorizationServerMetadata = await oauth2Client.fetchAuthorizationServerMetadata(\n authorizationServerConfig.issuer\n )\n if (!authorizationServerMetadata) {\n throw new Oauth2ServerErrorResponseError(\n {\n error: Oauth2ErrorCodes.ServerError,\n error_description: `Unable to retrieve authorization server metadata from external identity provider.`,\n },\n {\n internalMessage: `Unable to retrieve authorization server metadata from '${authorizationServerConfig.issuer}'`,\n }\n )\n }\n\n // Retrieve access token\n // TODO: add support for DPoP\n const { accessTokenResponse } = await oauth2Client\n .retrieveAuthorizationCodeAccessToken({\n authorizationCode: authorizationResponse.code,\n authorizationServerMetadata,\n pkceCodeVerifier: issuanceSession.chainedIdentity.pkceCodeVerifier,\n redirectUri: joinUriParts(config.baseUrl, [issuer.issuerId, 'redirect']),\n })\n .catch((error) => {\n if (error instanceof Oauth2ClientErrorResponseError) {\n switch (error.errorResponse.error) {\n case Oauth2ErrorCodes.InvalidGrant:\n throw new Oauth2ServerErrorResponseError(\n {\n error: Oauth2ErrorCodes.InvalidGrant,\n },\n {\n internalMessage: `Invalid authorization code received from '${authorizationServerMetadata.issuer}'.`,\n cause: error,\n }\n )\n case Oauth2ErrorCodes.AccessDenied:\n throw new Oauth2ServerErrorResponseError(\n {\n error: Oauth2ErrorCodes.AccessDenied,\n },\n {\n internalMessage: `The request has been denied by the user at '${authorizationServerMetadata.issuer}'.`,\n cause: error,\n }\n )\n }\n }\n\n throw new Oauth2ServerErrorResponseError(\n {\n error: Oauth2ErrorCodes.ServerError,\n error_description: 'Error processing authorization code',\n },\n {\n internalMessage: `Error occurred during retrieval of access token from ${authorizationServerMetadata.issuer}.`,\n cause: error,\n }\n )\n })\n\n // Verify the ID Token if 'openid' scope was requested\n if (accessTokenResponse.scope?.split(' ').includes('openid')) {\n const idToken = accessTokenResponse.id_token\n if (typeof idToken !== 'string') {\n throw new Oauth2ServerErrorResponseError(\n {\n error: Oauth2ErrorCodes.ServerError,\n error_description: `Missing 'id_token' in access token response`,\n },\n {\n internalMessage: `id_token is missing from access token response from ${authorizationServerMetadata.issuer} even though 'openid' scope was requested.`,\n }\n )\n }\n\n await verifyIdTokenJwt({\n idToken,\n authorizationServer: authorizationServerMetadata,\n clientId: authorizationServerConfig.clientAuthentication.clientId,\n callbacks: getOid4vcCallbacks(agentContext),\n })\n }\n\n // Grant authorization\n const kms = agentContext.resolve(Kms.KeyManagementApi)\n const authorizationCode = TypedArrayEncoder.toBase64URL(kms.randomBytes({ length: 32 }))\n const authorizationCodeExpiresAt = addSecondsToDate(new Date(), config.authorizationCodeExpiresInSeconds)\n\n const redirectUri = new URL(issuanceSession.chainedIdentity.redirectUri)\n redirectUri.searchParams.set('code', authorizationCode)\n\n if (issuanceSession.chainedIdentity.state) {\n redirectUri.searchParams.set('state', issuanceSession.chainedIdentity.state)\n }\n\n // Update authorization information\n issuanceSession.authorization = {\n ...issuanceSession.authorization,\n code: authorizationCode,\n codeExpiresAt: authorizationCodeExpiresAt,\n }\n\n // Store access token response\n issuanceSession.chainedIdentity = {\n ...issuanceSession.chainedIdentity,\n externalAccessTokenResponse: accessTokenResponse,\n }\n\n // TODO: we need to start using locks so we can't get corrupted state\n await openId4VcIssuerService.updateState(\n agentContext,\n issuanceSession,\n OpenId4VcIssuanceSessionState.AuthorizationGranted\n )\n\n return response.redirect(redirectUri.toString())\n } catch (error) {\n if (error instanceof Oauth2ServerErrorResponseError) {\n // Redirect to the redirect URI if available.\n if (issuanceSession?.chainedIdentity?.redirectUri) {\n const redirectUri = new URL(issuanceSession.chainedIdentity.redirectUri)\n redirectUri.searchParams.set('error', error.errorResponse.error)\n if (error.errorResponse.error_description) {\n redirectUri.searchParams.set('error_description', error.errorResponse.error_description)\n }\n if (issuanceSession.chainedIdentity.state) {\n redirectUri.searchParams.set('state', issuanceSession.chainedIdentity.state)\n }\n\n agentContext.config.logger.warn(\n `[OID4VC] Sending oauth2 error response: ${JSON.stringify(error.message)}`,\n {\n error,\n }\n )\n\n return response.redirect(redirectUri.toString())\n }\n\n return sendOauth2ErrorResponse(response, next, agentContext.config.logger, error)\n }\n\n return sendUnknownServerErrorResponse(response, next, agentContext.config.logger, error)\n }\n }\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"redirectEndpoint.mjs","names":["issuanceSession: OpenId4VcIssuanceSessionRecord | null","config"],"sources":["../../../src/openid4vc-issuer/router/redirectEndpoint.ts"],"sourcesContent":["import { joinUriParts, Kms, TypedArrayEncoder } from '@credo-ts/core'\nimport {\n Oauth2ClientErrorResponseError,\n Oauth2ErrorCodes,\n Oauth2ServerErrorResponseError,\n verifyIdTokenJwt,\n} from '@openid4vc/oauth2'\nimport { addSecondsToDate } from '@openid4vc/utils'\nimport type { NextFunction, Response, Router } from 'express'\nimport { getOid4vcCallbacks } from '../../shared'\nimport { getRequestContext, sendOauth2ErrorResponse, sendUnknownServerErrorResponse } from '../../shared/router'\nimport { OpenId4VcIssuanceSessionState } from '../OpenId4VcIssuanceSessionState'\nimport { OpenId4VcIssuerModuleConfig } from '../OpenId4VcIssuerModuleConfig'\nimport { OpenId4VcIssuerService } from '../OpenId4VcIssuerService'\nimport type { OpenId4VcIssuanceSessionRecord } from '../repository'\nimport type { OpenId4VcIssuanceRequest } from './requestContext'\n\nexport function configureRedirectEndpoint(router: Router, config: OpenId4VcIssuerModuleConfig) {\n router.get(\n config.redirectEndpoint,\n async (request: OpenId4VcIssuanceRequest, response: Response, next: NextFunction) => {\n const requestContext = getRequestContext(request)\n const { agentContext, issuer } = requestContext\n const openId4VcIssuerService = agentContext.dependencyManager.resolve(OpenId4VcIssuerService)\n const issuerMetadata = await openId4VcIssuerService.getIssuerMetadata(agentContext, issuer)\n const oauth2Client = openId4VcIssuerService.getOauth2Client(agentContext, issuer)\n const authorizationServerIssuer = issuerMetadata.authorizationServers[0].issuer\n\n let issuanceSession: OpenId4VcIssuanceSessionRecord | null = null\n try {\n const fullRequestUrl = joinUriParts(issuerMetadata.credentialIssuer.credential_issuer, [request.originalUrl])\n\n const authorizationResponse = oauth2Client.parseAuthorizationResponseRedirectUrl({\n url: fullRequestUrl,\n })\n\n if (!authorizationResponse.state) {\n throw new Oauth2ServerErrorResponseError({\n // Server error because it's an error of the external IDP\n error: Oauth2ErrorCodes.ServerError,\n error_description: `Missing required 'state' parameter`,\n })\n }\n\n issuanceSession = await openId4VcIssuerService.findSingleIssuanceSessionByQuery(agentContext, {\n issuerId: issuer.issuerId,\n chainedIdentityState: authorizationResponse.state,\n })\n\n if (!issuanceSession || issuanceSession.state !== OpenId4VcIssuanceSessionState.AuthorizationInitiated) {\n throw new Oauth2ServerErrorResponseError(\n {\n error: Oauth2ErrorCodes.InvalidRequest,\n error_description: `Invalid 'state' parameter`,\n },\n {\n internalMessage: !issuanceSession\n ? `Issuance session not found for identity chaining 'state' parameter '${authorizationResponse.state}'`\n : `Issuance session '${issuanceSession.id}' has state '${\n issuanceSession.state\n }' but expected ${OpenId4VcIssuanceSessionState.AuthorizationInitiated}`,\n }\n )\n }\n\n if (\n !issuanceSession.chainedIdentity?.externalAuthorizationServerUrl ||\n !issuanceSession.chainedIdentity?.externalAuthorizationServerMetadata ||\n !issuanceSession.chainedIdentity.redirectUri\n ) {\n throw new Oauth2ServerErrorResponseError(\n {\n error: Oauth2ErrorCodes.InvalidRequest,\n error_description: 'The session is invalid or has expired.',\n },\n {\n internalMessage: `Issuance session '${issuanceSession.id}' does not have identity chaining configured, so it's not compatible with the redirect endpoint.`,\n }\n )\n }\n\n oauth2Client.verifyAuthorizationResponse({\n authorizationResponse,\n authorizationServerMetadata: issuanceSession.chainedIdentity.externalAuthorizationServerMetadata,\n })\n\n // Throw the error. This will be caught and processed below.\n if (authorizationResponse.error) {\n throw new Oauth2ServerErrorResponseError(authorizationResponse)\n }\n\n if (!authorizationResponse.code) {\n throw new Oauth2ServerErrorResponseError({\n error: Oauth2ErrorCodes.ServerError,\n error_description: `Missing required 'error' or 'code' parameter`,\n })\n }\n\n const authorizationServerUrl = issuanceSession.chainedIdentity.externalAuthorizationServerUrl\n const authorizationServerConfig = issuer.chainedAuthorizationServerConfigs?.find(\n (config) => config.issuer === authorizationServerUrl\n )\n if (!authorizationServerConfig) {\n throw new Oauth2ServerErrorResponseError(\n {\n error: Oauth2ErrorCodes.ServerError,\n },\n {\n internalMessage: `Issuer '${issuer.issuerId}' does not have a chained authorization server config for issuer '${authorizationServerUrl}'`,\n }\n )\n }\n\n const authorizationServerMetadata = await oauth2Client.fetchAuthorizationServerMetadata(\n authorizationServerConfig.issuer\n )\n if (!authorizationServerMetadata) {\n throw new Oauth2ServerErrorResponseError(\n {\n error: Oauth2ErrorCodes.ServerError,\n error_description: `Unable to retrieve authorization server metadata from external identity provider.`,\n },\n {\n internalMessage: `Unable to retrieve authorization server metadata from '${authorizationServerConfig.issuer}'`,\n }\n )\n }\n\n // Retrieve access token\n // TODO: add support for DPoP\n const { accessTokenResponse } = await oauth2Client\n .retrieveAuthorizationCodeAccessToken({\n authorizationCode: authorizationResponse.code,\n authorizationServerMetadata,\n pkceCodeVerifier: issuanceSession.chainedIdentity.pkceCodeVerifier,\n redirectUri: joinUriParts(config.baseUrl, [issuer.issuerId, 'redirect']),\n })\n .catch((error) => {\n if (error instanceof Oauth2ClientErrorResponseError) {\n switch (error.errorResponse.error) {\n case Oauth2ErrorCodes.InvalidGrant:\n throw new Oauth2ServerErrorResponseError(\n {\n error: Oauth2ErrorCodes.InvalidGrant,\n },\n {\n internalMessage: `Invalid authorization code received from '${authorizationServerMetadata.issuer}'.`,\n cause: error,\n }\n )\n case Oauth2ErrorCodes.AccessDenied:\n throw new Oauth2ServerErrorResponseError(\n {\n error: Oauth2ErrorCodes.AccessDenied,\n },\n {\n internalMessage: `The request has been denied by the user at '${authorizationServerMetadata.issuer}'.`,\n cause: error,\n }\n )\n }\n }\n\n throw new Oauth2ServerErrorResponseError(\n {\n error: Oauth2ErrorCodes.ServerError,\n error_description: 'Error processing authorization code',\n },\n {\n internalMessage: `Error occurred during retrieval of access token from ${authorizationServerMetadata.issuer}.`,\n cause: error,\n }\n )\n })\n\n // Verify the ID Token if 'openid' scope was requested\n if (accessTokenResponse.scope?.split(' ').includes('openid')) {\n const idToken = accessTokenResponse.id_token\n if (typeof idToken !== 'string') {\n throw new Oauth2ServerErrorResponseError(\n {\n error: Oauth2ErrorCodes.ServerError,\n error_description: `Missing 'id_token' in access token response`,\n },\n {\n internalMessage: `id_token is missing from access token response from ${authorizationServerMetadata.issuer} even though 'openid' scope was requested.`,\n }\n )\n }\n\n await verifyIdTokenJwt({\n idToken,\n authorizationServer: authorizationServerMetadata,\n clientId: authorizationServerConfig.clientAuthentication.clientId,\n callbacks: getOid4vcCallbacks(agentContext),\n })\n }\n\n // Grant authorization\n const kms = agentContext.resolve(Kms.KeyManagementApi)\n const authorizationCode = TypedArrayEncoder.toBase64URL(kms.randomBytes({ length: 32 }))\n const authorizationCodeExpiresAt = addSecondsToDate(new Date(), config.authorizationCodeExpiresInSeconds)\n\n const redirectUri = new URL(issuanceSession.chainedIdentity.redirectUri)\n\n // First authorization server is the internal authorization server (always used with chained authorization)\n redirectUri.searchParams.set('iss', authorizationServerIssuer)\n redirectUri.searchParams.set('code', authorizationCode)\n\n if (issuanceSession.chainedIdentity.state) {\n redirectUri.searchParams.set('state', issuanceSession.chainedIdentity.state)\n }\n\n // Update authorization information\n issuanceSession.authorization = {\n ...issuanceSession.authorization,\n code: authorizationCode,\n codeExpiresAt: authorizationCodeExpiresAt,\n }\n\n // Store access token response\n issuanceSession.chainedIdentity = {\n ...issuanceSession.chainedIdentity,\n externalAccessTokenResponse: accessTokenResponse,\n }\n\n // TODO: we need to start using locks so we can't get corrupted state\n await openId4VcIssuerService.updateState(\n agentContext,\n issuanceSession,\n OpenId4VcIssuanceSessionState.AuthorizationGranted\n )\n\n return response.redirect(redirectUri.toString())\n } catch (error) {\n if (error instanceof Oauth2ServerErrorResponseError) {\n // Redirect to the redirect URI if available.\n if (issuanceSession?.chainedIdentity?.redirectUri) {\n const redirectUri = new URL(issuanceSession.chainedIdentity.redirectUri)\n redirectUri.searchParams.set('error', error.errorResponse.error)\n redirectUri.searchParams.set('iss', authorizationServerIssuer)\n if (error.errorResponse.error_description) {\n redirectUri.searchParams.set('error_description', error.errorResponse.error_description)\n }\n if (issuanceSession.chainedIdentity.state) {\n redirectUri.searchParams.set('state', issuanceSession.chainedIdentity.state)\n }\n\n agentContext.config.logger.warn(\n `[OID4VC] Sending oauth2 error response: ${JSON.stringify(error.message)}`,\n {\n error,\n }\n )\n\n return response.redirect(redirectUri.toString())\n }\n\n return sendOauth2ErrorResponse(response, next, agentContext.config.logger, error)\n }\n\n return sendUnknownServerErrorResponse(response, next, agentContext.config.logger, error)\n }\n }\n )\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
".": "./build/index.mjs",
|
|
5
5
|
"./package.json": "./package.json"
|
|
6
6
|
},
|
|
7
|
-
"version": "0.6.2-
|
|
7
|
+
"version": "0.6.2-pr-2610-20260107224500",
|
|
8
8
|
"files": [
|
|
9
9
|
"build"
|
|
10
10
|
],
|
|
@@ -30,18 +30,18 @@
|
|
|
30
30
|
"class-transformer": "0.5.1",
|
|
31
31
|
"rxjs": "^7.8.2",
|
|
32
32
|
"zod": "^4.1.12",
|
|
33
|
-
"@openid4vc/openid4vci": "^0.4.
|
|
34
|
-
"@openid4vc/oauth2": "^0.4.
|
|
35
|
-
"@openid4vc/openid4vp": "^0.4.
|
|
36
|
-
"@openid4vc/utils": "^0.4.
|
|
33
|
+
"@openid4vc/openid4vci": "^0.4.4",
|
|
34
|
+
"@openid4vc/oauth2": "^0.4.4",
|
|
35
|
+
"@openid4vc/openid4vp": "^0.4.4",
|
|
36
|
+
"@openid4vc/utils": "^0.4.4",
|
|
37
37
|
"@types/express": "^5.0.6",
|
|
38
38
|
"express": "^5.2.0",
|
|
39
|
-
"@credo-ts/core": "0.6.2-
|
|
39
|
+
"@credo-ts/core": "0.6.2-pr-2610-20260107224500"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"nock": "^14.0.10",
|
|
43
43
|
"typescript": "~5.9.3",
|
|
44
|
-
"@credo-ts/tenants": "0.6.2-
|
|
44
|
+
"@credo-ts/tenants": "0.6.2-pr-2610-20260107224500"
|
|
45
45
|
},
|
|
46
46
|
"scripts": {
|
|
47
47
|
"build": "tsdown --config-loader unconfig"
|