{"version":3,"file":"deferredCredentialEndpoint.mjs","names":[],"sources":["../../../src/openid4vc-issuer/router/deferredCredentialEndpoint.ts"],"sourcesContent":["import { joinUriParts, utils } from '@credo-ts/core'\nimport type { HttpMethod } from '@openid4vc/oauth2'\nimport { Oauth2ErrorCodes, Oauth2ResourceUnauthorizedError, Oauth2ServerErrorResponseError } from '@openid4vc/oauth2'\nimport type { Response, Router } from 'express'\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 { OpenId4VcIssuanceSessionRepository } from '../repository'\nimport type { OpenId4VcIssuanceRequest } from './requestContext'\n\nexport function configureDeferredCredentialEndpoint(router: Router, config: OpenId4VcIssuerModuleConfig) {\n  router.post(\n    config.deferredCredentialEndpointPath,\n    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.deferredCredentialEndpointPath,\n      ])\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\n      if (!resourceRequestResult) return\n      const { tokenPayload, accessToken, scheme, authorizationServer } = resourceRequestResult\n\n      const deferredCredentialRequest = request.body\n      const issuanceSessionRepository = agentContext.dependencyManager.resolve(OpenId4VcIssuanceSessionRepository)\n\n      const parsedCredentialRequest = vcIssuer.parseDeferredCredentialRequest({\n        deferredCredentialRequest,\n      })\n\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      if (!issuerState && !preAuthorizedCode) {\n        return sendOauth2ErrorResponse(\n          response,\n          next,\n          agentContext.config.logger,\n          new Oauth2ServerErrorResponseError(\n            {\n              error: Oauth2ErrorCodes.InvalidRequest,\n            },\n            {\n              internalMessage: `Received deferred credential request without 'pre-authorized_code' or 'issuer_state' claim. At least one of these claims is required to identify the issuance session`,\n            }\n          )\n        )\n      }\n\n      const issuanceSession = await issuanceSessionRepository.findSingleByQuery(agentContext, {\n        preAuthorizedCode,\n        issuerState,\n      })\n\n      if (!issuanceSession) {\n        agentContext.config.logger.warn(\n          `No issuance session found for incoming deferred 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.InvalidTransactionId,\n            },\n            {\n              internalMessage: `No issuance session found for incoming credential request for issuer ${issuer.issuerId}, access token data and transaction id`,\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      // Verify the issuance session subject\n      if (issuanceSession.authorization?.subject && 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      const expiresAt =\n        issuanceSession.expiresAt ??\n        utils.addSecondsToDate(issuanceSession.createdAt, config.statefulCredentialOfferExpirationInSeconds)\n\n      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      }\n\n      try {\n        const { deferredCredentialResponse } = await openId4VcIssuerService.createDeferredCredentialResponse(\n          agentContext,\n          {\n            issuanceSession,\n            deferredCredentialRequest: parsedCredentialRequest.deferredCredentialRequest,\n            authorization: {\n              authorizationServer,\n              accessToken: {\n                payload: tokenPayload,\n                value: accessToken,\n              },\n            },\n          }\n        )\n\n        return sendJsonResponse(\n          response,\n          next,\n          deferredCredentialResponse,\n          undefined,\n          deferredCredentialResponse.interval ? 202 : 200\n        )\n      } catch (error) {\n        issuanceSession.errorMessage =\n          typeof error.message === 'string' && error.message !== ''\n            ? error.message\n            : 'Failed to create a deferred credential response'\n\n        await openId4VcIssuerService.updateState(agentContext, issuanceSession, OpenId4VcIssuanceSessionState.Error)\n\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}\n"],"mappings":"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"}