{"version":3,"file":"credentialUse.mjs","names":[],"sources":["../../src/utils/credentialUse.ts"],"sourcesContent":["import { AgentContext } from '../agent'\nimport { CredoError } from '../error/CredoError'\nimport { Mdoc } from '../modules/mdoc/Mdoc'\nimport { MdocRecord, type MdocRecordInstances, MdocRepository } from '../modules/mdoc/repository'\nimport { decodeSdJwtVc } from '../modules/sd-jwt-vc/decodeSdJwtVc'\nimport { SdJwtVcRecord, type SdJwtVcRecordInstances, SdJwtVcRepository } from '../modules/sd-jwt-vc/repository'\nimport type { SdJwtVc } from '../modules/sd-jwt-vc/SdJwtVcService'\nimport {\n  W3cJsonLdVerifiableCredential,\n  W3cJwtVerifiableCredential,\n  W3cV2JwtVerifiableCredential,\n  W3cV2SdJwtVerifiableCredential,\n  type W3cV2VerifiableCredential,\n  type W3cVerifiableCredential,\n} from '../modules/vc'\nimport {\n  W3cCredentialRecord,\n  type W3cCredentialRecordInstances,\n  W3cCredentialRepository,\n  W3cV2CredentialRecord,\n  type W3cV2CredentialRecordInstances,\n  W3cV2CredentialRepository,\n} from '../modules/vc/repository'\nimport {\n  CredentialMultiInstanceState,\n  CredentialMultiInstanceUseMode,\n  CredentialMultiInstanceUseUpdateMode,\n} from './credentialUseTypes'\n\nexport { CredentialMultiInstanceUseMode, CredentialMultiInstanceUseUpdateMode, CredentialMultiInstanceState }\n\ntype CredentialRecord = W3cCredentialRecord | SdJwtVcRecord | MdocRecord | W3cV2CredentialRecord\n\ntype CredentialInstanceReturnType<Record extends CredentialRecord> = Record extends W3cCredentialRecord\n  ? W3cVerifiableCredential\n  : Record extends W3cV2CredentialRecord\n    ? W3cV2VerifiableCredential\n    : Record extends SdJwtVcRecord\n      ? SdJwtVc\n      : Record extends MdocRecord\n        ? Mdoc\n        : CredentialRecord\n\ntype CredentialRepositoryForRecord<Record extends CredentialRecord> = Record extends W3cCredentialRecord\n  ? W3cCredentialRepository\n  : Record extends W3cV2CredentialRepository\n    ? W3cV2VerifiableCredential\n    : Record extends SdJwtVcRepository\n      ? SdJwtVc\n      : Record extends MdocRepository\n        ? Mdoc\n        : W3cCredentialRepository | W3cV2CredentialRepository | SdJwtVcRepository | MdocRepository\n\n/**\n * Checks whether an instance can be used from the provided record based on\n * the required credential use mode.\n */\nexport function canUseInstanceFromCredentialRecord({\n  credentialRecord,\n  useMode,\n}: {\n  credentialRecord: CredentialRecord\n  useMode: CredentialMultiInstanceUseMode\n}) {\n  // If we're not required to use a new instance, we can always return the first instance\n  if (\n    useMode === CredentialMultiInstanceUseMode.First ||\n    useMode === CredentialMultiInstanceUseMode.NewOrFirst ||\n    (useMode === CredentialMultiInstanceUseMode.NewIfReceivedInBatch &&\n      [CredentialMultiInstanceState.SingleInstanceUnused, CredentialMultiInstanceState.SingleInstanceUsed].includes(\n        credentialRecord.multiInstanceState\n      ))\n  ) {\n    return true\n  }\n\n  // Otherwise we return whether we can use a new instance\n  return (\n    credentialRecord.multiInstanceState === CredentialMultiInstanceState.MultiInstanceFirstUnused ||\n    credentialRecord.multiInstanceState === CredentialMultiInstanceState.SingleInstanceUnused\n  )\n}\n\nexport interface UseInstanceFromCredentialRecordOptions<Record extends CredentialRecord> {\n  agentContext: AgentContext\n\n  /**\n   * Which mode to use for usage of the credential instance. See {@link CredentialMultiInstanceUseMode} for\n   * more information on the available options\n   */\n  useMode: CredentialMultiInstanceUseMode\n  credentialRecord: Record\n\n  /**\n   * The update mode for the credential record when a new instance is used.\n   *\n   * @default `CredentialMultiInstanceUseUpdateMode.RefetchAndUpdateWithLock`\n   */\n  updateMode?: CredentialMultiInstanceUseUpdateMode\n}\n\nexport interface UseInstanceFromCredentialRecordReturn<Record extends CredentialRecord> {\n  /**\n   * The credential instance with the kms key id\n   */\n  credentialInstance: CredentialInstanceReturnType<Record>\n\n  /**\n   * If the first instance was used, this value will be `true`. The first time\n   * the first instance is used, technically the credential is not reused yet,\n   * but we make no distinction between this.\n   */\n  isReused: boolean\n\n  /**\n   * Whether the last new instance was used. If `isReused` is `true` this value will\n   * always be `false`.\n   *\n   * The last new instance is the second entry in the mdoc instances, since the first\n   * one is reserved for 'reused' usage.\n   */\n  isLastNewInstance: boolean\n}\n\n/**\n * Extract an instance with the correct kms key id from the credential instances\n * on the record. Note that if an instance is extracted (that is not reused) it\n * will remove the instance from the record, and the record should be updated in\n * storage after usage.\n *\n * Note that the last credential instance is never removed from the record. So if the\n * method indicates the last instance has been used, you should remove the credential\n * from storage if you don't want it to be used anymore in the future.\n */\nexport async function useInstanceFromCredentialRecord<Record extends CredentialRecord>({\n  credentialRecord,\n  useMode,\n  agentContext,\n  updateMode,\n}: UseInstanceFromCredentialRecordOptions<Record>): Promise<UseInstanceFromCredentialRecordReturn<Record>> {\n  let extractResult = extractInstanceAndUpdateRecord({\n    credentialRecord,\n    useMode,\n  })\n\n  if (\n    (!extractResult.isReused || extractResult.isLastNewInstance) &&\n    updateMode === CredentialMultiInstanceUseUpdateMode.Update\n  ) {\n    await updateCredentialRecord(agentContext, credentialRecord)\n  }\n\n  if (\n    (!extractResult.isReused || extractResult.isLastNewInstance) &&\n    updateMode === CredentialMultiInstanceUseUpdateMode.RefetchAndUpdateWithLock\n  ) {\n    // the method is generic, but the types don't work well generically\n    const repository = repositoryForRecord(agentContext, credentialRecord) as SdJwtVcRepository\n\n    await repository.updateByIdWithLock(\n      agentContext,\n      credentialRecord.id,\n      async (freshCredentialRecord: SdJwtVcRecord) => {\n        extractResult = extractInstanceAndUpdateRecord({\n          credentialRecord: freshCredentialRecord,\n          useMode,\n        })\n\n        return freshCredentialRecord\n      }\n    )\n  }\n\n  let transformedCredentialInstance: W3cVerifiableCredential | Mdoc | SdJwtVc | W3cV2VerifiableCredential\n  if (credentialRecord instanceof MdocRecord) {\n    const { issuerSignedBase64Url, kmsKeyId } = extractResult.credentialInstance as MdocRecordInstances[0]\n    transformedCredentialInstance = Mdoc.fromBase64Url(issuerSignedBase64Url)\n    transformedCredentialInstance.deviceKeyId = kmsKeyId ?? transformedCredentialInstance.deviceKey.legacyKeyId\n  } else if (credentialRecord instanceof SdJwtVcRecord) {\n    const { compactSdJwtVc, kmsKeyId } = extractResult.credentialInstance as SdJwtVcRecordInstances[0]\n\n    transformedCredentialInstance = {\n      ...decodeSdJwtVc(compactSdJwtVc, credentialRecord.typeMetadata),\n      kmsKeyId,\n    }\n  } else if (credentialRecord instanceof W3cCredentialRecord) {\n    const { credential } = extractResult.credentialInstance as W3cCredentialRecordInstances[0]\n\n    transformedCredentialInstance =\n      typeof credential === 'string'\n        ? W3cJwtVerifiableCredential.fromSerializedJwt(credential)\n        : W3cJsonLdVerifiableCredential.fromJson(credential)\n  } else if (credentialRecord instanceof W3cV2CredentialRecord) {\n    const { credential } = extractResult.credentialInstance as W3cV2CredentialRecordInstances[0]\n\n    transformedCredentialInstance = credential.includes('~')\n      ? W3cV2SdJwtVerifiableCredential.fromCompact(credential)\n      : W3cV2JwtVerifiableCredential.fromCompact(credential)\n  } else {\n    throw new CredoError('Unsupported record type')\n  }\n\n  return {\n    credentialInstance: transformedCredentialInstance as CredentialInstanceReturnType<Record>,\n    isReused: extractResult.isReused,\n    isLastNewInstance: extractResult.isLastNewInstance,\n  }\n}\n\nfunction extractInstanceAndUpdateRecord<Record extends CredentialRecord>({\n  credentialRecord,\n  useMode,\n}: Pick<UseInstanceFromCredentialRecordOptions<Record>, 'credentialRecord' | 'useMode'>) {\n  if (credentialRecord.credentialInstances.length === 1 || useMode === CredentialMultiInstanceUseMode.First) {\n    const isFirstUnused =\n      credentialRecord.multiInstanceState === CredentialMultiInstanceState.MultiInstanceFirstUnused ||\n      credentialRecord.multiInstanceState === CredentialMultiInstanceState.SingleInstanceUnused\n\n    const isMultiInstance =\n      credentialRecord.multiInstanceState === CredentialMultiInstanceState.MultiInstanceFirstUnused ||\n      credentialRecord.multiInstanceState === CredentialMultiInstanceState.MultiInstanceFirstUsed\n\n    const allowsReuse =\n      // New does not allow reuse\n      useMode === CredentialMultiInstanceUseMode.New\n        ? false\n        : // NewIfReceivedInBatch only allows reuse if the credential was not multi instance\n          useMode === CredentialMultiInstanceUseMode.NewIfReceivedInBatch\n          ? !isMultiInstance\n          : // Otherwise we allow reuse (First or NewOrFirst)\n            true\n\n    if (isFirstUnused) {\n      credentialRecord.multiInstanceState =\n        credentialRecord.multiInstanceState === CredentialMultiInstanceState.SingleInstanceUnused\n          ? CredentialMultiInstanceState.SingleInstanceUsed\n          : CredentialMultiInstanceState.MultiInstanceFirstUsed\n    } else if (!allowsReuse) {\n      throw new CredoError(\n        `Unable to extract new credential instance from ${credentialRecord.type} with id '${credentialRecord.id}', since it only contains a single credential instance but using a new instance is required due to use mode '${useMode}'.`\n      )\n    }\n\n    return {\n      isReused: !isFirstUnused,\n      credentialInstance: credentialRecord.credentialInstances[0],\n      isLastNewInstance: isFirstUnused,\n    }\n  }\n\n  // We have multiple instances, so we pop the last one (never the first one)\n  const _credentialInstance = credentialRecord.credentialInstances.pop()\n  if (!_credentialInstance) {\n    throw new CredoError(\n      `Unable to extract credential instance from ${credentialRecord.type} with id '${credentialRecord.id}', since the credential record does not contain any credential instances.`\n    )\n  }\n\n  return {\n    credentialInstance: _credentialInstance,\n    isReused: false,\n    isLastNewInstance: false,\n  }\n}\n\nfunction repositoryForRecord<Record extends CredentialRecord>(\n  agentContext: AgentContext,\n  record: CredentialRecord\n): CredentialRepositoryForRecord<Record> {\n  if (record instanceof W3cCredentialRecord)\n    return agentContext.resolve(W3cCredentialRepository) as CredentialRepositoryForRecord<Record>\n  if (record instanceof W3cV2CredentialRecord)\n    return agentContext.resolve(W3cV2CredentialRepository) as CredentialRepositoryForRecord<Record>\n  if (record instanceof MdocRecord) return agentContext.resolve(MdocRepository) as CredentialRepositoryForRecord<Record>\n  return agentContext.resolve(SdJwtVcRepository) as CredentialRepositoryForRecord<Record>\n}\n\nfunction updateCredentialRecord(agentContext: AgentContext, record: CredentialRecord) {\n  if (record instanceof W3cCredentialRecord)\n    return agentContext.resolve(W3cCredentialRepository).update(agentContext, record)\n  if (record instanceof W3cV2CredentialRecord)\n    return agentContext.resolve(W3cV2CredentialRepository).update(agentContext, record)\n  if (record instanceof MdocRecord) return agentContext.resolve(MdocRepository).update(agentContext, record)\n  return agentContext.resolve(SdJwtVcRepository).update(agentContext, record)\n}\n"],"mappings":"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"}