UNPKG

@sphereon/ssi-sdk.data-store

Version:

159 lines (143 loc) • 6.3 kB
import { CredentialMapper, DocumentFormat, type IVerifiableCredential, type IVerifiablePresentation, ObjectUtils, type OriginalVerifiableCredential, type OriginalVerifiablePresentation, type SdJwtDecodedVerifiableCredentialPayload, } from '@sphereon/ssi-types' import { computeEntryHash } from '@veramo/utils' import { DigitalCredentialEntity } from '../../entities/digitalCredential/DigitalCredentialEntity' import type { AddCredentialArgs, DigitalCredential, NonPersistedDigitalCredential } from '../../types' import { CredentialDocumentFormat, DocumentType, RegulationType } from '../../types' import { replaceNullWithUndefined } from '../FormattingUtils' import { defaultHasher } from '@sphereon/ssi-sdk.core' function determineDocumentType(raw: string): DocumentType { const rawDocument = parseRawDocument(raw) if (!rawDocument) { throw new Error(`Couldn't parse the credential: ${raw}`) } const hasProof = CredentialMapper.hasProof(rawDocument) const isCredential = isHex(raw) || ObjectUtils.isBase64(raw) || CredentialMapper.isCredential(rawDocument) const isPresentation = CredentialMapper.isPresentation(rawDocument) if (isCredential) { return hasProof || isHex(raw) || ObjectUtils.isBase64(raw) ? DocumentType.VC : DocumentType.C } else if (isPresentation) { return hasProof ? DocumentType.VP : DocumentType.P } throw new Error(`Couldn't determine the type of the credential: ${raw}`) } export function isHex(input: string) { return input.match(/^([0-9A-Fa-f])+$/g) !== null } export function parseRawDocument(raw: string): OriginalVerifiableCredential | OriginalVerifiablePresentation { if (isHex(raw) || ObjectUtils.isBase64(raw)) { // mso_mdoc return raw } else if (CredentialMapper.isJwtEncoded(raw) || CredentialMapper.isSdJwtEncoded(raw)) { return raw } try { return JSON.parse(raw) } catch (e) { throw new Error(`Can't parse the raw credential: ${raw}`) } } export function ensureRawDocument(input: string | object): string { if (typeof input === 'string') { if (isHex(input) || ObjectUtils.isBase64(input)) { // mso_mdoc return input } else if (CredentialMapper.isJwtEncoded(input) || CredentialMapper.isSdJwtEncoded(input)) { return input } throw Error('Unknown input to be mapped as rawDocument') } try { return JSON.stringify(input) } catch (e) { throw new Error(`Can't stringify to a raw credential: ${input}`) } } function determineCredentialDocumentFormat(documentFormat: DocumentFormat): CredentialDocumentFormat { switch (documentFormat) { case DocumentFormat.JSONLD: return CredentialDocumentFormat.JSON_LD case DocumentFormat.JWT: return CredentialDocumentFormat.JWT case DocumentFormat.SD_JWT_VC: return CredentialDocumentFormat.SD_JWT case DocumentFormat.MSO_MDOC: return CredentialDocumentFormat.MSO_MDOC default: throw new Error(`Not supported document format: ${documentFormat}`) } } function getValidUntil(uniformDocument: IVerifiableCredential | IVerifiablePresentation | SdJwtDecodedVerifiableCredentialPayload): Date | undefined { if ('expirationDate' in uniformDocument && uniformDocument.expirationDate) { return new Date(uniformDocument.expirationDate) } else if ('validUntil' in uniformDocument && uniformDocument.validUntil) { return new Date(uniformDocument.validUntil) } else if ('exp' in uniformDocument && uniformDocument.exp) { return new Date(uniformDocument.exp * 1000) } return undefined } function getValidFrom(uniformDocument: IVerifiableCredential | IVerifiablePresentation | SdJwtDecodedVerifiableCredentialPayload): Date | undefined { if ('issuanceDate' in uniformDocument && uniformDocument.issuanceDate) { return new Date(uniformDocument.issuanceDate) } else if ('validFrom' in uniformDocument && uniformDocument.validFrom) { return new Date(uniformDocument['validFrom']) } else if ('nbf' in uniformDocument && uniformDocument.nbf) { return new Date(uniformDocument['nbf'] * 1000) } else if ('iat' in uniformDocument && uniformDocument.iat) { return new Date(uniformDocument['iat'] * 1000) } return undefined } const safeStringify = (object: any): string => { if (typeof object === 'string') { return object } return JSON.stringify(object) } export const nonPersistedDigitalCredentialEntityFromAddArgs = (addCredentialArgs: AddCredentialArgs): NonPersistedDigitalCredential => { const documentType: DocumentType = determineDocumentType(addCredentialArgs.rawDocument) const documentFormat: DocumentFormat = CredentialMapper.detectDocumentType(addCredentialArgs.rawDocument) const hasher = addCredentialArgs?.opts?.hasher ?? defaultHasher if (documentFormat === DocumentFormat.SD_JWT_VC && !addCredentialArgs.opts?.hasher) { throw new Error('No hasher function is provided for SD_JWT credential.') } const uniformDocument = documentType === DocumentType.VC || documentType === DocumentType.C ? CredentialMapper.toUniformCredential(addCredentialArgs.rawDocument, { hasher }) : CredentialMapper.toUniformPresentation(addCredentialArgs.rawDocument, { hasher }) const validFrom: Date | undefined = getValidFrom(uniformDocument) const validUntil: Date | undefined = getValidUntil(uniformDocument) const hash = computeEntryHash(addCredentialArgs.rawDocument) const regulationType = addCredentialArgs.regulationType ?? RegulationType.NON_REGULATED return { ...addCredentialArgs, regulationType, documentType, documentFormat: determineCredentialDocumentFormat(documentFormat), createdAt: new Date(), credentialId: uniformDocument.id ?? hash, hash, uniformDocument: safeStringify(uniformDocument), validFrom, ...(validUntil && { validUntil }), lastUpdatedAt: new Date(), } } export const digitalCredentialFrom = (credentialEntity: DigitalCredentialEntity): DigitalCredential => { const result: DigitalCredential = { ...credentialEntity, } return replaceNullWithUndefined(result) } export const digitalCredentialsFrom = (credentialEntities: Array<DigitalCredentialEntity>): DigitalCredential[] => { return credentialEntities.map((credentialEntity) => digitalCredentialFrom(credentialEntity)) }