@credo-ts/openid4vc 0.6.1-alpha-20251208081705 → 0.6.1-alpha-20251208140426
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.
|
@@ -20,6 +20,10 @@ async function handlePushedAuthorizationRequest(agentContext, options) {
|
|
|
20
20
|
error: Oauth2ErrorCodes.InvalidScope,
|
|
21
21
|
error_description: `Missing required 'scope' parameter`
|
|
22
22
|
});
|
|
23
|
+
if (!parsedAuthorizationRequest.authorizationRequest.redirect_uri) throw new Oauth2ServerErrorResponseError({
|
|
24
|
+
error: Oauth2ErrorCodes.InvalidScope,
|
|
25
|
+
error_description: `Missing required 'redirect_uri' parameter.`
|
|
26
|
+
});
|
|
23
27
|
const allowedStates = [OpenId4VcIssuanceSessionState.OfferCreated, OpenId4VcIssuanceSessionState.OfferUriRetrieved];
|
|
24
28
|
if (!allowedStates.includes(issuanceSession.state)) throw new Oauth2ServerErrorResponseError({
|
|
25
29
|
error: Oauth2ErrorCodes.InvalidRequest,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pushedAuthorizationRequestEndpoint.mjs","names":["config"],"sources":["../../../src/openid4vc-issuer/router/pushedAuthorizationRequestEndpoint.ts"],"sourcesContent":["import { AgentContext, joinUriParts, utils } from '@credo-ts/core'\nimport type { HttpMethod, ParsePushedAuthorizationRequestResult, RequestLike } from '@openid4vc/oauth2'\nimport {\n Oauth2ErrorCodes,\n Oauth2ServerErrorResponseError,\n pushedAuthorizationRequestUriPrefix,\n} from '@openid4vc/oauth2'\nimport { addSecondsToDate } from '@openid4vc/utils'\nimport type { NextFunction, Response, Router } from 'express'\nimport type { OpenId4VciCredentialConfigurationsSupportedWithFormats } from '../../shared'\nimport {\n getAllowedAndRequestedScopeValues,\n getCredentialConfigurationsSupportedForScopes,\n getOfferedCredentials,\n getScopesFromCredentialConfigurationsSupported,\n} from '../../shared'\nimport {\n getRequestContext,\n sendJsonResponse,\n sendOauth2ErrorResponse,\n sendUnknownServerErrorResponse,\n} from '../../shared/router'\nimport { OpenId4VcIssuanceSessionState } from '../OpenId4VcIssuanceSessionState'\nimport { OpenId4VcIssuerModuleConfig } from '../OpenId4VcIssuerModuleConfig'\nimport { OpenId4VcIssuerService } from '../OpenId4VcIssuerService'\nimport { OpenId4VcIssuanceSessionRecord, OpenId4VcIssuerRecord } from '../repository'\nimport type { OpenId4VcIssuanceRequest } from './requestContext'\n\nexport async function handlePushedAuthorizationRequest(\n agentContext: AgentContext,\n options: {\n issuer: OpenId4VcIssuerRecord\n issuanceSession: OpenId4VcIssuanceSessionRecord\n parsedAuthorizationRequest: ParsePushedAuthorizationRequestResult\n request: RequestLike\n }\n) {\n const openId4VcIssuerService = agentContext.dependencyManager.resolve(OpenId4VcIssuerService)\n const config = agentContext.dependencyManager.resolve(OpenId4VcIssuerModuleConfig)\n\n const { issuanceSession, parsedAuthorizationRequest, issuer, request } = options\n const issuerMetadata = await openId4VcIssuerService.getIssuerMetadata(agentContext, issuer)\n\n if (!parsedAuthorizationRequest.authorizationRequest.scope) {\n throw new Oauth2ServerErrorResponseError({\n error: Oauth2ErrorCodes.InvalidScope,\n error_description: `Missing required 'scope' parameter`,\n })\n }\n\n const allowedStates = [OpenId4VcIssuanceSessionState.OfferCreated, OpenId4VcIssuanceSessionState.OfferUriRetrieved]\n if (!allowedStates.includes(issuanceSession.state)) {\n throw new Oauth2ServerErrorResponseError(\n {\n error: Oauth2ErrorCodes.InvalidRequest,\n error_description: `Invalid 'issuer_state' parameter`,\n },\n {\n internalMessage: `Issuance session '${issuanceSession.id}' has state '${\n issuanceSession.state\n }' but expected one of ${allowedStates.join(', ')}`,\n }\n )\n }\n\n if (!issuanceSession.chainedIdentity?.externalAuthorizationServerUrl) {\n throw new Oauth2ServerErrorResponseError(\n {\n error: Oauth2ErrorCodes.InvalidRequest,\n error_description: `Unexpected redirect call for session.`,\n },\n {\n internalMessage: `Issuance session '${issuanceSession.id}' is not configured to use chained identity for authorization.`,\n }\n )\n }\n\n const authorizationServer = openId4VcIssuerService.getOauth2AuthorizationServer(agentContext, {\n issuanceSessionId: issuanceSession.id,\n })\n\n const { clientAttestation, dpop } = await authorizationServer.verifyPushedAuthorizationRequest({\n authorizationRequest: parsedAuthorizationRequest.authorizationRequest,\n // First authorization server is the internal authorization server\n authorizationServerMetadata: issuerMetadata.authorizationServers[0],\n request,\n clientAttestation: {\n ...parsedAuthorizationRequest.clientAttestation,\n // First session config, fall back to global config\n required: issuanceSession.walletAttestation?.required ?? config.walletAttestationsRequired,\n },\n dpop: {\n ...parsedAuthorizationRequest.dpop,\n // First session config, fall back to global config\n required: issuanceSession.dpop?.required ?? config.dpopRequired,\n },\n })\n\n if (dpop) {\n // Bind dpop jwk thumbprint to session\n issuanceSession.dpop = {\n // If dpop is provided at the start, it's required from now on.\n required: true,\n dpopJkt: dpop.jwkThumbprint,\n }\n }\n\n if (clientAttestation) {\n issuanceSession.walletAttestation = {\n // If client attestation is provided at the start, it's required from now on.\n required: true,\n }\n }\n\n const offeredCredentialConfigurations = getOfferedCredentials(\n issuanceSession.credentialOfferPayload.credential_configuration_ids,\n issuerMetadata.credentialIssuer.credential_configurations_supported\n )\n const allowedScopes = getScopesFromCredentialConfigurationsSupported(offeredCredentialConfigurations)\n const requestedScopes = getAllowedAndRequestedScopeValues({\n allowedScopes,\n requestedScope: parsedAuthorizationRequest.authorizationRequest.scope,\n })\n const requestedCredentialConfigurations = getCredentialConfigurationsSupportedForScopes(\n offeredCredentialConfigurations,\n requestedScopes\n ) as OpenId4VciCredentialConfigurationsSupportedWithFormats\n if (requestedScopes.length === 0 || Object.keys(requestedCredentialConfigurations).length === 0) {\n throw new Oauth2ServerErrorResponseError({\n error: Oauth2ErrorCodes.InvalidScope,\n error_description: `No requested 'scope' values match with offered credential configurations.`,\n })\n }\n\n issuanceSession.authorization = {\n ...issuanceSession.authorization,\n scopes: requestedScopes,\n }\n\n // If client attestation is used we have verified this client_id matches with the sub\n // of the wallet attestation\n issuanceSession.clientId =\n clientAttestation?.clientAttestation.payload.sub ?? parsedAuthorizationRequest.authorizationRequest.client_id\n\n const oauth2Client = openId4VcIssuerService.getOauth2Client(agentContext, issuer)\n const authorizationServerUrl = issuanceSession.chainedIdentity.externalAuthorizationServerUrl\n\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 const redirectUri = joinUriParts(config.baseUrl, [issuer.issuerId, 'redirect'])\n const chainedIdentityState = utils.uuid()\n\n const scopes = requestedScopes.flatMap((scope) => {\n if (scope in authorizationServerConfig.scopesMapping) {\n return authorizationServerConfig.scopesMapping[scope]\n }\n\n throw new Oauth2ServerErrorResponseError(\n {\n error: Oauth2ErrorCodes.ServerError,\n },\n {\n internalMessage: `Issuer '${issuer.issuerId}' does not have a scope mapping for scope '${scope}' for external authorization server '${authorizationServerConfig.issuer}'`,\n }\n )\n })\n\n // TODO: add support for DPoP\n const { authorizationRequestUrl, pkce } = await oauth2Client.initiateAuthorization({\n authorizationServerMetadata,\n clientId: authorizationServerConfig.clientAuthentication.clientId,\n redirectUri,\n state: chainedIdentityState,\n scope: scopes.join(' '),\n })\n\n issuanceSession.chainedIdentity = {\n ...issuanceSession.chainedIdentity,\n requestUriReferenceValue: utils.uuid(),\n externalAuthorizationRequestUrl: authorizationRequestUrl,\n requestUriExpiresAt: addSecondsToDate(new Date(), config.requestUriExpiresInSeconds),\n pkceCodeVerifier: pkce?.codeVerifier,\n externalState: chainedIdentityState,\n state: parsedAuthorizationRequest.authorizationRequest.state,\n redirectUri: parsedAuthorizationRequest.authorizationRequest.redirect_uri,\n externalAuthorizationServerMetadata: authorizationServerMetadata,\n }\n\n const { pushedAuthorizationResponse } = authorizationServer.createPushedAuthorizationResponse({\n expiresInSeconds: config.requestUriExpiresInSeconds,\n requestUri: pushedAuthorizationRequestUriPrefix + issuanceSession.chainedIdentity.requestUriReferenceValue,\n })\n\n await openId4VcIssuerService.updateState(\n agentContext,\n issuanceSession,\n OpenId4VcIssuanceSessionState.AuthorizationInitiated\n )\n\n return { pushedAuthorizationResponse }\n}\n\nexport function configurePushedAuthorizationRequestEndpoint(router: Router, config: OpenId4VcIssuerModuleConfig) {\n router.post(\n config.pushedAuthorizationRequestEndpoint,\n async (request: OpenId4VcIssuanceRequest, response: Response, next: NextFunction) => {\n const requestContext = getRequestContext(request)\n const { agentContext, issuer } = requestContext\n\n try {\n const config = agentContext.dependencyManager.resolve(OpenId4VcIssuerModuleConfig)\n const openId4VcIssuerService = agentContext.dependencyManager.resolve(OpenId4VcIssuerService)\n const issuerMetadata = await openId4VcIssuerService.getIssuerMetadata(agentContext, issuer)\n const authorizationServer = openId4VcIssuerService.getOauth2AuthorizationServer(agentContext)\n const fullRequestUrl = joinUriParts(issuerMetadata.credentialIssuer.credential_issuer, [\n config.pushedAuthorizationRequestEndpoint,\n ])\n\n const requestLike = {\n headers: new Headers(request.headers as Record<string, string>),\n method: request.method as HttpMethod,\n url: fullRequestUrl,\n } as const\n\n const parseResult = await authorizationServer.parsePushedAuthorizationRequest({\n authorizationRequest: request.body,\n request: requestLike,\n })\n\n if (parseResult.authorizationRequestJwt) {\n throw new Oauth2ServerErrorResponseError({\n error: Oauth2ErrorCodes.InvalidRequest,\n error_description: `Using JWT-secured authorization request is not supported.`,\n })\n }\n\n // TODO: we could allow dynamic issuance sessions here. Maybe based on a callback?\n // Not sure how to decide which credentials are allowed to be requested dynamically\n if (!parseResult.authorizationRequest.issuer_state) {\n throw new Oauth2ServerErrorResponseError({\n error: Oauth2ErrorCodes.InvalidRequest,\n error_description: `Missing required 'issuer_state' parameter. Only requests initiated by a credential offer are supported for pushed authorization requests.`,\n })\n }\n\n if (!parseResult.authorizationRequest.scope) {\n throw new Oauth2ServerErrorResponseError({\n error: Oauth2ErrorCodes.InvalidScope,\n error_description: `Missing required 'scope' parameter`,\n })\n }\n\n const issuanceSession = await openId4VcIssuerService.findSingleIssuanceSessionByQuery(agentContext, {\n issuerId: issuer.issuerId,\n issuerState: parseResult.authorizationRequest.issuer_state,\n })\n\n if (!issuanceSession) {\n throw new Oauth2ServerErrorResponseError(\n {\n error: Oauth2ErrorCodes.InvalidRequest,\n error_description: `Invalid 'issuer_state' parameter`,\n },\n {\n internalMessage: `Issuance session not found for 'issuer_state' parameter '${parseResult.authorizationRequest.issuer_state}'`,\n }\n )\n }\n\n const { pushedAuthorizationResponse } = await handlePushedAuthorizationRequest(agentContext, {\n issuanceSession,\n issuer,\n parsedAuthorizationRequest: parseResult,\n request: requestLike,\n })\n\n return sendJsonResponse(response, next, pushedAuthorizationResponse)\n } catch (error) {\n if (error instanceof Oauth2ServerErrorResponseError) {\n return sendOauth2ErrorResponse(response, next, agentContext.config.logger, error)\n }\n return sendUnknownServerErrorResponse(response, next, agentContext.config.logger, error)\n }\n }\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"pushedAuthorizationRequestEndpoint.mjs","names":["config"],"sources":["../../../src/openid4vc-issuer/router/pushedAuthorizationRequestEndpoint.ts"],"sourcesContent":["import { AgentContext, joinUriParts, utils } from '@credo-ts/core'\nimport type { HttpMethod, ParsePushedAuthorizationRequestResult, RequestLike } from '@openid4vc/oauth2'\nimport {\n Oauth2ErrorCodes,\n Oauth2ServerErrorResponseError,\n pushedAuthorizationRequestUriPrefix,\n} from '@openid4vc/oauth2'\nimport { addSecondsToDate } from '@openid4vc/utils'\nimport type { NextFunction, Response, Router } from 'express'\nimport type { OpenId4VciCredentialConfigurationsSupportedWithFormats } from '../../shared'\nimport {\n getAllowedAndRequestedScopeValues,\n getCredentialConfigurationsSupportedForScopes,\n getOfferedCredentials,\n getScopesFromCredentialConfigurationsSupported,\n} from '../../shared'\nimport {\n getRequestContext,\n sendJsonResponse,\n sendOauth2ErrorResponse,\n sendUnknownServerErrorResponse,\n} from '../../shared/router'\nimport { OpenId4VcIssuanceSessionState } from '../OpenId4VcIssuanceSessionState'\nimport { OpenId4VcIssuerModuleConfig } from '../OpenId4VcIssuerModuleConfig'\nimport { OpenId4VcIssuerService } from '../OpenId4VcIssuerService'\nimport { OpenId4VcIssuanceSessionRecord, OpenId4VcIssuerRecord } from '../repository'\nimport type { OpenId4VcIssuanceRequest } from './requestContext'\n\nexport async function handlePushedAuthorizationRequest(\n agentContext: AgentContext,\n options: {\n issuer: OpenId4VcIssuerRecord\n issuanceSession: OpenId4VcIssuanceSessionRecord\n parsedAuthorizationRequest: ParsePushedAuthorizationRequestResult\n request: RequestLike\n }\n) {\n const openId4VcIssuerService = agentContext.dependencyManager.resolve(OpenId4VcIssuerService)\n const config = agentContext.dependencyManager.resolve(OpenId4VcIssuerModuleConfig)\n\n const { issuanceSession, parsedAuthorizationRequest, issuer, request } = options\n const issuerMetadata = await openId4VcIssuerService.getIssuerMetadata(agentContext, issuer)\n\n if (!parsedAuthorizationRequest.authorizationRequest.scope) {\n throw new Oauth2ServerErrorResponseError({\n error: Oauth2ErrorCodes.InvalidScope,\n error_description: `Missing required 'scope' parameter`,\n })\n }\n\n if (!parsedAuthorizationRequest.authorizationRequest.redirect_uri) {\n throw new Oauth2ServerErrorResponseError({\n error: Oauth2ErrorCodes.InvalidScope,\n error_description: `Missing required 'redirect_uri' parameter.`,\n })\n }\n\n const allowedStates = [OpenId4VcIssuanceSessionState.OfferCreated, OpenId4VcIssuanceSessionState.OfferUriRetrieved]\n if (!allowedStates.includes(issuanceSession.state)) {\n throw new Oauth2ServerErrorResponseError(\n {\n error: Oauth2ErrorCodes.InvalidRequest,\n error_description: `Invalid 'issuer_state' parameter`,\n },\n {\n internalMessage: `Issuance session '${issuanceSession.id}' has state '${\n issuanceSession.state\n }' but expected one of ${allowedStates.join(', ')}`,\n }\n )\n }\n\n if (!issuanceSession.chainedIdentity?.externalAuthorizationServerUrl) {\n throw new Oauth2ServerErrorResponseError(\n {\n error: Oauth2ErrorCodes.InvalidRequest,\n error_description: `Unexpected redirect call for session.`,\n },\n {\n internalMessage: `Issuance session '${issuanceSession.id}' is not configured to use chained identity for authorization.`,\n }\n )\n }\n\n const authorizationServer = openId4VcIssuerService.getOauth2AuthorizationServer(agentContext, {\n issuanceSessionId: issuanceSession.id,\n })\n\n const { clientAttestation, dpop } = await authorizationServer.verifyPushedAuthorizationRequest({\n authorizationRequest: parsedAuthorizationRequest.authorizationRequest,\n // First authorization server is the internal authorization server\n authorizationServerMetadata: issuerMetadata.authorizationServers[0],\n request,\n clientAttestation: {\n ...parsedAuthorizationRequest.clientAttestation,\n // First session config, fall back to global config\n required: issuanceSession.walletAttestation?.required ?? config.walletAttestationsRequired,\n },\n dpop: {\n ...parsedAuthorizationRequest.dpop,\n // First session config, fall back to global config\n required: issuanceSession.dpop?.required ?? config.dpopRequired,\n },\n })\n\n if (dpop) {\n // Bind dpop jwk thumbprint to session\n issuanceSession.dpop = {\n // If dpop is provided at the start, it's required from now on.\n required: true,\n dpopJkt: dpop.jwkThumbprint,\n }\n }\n\n if (clientAttestation) {\n issuanceSession.walletAttestation = {\n // If client attestation is provided at the start, it's required from now on.\n required: true,\n }\n }\n\n const offeredCredentialConfigurations = getOfferedCredentials(\n issuanceSession.credentialOfferPayload.credential_configuration_ids,\n issuerMetadata.credentialIssuer.credential_configurations_supported\n )\n const allowedScopes = getScopesFromCredentialConfigurationsSupported(offeredCredentialConfigurations)\n const requestedScopes = getAllowedAndRequestedScopeValues({\n allowedScopes,\n requestedScope: parsedAuthorizationRequest.authorizationRequest.scope,\n })\n const requestedCredentialConfigurations = getCredentialConfigurationsSupportedForScopes(\n offeredCredentialConfigurations,\n requestedScopes\n ) as OpenId4VciCredentialConfigurationsSupportedWithFormats\n if (requestedScopes.length === 0 || Object.keys(requestedCredentialConfigurations).length === 0) {\n throw new Oauth2ServerErrorResponseError({\n error: Oauth2ErrorCodes.InvalidScope,\n error_description: `No requested 'scope' values match with offered credential configurations.`,\n })\n }\n\n issuanceSession.authorization = {\n ...issuanceSession.authorization,\n scopes: requestedScopes,\n }\n\n // If client attestation is used we have verified this client_id matches with the sub\n // of the wallet attestation\n issuanceSession.clientId =\n clientAttestation?.clientAttestation.payload.sub ?? parsedAuthorizationRequest.authorizationRequest.client_id\n\n const oauth2Client = openId4VcIssuerService.getOauth2Client(agentContext, issuer)\n const authorizationServerUrl = issuanceSession.chainedIdentity.externalAuthorizationServerUrl\n\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 const redirectUri = joinUriParts(config.baseUrl, [issuer.issuerId, 'redirect'])\n const chainedIdentityState = utils.uuid()\n\n const scopes = requestedScopes.flatMap((scope) => {\n if (scope in authorizationServerConfig.scopesMapping) {\n return authorizationServerConfig.scopesMapping[scope]\n }\n\n throw new Oauth2ServerErrorResponseError(\n {\n error: Oauth2ErrorCodes.ServerError,\n },\n {\n internalMessage: `Issuer '${issuer.issuerId}' does not have a scope mapping for scope '${scope}' for external authorization server '${authorizationServerConfig.issuer}'`,\n }\n )\n })\n\n // TODO: add support for DPoP\n const { authorizationRequestUrl, pkce } = await oauth2Client.initiateAuthorization({\n authorizationServerMetadata,\n clientId: authorizationServerConfig.clientAuthentication.clientId,\n redirectUri,\n state: chainedIdentityState,\n scope: scopes.join(' '),\n })\n\n issuanceSession.chainedIdentity = {\n ...issuanceSession.chainedIdentity,\n requestUriReferenceValue: utils.uuid(),\n externalAuthorizationRequestUrl: authorizationRequestUrl,\n requestUriExpiresAt: addSecondsToDate(new Date(), config.requestUriExpiresInSeconds),\n pkceCodeVerifier: pkce?.codeVerifier,\n externalState: chainedIdentityState,\n state: parsedAuthorizationRequest.authorizationRequest.state,\n redirectUri: parsedAuthorizationRequest.authorizationRequest.redirect_uri,\n externalAuthorizationServerMetadata: authorizationServerMetadata,\n }\n\n const { pushedAuthorizationResponse } = authorizationServer.createPushedAuthorizationResponse({\n expiresInSeconds: config.requestUriExpiresInSeconds,\n requestUri: pushedAuthorizationRequestUriPrefix + issuanceSession.chainedIdentity.requestUriReferenceValue,\n })\n\n await openId4VcIssuerService.updateState(\n agentContext,\n issuanceSession,\n OpenId4VcIssuanceSessionState.AuthorizationInitiated\n )\n\n return { pushedAuthorizationResponse }\n}\n\nexport function configurePushedAuthorizationRequestEndpoint(router: Router, config: OpenId4VcIssuerModuleConfig) {\n router.post(\n config.pushedAuthorizationRequestEndpoint,\n async (request: OpenId4VcIssuanceRequest, response: Response, next: NextFunction) => {\n const requestContext = getRequestContext(request)\n const { agentContext, issuer } = requestContext\n\n try {\n const config = agentContext.dependencyManager.resolve(OpenId4VcIssuerModuleConfig)\n const openId4VcIssuerService = agentContext.dependencyManager.resolve(OpenId4VcIssuerService)\n const issuerMetadata = await openId4VcIssuerService.getIssuerMetadata(agentContext, issuer)\n const authorizationServer = openId4VcIssuerService.getOauth2AuthorizationServer(agentContext)\n const fullRequestUrl = joinUriParts(issuerMetadata.credentialIssuer.credential_issuer, [\n config.pushedAuthorizationRequestEndpoint,\n ])\n\n const requestLike = {\n headers: new Headers(request.headers as Record<string, string>),\n method: request.method as HttpMethod,\n url: fullRequestUrl,\n } as const\n\n const parseResult = await authorizationServer.parsePushedAuthorizationRequest({\n authorizationRequest: request.body,\n request: requestLike,\n })\n\n if (parseResult.authorizationRequestJwt) {\n throw new Oauth2ServerErrorResponseError({\n error: Oauth2ErrorCodes.InvalidRequest,\n error_description: `Using JWT-secured authorization request is not supported.`,\n })\n }\n\n // TODO: we could allow dynamic issuance sessions here. Maybe based on a callback?\n // Not sure how to decide which credentials are allowed to be requested dynamically\n if (!parseResult.authorizationRequest.issuer_state) {\n throw new Oauth2ServerErrorResponseError({\n error: Oauth2ErrorCodes.InvalidRequest,\n error_description: `Missing required 'issuer_state' parameter. Only requests initiated by a credential offer are supported for pushed authorization requests.`,\n })\n }\n\n if (!parseResult.authorizationRequest.scope) {\n throw new Oauth2ServerErrorResponseError({\n error: Oauth2ErrorCodes.InvalidScope,\n error_description: `Missing required 'scope' parameter`,\n })\n }\n\n const issuanceSession = await openId4VcIssuerService.findSingleIssuanceSessionByQuery(agentContext, {\n issuerId: issuer.issuerId,\n issuerState: parseResult.authorizationRequest.issuer_state,\n })\n\n if (!issuanceSession) {\n throw new Oauth2ServerErrorResponseError(\n {\n error: Oauth2ErrorCodes.InvalidRequest,\n error_description: `Invalid 'issuer_state' parameter`,\n },\n {\n internalMessage: `Issuance session not found for 'issuer_state' parameter '${parseResult.authorizationRequest.issuer_state}'`,\n }\n )\n }\n\n const { pushedAuthorizationResponse } = await handlePushedAuthorizationRequest(agentContext, {\n issuanceSession,\n issuer,\n parsedAuthorizationRequest: parseResult,\n request: requestLike,\n })\n\n return sendJsonResponse(response, next, pushedAuthorizationResponse)\n } catch (error) {\n if (error instanceof Oauth2ServerErrorResponseError) {\n return sendOauth2ErrorResponse(response, next, agentContext.config.logger, error)\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.1-alpha-
|
|
7
|
+
"version": "0.6.1-alpha-20251208140426",
|
|
8
8
|
"files": [
|
|
9
9
|
"build"
|
|
10
10
|
],
|
|
@@ -36,12 +36,12 @@
|
|
|
36
36
|
"@openid4vc/utils": "^0.4.1",
|
|
37
37
|
"@types/express": "^5.0.6",
|
|
38
38
|
"express": "^5.2.0",
|
|
39
|
-
"@credo-ts/core": "0.6.1-alpha-
|
|
39
|
+
"@credo-ts/core": "0.6.1-alpha-20251208140426"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"nock": "^14.0.10",
|
|
43
43
|
"typescript": "~5.9.3",
|
|
44
|
-
"@credo-ts/tenants": "0.6.1-alpha-
|
|
44
|
+
"@credo-ts/tenants": "0.6.1-alpha-20251208140426"
|
|
45
45
|
},
|
|
46
46
|
"scripts": {
|
|
47
47
|
"build": "tsdown --config-loader unconfig"
|