@sphereon/ssi-sdk.data-store
Version:
159 lines (143 loc) • 6.3 kB
text/typescript
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))
}