{"version":3,"file":"OpenId4VcIssuanceSessionRecord.mjs","names":[],"sources":["../../../src/openid4vc-issuer/repository/OpenId4VcIssuanceSessionRecord.ts"],"sourcesContent":["import type { RecordTags, TagsBase } from '@credo-ts/core'\nimport { BaseRecord, CredoError, DateTransformer, isJsonObject, Kms, utils } from '@credo-ts/core'\nimport { type AccessTokenResponse, type AuthorizationServerMetadata, PkceCodeChallengeMethod } from '@openid4vc/oauth2'\nimport { Transform, TransformationType } from 'class-transformer'\nimport type { OpenId4VciCredentialOfferPayload, VerifiedOpenId4VcCredentialHolderBinding } from '../../shared'\nimport { OpenId4VcIssuanceSessionState } from '../OpenId4VcIssuanceSessionState'\nimport type { OpenId4VciVersion } from '../OpenId4VcIssuerServiceOptions'\n\nexport type OpenId4VcIssuanceSessionRecordTags = RecordTags<OpenId4VcIssuanceSessionRecord>\n\nexport interface OpenId4VcIssuanceSessionDpop {\n  /**\n   * Whether dpop is required. Can be set to false to override the\n   * global config\n   */\n  required: boolean\n\n  /**\n   * JWK thumbprint of the dpop key. This is mostly used when a dpop key is bound\n   * to the issuance session before the access token is created (which contains the dpop key)\n   */\n  dpopJkt?: string\n}\n\nexport interface OpenId4VcIssuanceSessionWalletAttestation {\n  /**\n   * Whether presentation of a wallet attestation is required.\n   * Can be set to false to override the global config\n   */\n  required: boolean\n}\n\nexport interface OpenId4VcIssuanceSessionAuthorization {\n  code?: string\n\n  /**\n   * @todo: I saw in google's library that for codes they encrypt an id with expiration time.\n   * You know the code was created by you because you can decrypt it, and you don't have to store\n   * additional metadata on your server. It's similar to the signed / encrypted nonce\n   */\n  codeExpiresAt?: Date\n\n  /**\n   * String value created by the Credential Issuer and opaque to the Wallet that\n   * is used to bind the subsequent Authorization Request with the Credential Issuer to a context set up during previous steps.\n   */\n  issuerState?: string\n\n  /**\n   * Scopes that are granted when the authorization is complete.\n   */\n  scopes?: string[]\n\n  /**\n   * Subject the issuance session is bound to. For internal authorization this will be defined\n   * from the moment the token is issued. For external authorization this will be defined after\n   * the first time the credential endpoint has been called.\n   */\n  subject?: string\n}\n\nexport interface OpenId4VcIssuanceSessionPresentation {\n  /**\n   * Whether presentation during issuance is required. Mutually exclusive with `chainedIdentity`.\n   */\n  required: true\n\n  /**\n   * Auth session for the presentation during issuance flow\n   */\n  authSession?: string\n\n  /**\n   * The id of the `OpenId4VcVerificationSessionRecord` record this issuance session is linked to\n   */\n  openId4VcVerificationSessionId?: string\n}\n\nexport interface OpenId4VcIssuanceSessionPkce {\n  codeChallengeMethod: PkceCodeChallengeMethod\n  codeChallenge: string\n}\n\nexport interface OpenId4VcIssuanceSessionChainedIdentity {\n  /**\n   * The identifier of the external identity provider's authorization server.\n   * Mutually exclusive with `presentation`.\n   */\n  externalAuthorizationServerUrl: string\n\n  /**\n   * The <reference-value> from the `request_uri` parameter returned to the client\n   * in the form of `urn:ietf:params:oauth:request_uri:<reference-value>`.\n   */\n  requestUriReferenceValue?: string\n\n  /**\n   * The expiry time of the request URI.\n   *\n   * @todo: I saw in google's library that for codes they encrypt an id with expiration time.\n   * You know the code was created by you because you can decrypt it, and you don't have to store\n   * additional metadata on your server. It's similar to the signed / encrypted nonce\n   */\n  requestUriExpiresAt?: Date\n\n  /**\n   * The state value that was received in the pushed authorization request.\n   */\n  state?: string\n\n  /**\n   * The redirect uri to redirect to after the authorization code has been granted.\n   */\n  redirectUri?: string\n\n  /**\n   * The PKCE code verifier used in the authorization request to the external identity provider.\n   */\n  pkceCodeVerifier?: string\n\n  /**\n   * The chained identity authorization request url, used to authorize to the external identity provider.\n   */\n  externalAuthorizationRequestUrl?: string\n\n  /**\n   * The state value used in the authorization request to the external identity provider.\n   */\n  externalState?: string\n\n  /**\n   * The metadata of the external identity provider's authorization server.\n   */\n  externalAuthorizationServerMetadata?: AuthorizationServerMetadata\n\n  /**\n   * The access token response received from the external identity provider.\n   *\n   * If the scope 'openid' is requested, we automatically verify if the\n   * ID Token JWT is valid.\n   */\n  externalAccessTokenResponse?: AccessTokenResponse\n}\n\nexport type DefaultOpenId4VcIssuanceSessionRecordTags = {\n  issuerId: string\n  cNonce?: string\n  state: OpenId4VcIssuanceSessionState\n  credentialOfferUri?: string\n  credentialOfferId?: string\n\n  // pre-auth flow\n  preAuthorizedCode?: string\n\n  // auth flow\n  authorizationCode?: string\n  issuerState?: string\n\n  authorizationSubject?: string\n\n  // presentation during issuance\n  presentationAuthSession?: string\n\n  // identity chaining\n  chainedIdentityRequestUriReferenceValue?: string\n  chainedIdentityState?: string\n}\n\nexport interface OpenId4VcIssuanceSessionRecordTransaction {\n  transactionId: string\n\n  // The expected number of credentials that will be issued in this transaction\n  numberOfCredentials: number\n\n  // The credential configuration that is used for this transaction.\n  credentialConfigurationId: string\n\n  /**\n   * The holder binding that should be used for the credentials in this\n   * transaction.\n   *\n   * @since 0.6.3\n   */\n  holderBinding?: VerifiedOpenId4VcCredentialHolderBinding\n\n  /**\n   * The time until which this transaction is deferred. This is based on\n   * the previously returned interval.\n   *\n   * @since 0.6.3\n   */\n  deferredUntil?: Date\n}\n\nexport interface OpenId4VcIssuanceSessionRecordProps {\n  createdAt: Date\n  expiresAt: Date\n\n  id?: string\n  tags?: TagsBase\n\n  state: OpenId4VcIssuanceSessionState\n  issuerId: string\n\n  /**\n   * Client id will mostly be used when doing auth flow\n   */\n  clientId?: string\n\n  walletAttestation?: OpenId4VcIssuanceSessionWalletAttestation\n  dpop?: OpenId4VcIssuanceSessionDpop\n\n  // Pre auth flow\n  preAuthorizedCode?: string\n  userPin?: string\n\n  // Auth flow (move to authorization?)\n  pkce?: {\n    codeChallengeMethod: PkceCodeChallengeMethod\n    codeChallenge: string\n  }\n\n  /**\n   * When authorization code flow is used, this links the authorization\n   */\n  authorization?: OpenId4VcIssuanceSessionAuthorization\n\n  /**\n   * When presentation during issuance is required this should link the\n   * `OpenId4VcVerificationSessionRecord` and state\n   */\n  presentation?: OpenId4VcIssuanceSessionPresentation\n\n  // Transaction data for deferred credential issuances\n  transactions?: OpenId4VcIssuanceSessionRecordTransaction[]\n\n  /**\n   * Identity chaining enables doing another OAuth2 authentication flow as part\n   * of the OpenID4VCI authorization flow. This allows leveraging the advanced OAuth2\n   * functionality from Credo (e.g. Wallet Attestations, DPoP, PAR) while still allowing\n   * integration with existing IDPs.\n   */\n  chainedIdentity?: OpenId4VcIssuanceSessionChainedIdentity\n\n  credentialOfferUri?: string\n  credentialOfferId: string\n\n  credentialOfferPayload: OpenId4VciCredentialOfferPayload\n\n  issuanceMetadata?: Record<string, unknown>\n  errorMessage?: string\n\n  generateRefreshTokens?: boolean\n\n  /**\n   * The version of openid4ci used for the request\n   */\n  openId4VciVersion: OpenId4VciVersion\n}\n\nexport class OpenId4VcIssuanceSessionRecord extends BaseRecord<DefaultOpenId4VcIssuanceSessionRecordTags> {\n  public static readonly type = 'OpenId4VcIssuanceSessionRecord'\n  public readonly type = OpenId4VcIssuanceSessionRecord.type\n\n  /**\n   * Expiry time for the issuance session. This can change dynamically during\n   * the session lifetime, based on the possible deferrals.\n   *\n   * @since 0.6\n   */\n  @DateTransformer()\n  public expiresAt?: Date\n\n  /**\n   * The id of the issuer that this session is for.\n   */\n  public issuerId!: string\n\n  /**\n   * The state of the issuance session.\n   */\n  @Transform(({ value }) => {\n    // CredentialIssued is an old state that is no longer used. It should be mapped to Error.\n    if (value === 'CredentialIssued') {\n      return OpenId4VcIssuanceSessionState.Error\n    }\n\n    return value\n  })\n  public state!: OpenId4VcIssuanceSessionState\n\n  /**\n   * The credentials that were issued during this session.\n   */\n  public issuedCredentials: string[] = []\n\n  /**\n   * The credential transactions for deferred credentials.\n   */\n  @Transform(({ type, value }) => {\n    if (!Array.isArray(value)) {\n      return value\n    }\n\n    return value.map((transaction) => {\n      if (\n        type === TransformationType.PLAIN_TO_CLASS &&\n        isJsonObject(transaction) &&\n        isJsonObject(transaction.holderBinding) &&\n        Array.isArray(transaction.holderBinding.keys)\n      ) {\n        return {\n          ...transaction,\n          ...(typeof transaction.deferredUntil === 'string'\n            ? { deferredUntil: new Date(transaction.deferredUntil) }\n            : {}),\n          holderBinding: {\n            ...transaction.holderBinding,\n            keys: transaction.holderBinding.keys.map((key) => {\n              if (!isJsonObject(key)) {\n                throw new CredoError('Key is not json object.')\n              }\n\n              return {\n                ...key,\n                jwk: Kms.PublicJwk.fromUnknown(key.jwk),\n              }\n            }),\n          },\n        }\n      }\n      if (\n        type === TransformationType.CLASS_TO_PLAIN &&\n        isJsonObject(transaction) &&\n        isJsonObject(transaction.holderBinding) &&\n        Array.isArray(transaction.holderBinding.keys)\n      ) {\n        return {\n          ...transaction,\n          ...(transaction.deferredUntil instanceof Date\n            ? { deferredUntil: transaction.deferredUntil.toISOString() }\n            : {}),\n          holderBinding: {\n            ...transaction.holderBinding,\n            keys: transaction.holderBinding.keys.map((key) => {\n              if (!isJsonObject(key)) {\n                throw new CredoError('Key is not json object.')\n              }\n\n              if (!(key.jwk instanceof Kms.PublicJwk)) {\n                throw new CredoError('JWK is not instance of PublicKey')\n              }\n\n              return {\n                ...key,\n                jwk: key.jwk.toJson(),\n              }\n            }),\n          },\n        }\n      }\n\n      return transaction\n    })\n  })\n  public transactions: OpenId4VcIssuanceSessionRecordTransaction[] = []\n\n  /**\n   * Pre authorized code used for the issuance session. Only used when a pre-authorized credential\n   * offer is created.\n   */\n  public preAuthorizedCode?: string\n\n  /**\n   * Optional user pin that needs to be provided by the user in the access token request.\n   */\n  public userPin?: string\n\n  /**\n   * Client id of the exchange\n   */\n  public clientId?: string\n\n  /**\n   * Proof Key Code Exchange\n   */\n  public pkce?: OpenId4VcIssuanceSessionPkce\n\n  walletAttestation?: OpenId4VcIssuanceSessionWalletAttestation\n  dpop?: OpenId4VcIssuanceSessionDpop\n\n  /**\n   * Authorization code flow specific metadata values\n   */\n  @Transform(({ type, value }) => {\n    if (type === TransformationType.PLAIN_TO_CLASS && isJsonObject(value) && typeof value.codeExpiresAt === 'string') {\n      return {\n        ...value,\n        codeExpiresAt: new Date(value.codeExpiresAt),\n      }\n    }\n    if (type === TransformationType.CLASS_TO_CLASS && isJsonObject(value) && value.codeExpiresAt instanceof Date) {\n      return {\n        ...value,\n        codeExpiresAt: new Date(value.codeExpiresAt.getTime()),\n      }\n    }\n    if (type === TransformationType.CLASS_TO_PLAIN && isJsonObject(value) && value.codeExpiresAt instanceof Date) {\n      return {\n        ...value,\n        codeExpiresAt: value.codeExpiresAt.toISOString(),\n      }\n    }\n\n    return value\n  })\n  public authorization?: OpenId4VcIssuanceSessionAuthorization\n\n  /**\n   * Presentation during issuance specific metadata values\n   */\n  public presentation?: OpenId4VcIssuanceSessionPresentation\n\n  /**\n   * Chained identity specific metadata values\n   */\n  @Transform(({ type, value }) => {\n    if (\n      type === TransformationType.PLAIN_TO_CLASS &&\n      isJsonObject(value) &&\n      typeof value.requestUriExpiresAt === 'string'\n    ) {\n      return {\n        ...value,\n        requestUriExpiresAt: new Date(value.requestUriExpiresAt),\n      }\n    }\n    if (\n      type === TransformationType.CLASS_TO_CLASS &&\n      isJsonObject(value) &&\n      value.requestUriExpiresAt instanceof Date\n    ) {\n      return {\n        ...value,\n        requestUriExpiresAt: new Date(value.requestUriExpiresAt.getTime()),\n      }\n    }\n    if (\n      type === TransformationType.CLASS_TO_PLAIN &&\n      isJsonObject(value) &&\n      value.requestUriExpiresAt instanceof Date\n    ) {\n      return {\n        ...value,\n        requestUriExpiresAt: value.requestUriExpiresAt.toISOString(),\n      }\n    }\n\n    return value\n  })\n  public chainedIdentity?: OpenId4VcIssuanceSessionChainedIdentity\n\n  /**\n   * User-defined metadata that will be provided to the credential request to credential mapper\n   * to allow to retrieve the needed credential input data. Can be the credential data itself,\n   * or some other data that is needed to retrieve the credential data.\n   */\n  public issuanceMetadata?: Record<string, unknown>\n\n  /**\n   * The credential offer that was used to create the issuance session.\n   */\n  public credentialOfferPayload!: OpenId4VciCredentialOfferPayload\n\n  /**\n   * URI of the credential offer. This is the url that cn can be used to retrieve\n   * the credential offer\n   */\n  public credentialOfferUri?: string\n\n  /**\n   * The public id for the credential offer. This is used in the credential\n   * offer uri.\n   *\n   * @since 0.6\n   */\n  public credentialOfferId?: string\n\n  /**\n   * Whether to generate refresh tokens for the issuance session.\n   *\n   * @since 0.6\n   */\n  public generateRefreshTokens?: boolean\n\n  /**\n   * The version of openid4ci used for the request\n   *\n   * @since 0.6\n   */\n  public openId4VciVersion?: OpenId4VciVersion\n\n  /**\n   * Optional error message of the error that occurred during the issuance session. Will be set when state is {@link OpenId4VcIssuanceSessionState.Error}\n   */\n  public errorMessage?: string\n\n  public constructor(props: OpenId4VcIssuanceSessionRecordProps) {\n    super()\n\n    if (props) {\n      this.id = props.id ?? utils.uuid()\n      this.createdAt = props.createdAt\n      this.expiresAt = props.expiresAt\n      this._tags = props.tags ?? {}\n\n      this.issuerId = props.issuerId\n      this.clientId = props.clientId\n      this.userPin = props.userPin\n      this.preAuthorizedCode = props.preAuthorizedCode\n      this.pkce = props.pkce\n      this.authorization = props.authorization\n      this.presentation = props.presentation\n      this.chainedIdentity = props.chainedIdentity\n      this.credentialOfferUri = props.credentialOfferUri\n      this.credentialOfferId = props.credentialOfferId\n      this.credentialOfferPayload = props.credentialOfferPayload\n      this.issuanceMetadata = props.issuanceMetadata\n      this.dpop = props.dpop\n      this.walletAttestation = props.walletAttestation\n      this.state = props.state\n      this.generateRefreshTokens = props.generateRefreshTokens\n      this.errorMessage = props.errorMessage\n      this.transactions = props.transactions ?? []\n      this.openId4VciVersion = props.openId4VciVersion\n    }\n  }\n\n  public assertState(expectedStates: OpenId4VcIssuanceSessionState | OpenId4VcIssuanceSessionState[]) {\n    if (!Array.isArray(expectedStates)) {\n      expectedStates = [expectedStates]\n    }\n\n    if (!expectedStates.includes(this.state)) {\n      throw new CredoError(\n        `OpenId4VcIssuanceSessionRecord is in invalid state ${this.state}. Valid states are: ${expectedStates.join(\n          ', '\n        )}.`\n      )\n    }\n  }\n\n  public getTags() {\n    return {\n      ...this._tags,\n      issuerId: this.issuerId,\n      credentialOfferUri: this.credentialOfferUri,\n      credentialOfferId: this.credentialOfferId,\n      state: this.state,\n\n      // Pre-auth flow\n      preAuthorizedCode: this.preAuthorizedCode,\n\n      // Auth flow\n      issuerState: this.authorization?.issuerState,\n      authorizationCode: this.authorization?.code,\n\n      authorizationSubject: this.authorization?.subject,\n\n      // Presentation during issuance\n      presentationAuthSession: this.presentation?.authSession,\n\n      // Chained identity\n      chainedIdentityRequestUriReferenceValue: this.chainedIdentity?.requestUriReferenceValue,\n      chainedIdentityState: this.chainedIdentity?.externalState,\n    }\n  }\n}\n"],"mappings":"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"}