@sphereon/ssi-sdk.data-store 0.29.1-next.105 → 0.29.1-next.122

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/digitalCredential/DigitalCredentialStore.d.ts +5 -1
  2. package/dist/digitalCredential/DigitalCredentialStore.d.ts.map +1 -1
  3. package/dist/digitalCredential/DigitalCredentialStore.js +57 -8
  4. package/dist/digitalCredential/DigitalCredentialStore.js.map +1 -1
  5. package/dist/entities/digitalCredential/DigitalCredentialEntity.d.ts +9 -1
  6. package/dist/entities/digitalCredential/DigitalCredentialEntity.d.ts.map +1 -1
  7. package/dist/entities/digitalCredential/DigitalCredentialEntity.js +32 -0
  8. package/dist/entities/digitalCredential/DigitalCredentialEntity.js.map +1 -1
  9. package/dist/index.d.ts +1 -0
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +1 -0
  12. package/dist/index.js.map +1 -1
  13. package/dist/migrations/postgres/1708525189001-CreateDigitalCredential.d.ts.map +1 -1
  14. package/dist/migrations/postgres/1708525189001-CreateDigitalCredential.js +16 -4
  15. package/dist/migrations/postgres/1708525189001-CreateDigitalCredential.js.map +1 -1
  16. package/dist/migrations/sqlite/1708525189002-CreateDigitalCredential.d.ts.map +1 -1
  17. package/dist/migrations/sqlite/1708525189002-CreateDigitalCredential.js +15 -5
  18. package/dist/migrations/sqlite/1708525189002-CreateDigitalCredential.js.map +1 -1
  19. package/dist/types/contact/contact.d.ts +2 -2
  20. package/dist/types/contact/contact.d.ts.map +1 -1
  21. package/dist/types/digitalCredential/IAbstractDigitalCredentialStore.d.ts +5 -1
  22. package/dist/types/digitalCredential/IAbstractDigitalCredentialStore.d.ts.map +1 -1
  23. package/dist/types/digitalCredential/digitalCredential.d.ts +20 -3
  24. package/dist/types/digitalCredential/digitalCredential.d.ts.map +1 -1
  25. package/dist/types/digitalCredential/digitalCredential.js +13 -5
  26. package/dist/types/digitalCredential/digitalCredential.js.map +1 -1
  27. package/dist/utils/digitalCredential/MappingUtils.d.ts +4 -0
  28. package/dist/utils/digitalCredential/MappingUtils.d.ts.map +1 -1
  29. package/dist/utils/digitalCredential/MappingUtils.js +44 -6
  30. package/dist/utils/digitalCredential/MappingUtils.js.map +1 -1
  31. package/dist/utils/hasher.d.ts +3 -0
  32. package/dist/utils/hasher.d.ts.map +1 -0
  33. package/dist/utils/hasher.js +20 -0
  34. package/dist/utils/hasher.js.map +1 -0
  35. package/package.json +11 -8
  36. package/src/__tests__/digitalCredential.entities.test.ts +12 -0
  37. package/src/__tests__/digitalCredential.store.test.ts +22 -0
  38. package/src/digitalCredential/DigitalCredentialStore.ts +66 -10
  39. package/src/entities/digitalCredential/DigitalCredentialEntity.ts +25 -0
  40. package/src/index.ts +2 -0
  41. package/src/migrations/postgres/1708525189001-CreateDigitalCredential.ts +17 -4
  42. package/src/migrations/sqlite/1708525189002-CreateDigitalCredential.ts +15 -5
  43. package/src/types/contact/contact.ts +2 -2
  44. package/src/types/digitalCredential/IAbstractDigitalCredentialStore.ts +5 -1
  45. package/src/types/digitalCredential/digitalCredential.ts +21 -5
  46. package/src/utils/digitalCredential/MappingUtils.ts +50 -6
  47. package/src/utils/hasher.ts +19 -0
@@ -1,14 +1,15 @@
1
1
  import { AbstractDigitalCredentialStore } from './AbstractDigitalCredentialStore'
2
2
  import {
3
3
  AddCredentialArgs,
4
+ CredentialRole,
5
+ CredentialStateType,
6
+ DigitalCredential,
4
7
  GetCredentialArgs,
5
8
  GetCredentialsArgs,
6
9
  GetCredentialsResponse,
10
+ NonPersistedDigitalCredential,
7
11
  RemoveCredentialArgs,
8
12
  UpdateCredentialStateArgs,
9
- CredentialStateType,
10
- DigitalCredential,
11
- NonPersistedDigitalCredential,
12
13
  } from '../types'
13
14
  import { OrPromise } from '@sphereon/ssi-types'
14
15
  import { DataSource, FindOptionsOrder, Repository } from 'typeorm'
@@ -26,6 +27,7 @@ const debug: Debug.Debugger = Debug('sphereon:ssi-sdk:credential-store')
26
27
 
27
28
  export class DigitalCredentialStore extends AbstractDigitalCredentialStore {
28
29
  private readonly dbConnection: OrPromise<DataSource>
30
+ private dcRepo: Repository<DigitalCredentialEntity> | undefined
29
31
 
30
32
  constructor(dbConnection: OrPromise<DataSource>) {
31
33
  super()
@@ -34,14 +36,19 @@ export class DigitalCredentialStore extends AbstractDigitalCredentialStore {
34
36
 
35
37
  addCredential = async (args: AddCredentialArgs): Promise<DigitalCredential> => {
36
38
  debug('Adding credential', args)
37
- const digitalCredentialEntityRepository: Repository<DigitalCredentialEntity> = (await this.dbConnection).getRepository(DigitalCredentialEntity)
38
39
  const credentialEntity: NonPersistedDigitalCredential = nonPersistedDigitalCredentialEntityFromAddArgs(args)
39
- const createdResult: DigitalCredentialEntity = await digitalCredentialEntityRepository.save(credentialEntity)
40
+ const validationError = this.assertValidDigitalCredential(credentialEntity)
41
+ if (validationError) {
42
+ return Promise.reject(validationError)
43
+ }
44
+ const dcRepo = await this.getRepository()
45
+ const createdResult: DigitalCredentialEntity = await dcRepo.save(credentialEntity)
40
46
  return Promise.resolve(digitalCredentialFrom(createdResult))
41
47
  }
42
48
 
43
49
  getCredential = async (args: GetCredentialArgs): Promise<DigitalCredential> => {
44
- const result: DigitalCredentialEntity | null = await (await this.dbConnection).getRepository(DigitalCredentialEntity).findOne({
50
+ const dcRepo = await this.getRepository()
51
+ const result: DigitalCredentialEntity | null = await dcRepo.findOne({
45
52
  where: args,
46
53
  })
47
54
 
@@ -57,7 +64,8 @@ export class DigitalCredentialStore extends AbstractDigitalCredentialStore {
57
64
  order && typeof order === 'string'
58
65
  ? parseAndValidateOrderOptions<DigitalCredentialEntity>(order)
59
66
  : <FindOptionsOrder<DigitalCredentialEntity>>order
60
- const [result, total] = await (await this.dbConnection).getRepository(DigitalCredentialEntity).findAndCount({
67
+ const dcRepo = await this.getRepository()
68
+ const [result, total] = await dcRepo.findAndCount({
61
69
  where: filter,
62
70
  skip: offset,
63
71
  take: limit,
@@ -84,15 +92,42 @@ export class DigitalCredentialStore extends AbstractDigitalCredentialStore {
84
92
  return false
85
93
  }
86
94
  try {
87
- const connection = await this.dbConnection
88
- const result = await connection.getRepository(DigitalCredentialEntity).delete(query)
89
- return result.affected === 1
95
+ const dcRepo = await this.getRepository()
96
+ // TODO create a flag whether we want to delete recursively or return an error when there are child credentials?
97
+ const affected = await this.deleteTree(dcRepo, query)
98
+ return affected > 0
90
99
  } catch (error) {
91
100
  console.error('Error removing digital credential:', error)
92
101
  return false
93
102
  }
94
103
  }
95
104
 
105
+ private async deleteTree(dcRepo: Repository<DigitalCredentialEntity>, query: FindOptionsWhere<DigitalCredentialEntity>): Promise<number> {
106
+ let affected: number = 0
107
+ const findResult = await dcRepo.findBy(query)
108
+ for (const dc of findResult) {
109
+ if (dc.parentId !== null && dc.parentId !== undefined) {
110
+ affected += await this.deleteTree(dcRepo, { id: dc.parentId })
111
+ }
112
+ const result = await dcRepo.delete(dc.id)
113
+ if (result.affected) {
114
+ affected += result.affected
115
+ }
116
+ }
117
+ return affected
118
+ }
119
+
120
+ private async getRepository(): Promise<Repository<DigitalCredentialEntity>> {
121
+ if (this.dcRepo !== undefined) {
122
+ return Promise.resolve(this.dcRepo)
123
+ }
124
+ this.dcRepo = (await this.dbConnection).getRepository(DigitalCredentialEntity)
125
+ if (this.dcRepo === undefined) {
126
+ return Promise.reject(Error('Could not get DigitalCredentialEntity repository'))
127
+ }
128
+ return this.dcRepo
129
+ }
130
+
96
131
  updateCredentialState = async (args: UpdateCredentialStateArgs): Promise<DigitalCredential> => {
97
132
  const credentialRepository: Repository<DigitalCredentialEntity> = (await this.dbConnection).getRepository(DigitalCredentialEntity)
98
133
  const whereClause: Record<string, any> = {}
@@ -123,6 +158,7 @@ export class DigitalCredentialStore extends AbstractDigitalCredentialStore {
123
158
  ...credential,
124
159
  ...(args.verifiedState !== CredentialStateType.REVOKED && { verifiedAt: args.verifiedAt }),
125
160
  ...(args.verifiedState === CredentialStateType.REVOKED && { revokedAt: args.revokedAt }),
161
+ identifierMethod: credential.identifierMethod,
126
162
  lastUpdatedAt: new Date(),
127
163
  verifiedState: args.verifiedState,
128
164
  }
@@ -130,4 +166,24 @@ export class DigitalCredentialStore extends AbstractDigitalCredentialStore {
130
166
  const updatedResult: DigitalCredentialEntity = await credentialRepository.save(updatedCredential, { transaction: true })
131
167
  return digitalCredentialFrom(updatedResult)
132
168
  }
169
+
170
+ private assertValidDigitalCredential(credentialEntity: NonPersistedDigitalCredential): Error | undefined {
171
+ const { kmsKeyRef, identifierMethod, credentialRole, isIssuerSigned } = credentialEntity
172
+
173
+ const isRoleInvalid = credentialRole === CredentialRole.ISSUER || (credentialRole === CredentialRole.HOLDER && !isIssuerSigned)
174
+
175
+ if (isRoleInvalid && (!kmsKeyRef || !identifierMethod)) {
176
+ const missingFields = []
177
+
178
+ if (!kmsKeyRef) missingFields.push('kmsKeyRef')
179
+ if (!identifierMethod) missingFields.push('identifierMethod')
180
+
181
+ const fields = missingFields.join(' and ')
182
+ return new Error(
183
+ `DigitalCredential field(s) ${fields} is/are required for credential role ${credentialRole} with isIssuerSigned=${isIssuerSigned}.`,
184
+ )
185
+ }
186
+
187
+ return undefined
188
+ }
133
189
  }
@@ -6,6 +6,7 @@ import {
6
6
  CredentialStateType,
7
7
  DigitalCredential,
8
8
  DocumentType,
9
+ RegulationType,
9
10
  } from '../../types'
10
11
  import { typeormDate, typeOrmDateTime } from '@sphereon/ssi-sdk.agent-config'
11
12
 
@@ -14,9 +15,15 @@ export class DigitalCredentialEntity extends BaseEntity implements DigitalCreden
14
15
  @PrimaryGeneratedColumn('uuid')
15
16
  id!: string
16
17
 
18
+ @Column('text', { name: 'parent_id', nullable: true })
19
+ parentId?: string
20
+
17
21
  @Column('simple-enum', { name: 'document_type', enum: DocumentType, nullable: false })
18
22
  documentType!: DocumentType
19
23
 
24
+ @Column('simple-enum', { name: 'regulation_type', enum: RegulationType, nullable: false })
25
+ regulationType!: RegulationType
26
+
20
27
  @Column('simple-enum', { name: 'document_format', enum: CredentialDocumentFormat, nullable: false })
21
28
  documentFormat!: CredentialDocumentFormat
22
29
 
@@ -35,18 +42,33 @@ export class DigitalCredentialEntity extends BaseEntity implements DigitalCreden
35
42
  @Column('text', { name: 'hash', nullable: false, unique: true })
36
43
  hash!: string
37
44
 
45
+ @Column('text', { name: 'kms_key_ref', nullable: true })
46
+ kmsKeyRef!: string
47
+
48
+ @Column('text', { name: 'identifier_method', nullable: true })
49
+ identifierMethod!: string
50
+
38
51
  @Column('simple-enum', { name: 'issuer_correlation_type', enum: CredentialCorrelationType, nullable: false })
39
52
  issuerCorrelationType!: CredentialCorrelationType
40
53
 
41
54
  @Column('simple-enum', { name: 'subject_correlation_type', enum: CredentialCorrelationType, nullable: true })
42
55
  subjectCorrelationType?: CredentialCorrelationType
43
56
 
57
+ @Column('simple-enum', { name: 'rp_correlation_type', enum: CredentialCorrelationType, nullable: true })
58
+ rpCorrelationType?: CredentialCorrelationType
59
+
60
+ @Column('boolean', { name: 'issuer_signed', nullable: true })
61
+ isIssuerSigned?: boolean
62
+
44
63
  @Column('text', { name: 'issuer_correlation_id', nullable: false })
45
64
  issuerCorrelationId!: string
46
65
 
47
66
  @Column('text', { name: 'subject_correlation_id', nullable: true })
48
67
  subjectCorrelationId?: string
49
68
 
69
+ @Column('text', { name: 'rp_correlation_id', nullable: true })
70
+ rpCorrelationId?: string
71
+
50
72
  @Column('simple-enum', { name: 'verified_state', enum: CredentialStateType, nullable: true })
51
73
  verifiedState?: CredentialStateType
52
74
 
@@ -56,6 +78,9 @@ export class DigitalCredentialEntity extends BaseEntity implements DigitalCreden
56
78
  @CreateDateColumn({ name: 'created_at', nullable: false, type: typeOrmDateTime() })
57
79
  createdAt!: Date
58
80
 
81
+ @Column({ name: 'presented_at', nullable: true, type: typeormDate() })
82
+ presentedAt?: Date
83
+
59
84
  @UpdateDateColumn({ name: 'last_updated_at', nullable: false, type: typeOrmDateTime() })
60
85
  lastUpdatedAt!: Date
61
86
 
package/src/index.ts CHANGED
@@ -156,3 +156,5 @@ export {
156
156
  isPresentationDefinitionEqual,
157
157
  ContactMetadataItemEntity,
158
158
  }
159
+
160
+ export * from './utils/hasher'
@@ -5,34 +5,46 @@ export class CreateDigitalCredential1708525189001 implements MigrationInterface
5
5
 
6
6
  public async up(queryRunner: QueryRunner): Promise<void> {
7
7
  await queryRunner.query(`CREATE TYPE "digital_document_type" AS ENUM('VC', 'VP', 'C', 'P')`)
8
- await queryRunner.query(`CREATE TYPE "digital_credential_document_format" AS ENUM('JSON_LD', 'JWT', 'SD_JWT', 'MDOC')`)
8
+ await queryRunner.query(`CREATE TYPE "digital_regulation_type" AS ENUM('PID', 'QEAA', 'EAA', 'NON_REGULATED')`)
9
+ await queryRunner.query(`CREATE TYPE "digital_credential_document_format" AS ENUM('JSON_LD', 'JWT', 'SD_JWT', 'MSO_MDOC')`)
9
10
  await queryRunner.query(`CREATE TYPE "digital_credential_credential_role" AS ENUM('ISSUER', 'VERIFIER', 'HOLDER')`)
10
- await queryRunner.query(`CREATE TYPE "digital_credential_correlation_type" AS ENUM('DID', 'URL', 'X509_CN')`)
11
+ await queryRunner.query(`CREATE TYPE "digital_credential_correlation_type" AS ENUM('DID', 'KID', 'URL', 'X509_SAN')`)
11
12
  await queryRunner.query(`CREATE TYPE "digital_credential_state_type" AS ENUM('REVOKED', 'VERIFIED', 'EXPIRED')`)
12
13
 
14
+ // TODO FK for parent
15
+
13
16
  await queryRunner.query(`
14
17
  CREATE TABLE "DigitalCredential" (
15
18
  "id" uuid NOT NULL DEFAULT uuid_generate_v4(),
19
+ "parent_id" text,
16
20
  "document_type" "digital_document_type" NOT NULL,
21
+ "regulation_type" "digital_regulation_type" NOT NULL DEFAULT 'NON_REGULATED'::"digital_regulation_type",
17
22
  "document_format" "digital_credential_document_format" NOT NULL,
18
23
  "credential_role" "digital_credential_credential_role" NOT NULL,
19
24
  "raw_document" text NOT NULL,
20
25
  "uniform_document" text NOT NULL,
21
26
  "credential_id" text,
22
- "hash" text NOT NULL UNIQUE,
27
+ "hash" text NOT NULL,
28
+ "kms_key_ref" text,
29
+ "identifier_method" text,
23
30
  "issuer_correlation_type" "digital_credential_correlation_type" NOT NULL,
24
31
  "subject_correlation_type" "digital_credential_correlation_type",
25
32
  "issuer_correlation_id" text NOT NULL,
26
33
  "subject_correlation_id" text,
27
34
  "verified_state" "digital_credential_state_type",
35
+ "issuer_signed" boolean,
36
+ "rp_correlation_id" text,
37
+ "rp_correlation_type" "digital_credential_correlation_type",
28
38
  "tenant_id" text,
29
39
  "created_at" TIMESTAMP NOT NULL DEFAULT now(),
30
40
  "last_updated_at" TIMESTAMP NOT NULL DEFAULT now(),
41
+ "presented_at" DATE,
31
42
  "valid_from" DATE,
32
43
  "valid_until" DATE,
33
44
  "verified_at" DATE,
34
45
  "revoked_at" DATE,
35
- PRIMARY KEY ("id")
46
+ PRIMARY KEY ("id"),
47
+ UNIQUE ("hash", "credential_role")
36
48
  )
37
49
  `)
38
50
  }
@@ -43,6 +55,7 @@ export class CreateDigitalCredential1708525189001 implements MigrationInterface
43
55
  await queryRunner.query(`DROP TYPE "digital_credential_correlation_type"`)
44
56
  await queryRunner.query(`DROP TYPE "digital_credential_document_format"`)
45
57
  await queryRunner.query(`DROP TYPE "digital_credential_credential_role"`)
58
+ await queryRunner.query(`DROP TYPE "digital_regulation_type"`)
46
59
  await queryRunner.query(`DROP TYPE "digital_document_type"`)
47
60
  }
48
61
  }
@@ -4,28 +4,38 @@ export class CreateDigitalCredential1708525189002 implements MigrationInterface
4
4
  name = 'CreateDigitalCredential1708525189002'
5
5
 
6
6
  public async up(queryRunner: QueryRunner): Promise<void> {
7
+ // TODO FK for parent
7
8
  await queryRunner.query(`
8
9
  CREATE TABLE "DigitalCredential" (
9
10
  "id" varchar PRIMARY KEY NOT NULL,
11
+ "parent_id" text,
10
12
  "document_type" varchar CHECK( "document_type" IN ('VC', 'VP', 'C', 'P') ) NOT NULL,
11
- "document_format" varchar CHECK( "document_format" IN ('JSON_LD', 'JWT', 'SD_JWT', 'MDOC') ) NOT NULL,
13
+ "regulation_type" varchar CHECK( "regulation_type" IN ('PID', 'QEAA', 'EAA', 'NON_REGULATED') ) NOT NULL DEFAULT 'NON_REGULATED',
14
+ "document_format" varchar CHECK( "document_format" IN ('JSON_LD', 'JWT', 'SD_JWT', 'MSO_MDOC') ) NOT NULL,
12
15
  "credential_role" varchar CHECK( "credential_role" IN ('ISSUER', 'VERIFIER', 'HOLDER') ) NOT NULL,
13
16
  "raw_document" text NOT NULL,
14
17
  "uniform_document" text NOT NULL,
15
18
  "credential_id" text,
16
- "hash" text NOT NULL UNIQUE,
17
- "issuer_correlation_type" varchar CHECK( "issuer_correlation_type" IN ('DID', 'URL', 'X509_CN') ) NOT NULL,
18
- "subject_correlation_type" varchar CHECK( "subject_correlation_type" IN ('DID', 'URL', 'X509_CN') ),
19
+ "hash" text NOT NULL,
20
+ "kms_key_ref" text,
21
+ "identifier_method" text,
22
+ "issuer_correlation_type" varchar CHECK( "issuer_correlation_type" IN ('DID', 'KID', 'URL', 'X509_SAN') ) NOT NULL,
23
+ "subject_correlation_type" varchar CHECK( "subject_correlation_type" IN ('DID', 'KID', 'URL', 'X509_SAN') ),
19
24
  "issuer_correlation_id" text NOT NULL,
20
25
  "subject_correlation_id" text,
26
+ "issuer_signed" boolean,
27
+ "rp_correlation_id" text,
28
+ "rp_correlation_type" varchar CHECK( "issuer_correlation_type" IN ('DID', 'KID', 'URL', 'X509_SAN') ),
21
29
  "verified_state" varchar CHECK( "verified_state" IN ('REVOKED', 'VERIFIED', 'EXPIRED') ),
22
30
  "tenant_id" text,
23
31
  "created_at" datetime NOT NULL DEFAULT (datetime('now')),
24
32
  "last_updated_at" datetime NOT NULL DEFAULT (datetime('now')),
33
+ "presented_at" datetime,
25
34
  "valid_from" datetime,
26
35
  "valid_until" datetime,
27
36
  "verified_at" datetime,
28
- "revoked_at" datetime
37
+ "revoked_at" datetime,
38
+ UNIQUE ("hash", "credential_role")
29
39
  )
30
40
  `)
31
41
  }
@@ -1,4 +1,4 @@
1
- import { ManagedIdentifierOpts } from '@sphereon/ssi-sdk-ext.identifier-resolution/dist/types'
1
+ import { ManagedIdentifierOptsOrResult } from '@sphereon/ssi-sdk-ext.identifier-resolution'
2
2
  import { IIdentifier } from '@veramo/core'
3
3
  import { ILocaleBranding } from '../issuanceBranding/issuanceBranding'
4
4
  import { CredentialRole } from '../digitalCredential/digitalCredential'
@@ -149,7 +149,7 @@ export type PartialOpenIdConfig = Partial<OpenIdConfig>
149
149
 
150
150
  export type DidAuthConfig = {
151
151
  id: string
152
- idOpts: ManagedIdentifierOpts
152
+ idOpts: ManagedIdentifierOptsOrResult
153
153
  stateId: string
154
154
  ownerId?: string
155
155
  tenantId?: string
@@ -1,4 +1,4 @@
1
- import { CredentialCorrelationType, CredentialRole, CredentialStateType, DigitalCredential } from './digitalCredential'
1
+ import { CredentialCorrelationType, CredentialRole, CredentialStateType, DigitalCredential, RegulationType } from './digitalCredential'
2
2
  import { Hasher } from '@sphereon/ssi-types'
3
3
  import { FindOptionsOrder } from 'typeorm'
4
4
  import { DigitalCredentialEntity } from '../../entities/digitalCredential/DigitalCredentialEntity'
@@ -21,6 +21,10 @@ export type GetCredentialsResponse = {
21
21
 
22
22
  export type AddCredentialArgs = {
23
23
  rawDocument: string
24
+ kmsKeyRef?: string
25
+ identifierMethod?: string
26
+ regulationType?: RegulationType
27
+ parentId?: string
24
28
  issuerCorrelationType: CredentialCorrelationType
25
29
  subjectCorrelationType?: CredentialCorrelationType
26
30
  issuerCorrelationId: string
@@ -1,21 +1,29 @@
1
- export type NonPersistedDigitalCredential = Omit<DigitalCredential, 'id'>
1
+ export type NonPersistedDigitalCredential = Omit<DigitalCredential, 'id' | 'regulationType'> & { regulationType?: RegulationType }
2
2
 
3
3
  export type DigitalCredential = {
4
4
  id: string
5
+ parentId?: string
5
6
  documentType: DocumentType
6
7
  documentFormat: CredentialDocumentFormat
7
8
  credentialRole: CredentialRole
9
+ regulationType: RegulationType
8
10
  rawDocument: string
9
11
  uniformDocument: string
10
12
  credentialId?: string
11
13
  hash: string
14
+ kmsKeyRef?: string
15
+ identifierMethod?: string
12
16
  issuerCorrelationType: CredentialCorrelationType
13
17
  subjectCorrelationType?: CredentialCorrelationType
18
+ rpCorrelationType?: CredentialCorrelationType
19
+ isIssuerSigned?: boolean
14
20
  issuerCorrelationId: string
15
21
  subjectCorrelationId?: string
22
+ rpCorrelationId?: string
16
23
  verifiedState?: CredentialStateType
17
24
  tenantId?: string
18
25
  createdAt: Date
26
+ presentedAt?: Date
19
27
  lastUpdatedAt: Date
20
28
  validUntil?: Date
21
29
  validFrom?: Date
@@ -30,11 +38,18 @@ export enum DocumentType {
30
38
  C = 'C',
31
39
  }
32
40
 
41
+ export enum RegulationType {
42
+ PID = 'PID',
43
+ QEAA = 'QEAA',
44
+ EAA = 'EAA',
45
+ NON_REGULATED = 'NON_REGULATED',
46
+ }
47
+
33
48
  export enum CredentialDocumentFormat {
34
49
  JSON_LD = 'JSON_LD',
35
50
  JWT = 'JWT',
36
51
  SD_JWT = 'SD_JWT',
37
- MDOC = 'MDOC',
52
+ MSO_MDOC = 'MSO_MDOC',
38
53
  }
39
54
 
40
55
  export namespace CredentialDocumentFormat {
@@ -45,7 +60,7 @@ export namespace CredentialDocumentFormat {
45
60
  } else if (format.includes('ldp')) {
46
61
  return CredentialDocumentFormat.JSON_LD
47
62
  } else if (format.includes('mso') || credentialFormat.includes('mdoc')) {
48
- return CredentialDocumentFormat.MDOC
63
+ return CredentialDocumentFormat.MSO_MDOC
49
64
  } else if (format.includes('jwt_')) {
50
65
  return CredentialDocumentFormat.JWT
51
66
  } else {
@@ -57,7 +72,7 @@ export namespace CredentialDocumentFormat {
57
72
  switch (documentFormat) {
58
73
  case CredentialDocumentFormat.SD_JWT:
59
74
  return 'vc+sd-jwt'
60
- case CredentialDocumentFormat.MDOC:
75
+ case CredentialDocumentFormat.MSO_MDOC:
61
76
  return 'mso_mdoc'
62
77
  case CredentialDocumentFormat.JSON_LD:
63
78
  return documentType === DocumentType.C || documentType === DocumentType.VC ? 'ldp_vc' : 'ldp_vp'
@@ -69,7 +84,8 @@ export namespace CredentialDocumentFormat {
69
84
 
70
85
  export enum CredentialCorrelationType {
71
86
  DID = 'DID',
72
- X509_CN = 'X509_CN',
87
+ X509_SAN = 'X509_SAN',
88
+ KID = 'KID',
73
89
  URL = 'URL',
74
90
  }
75
91
 
@@ -3,13 +3,21 @@ import {
3
3
  DocumentFormat,
4
4
  IVerifiableCredential,
5
5
  IVerifiablePresentation,
6
+ ObjectUtils,
6
7
  OriginalVerifiableCredential,
7
8
  OriginalVerifiablePresentation,
8
9
  SdJwtDecodedVerifiableCredentialPayload,
9
10
  } from '@sphereon/ssi-types'
10
11
  import { computeEntryHash } from '@veramo/utils'
11
12
  import { DigitalCredentialEntity } from '../../entities/digitalCredential/DigitalCredentialEntity'
12
- import { AddCredentialArgs, CredentialDocumentFormat, DigitalCredential, DocumentType, NonPersistedDigitalCredential } from '../../types'
13
+ import {
14
+ AddCredentialArgs,
15
+ CredentialDocumentFormat,
16
+ DigitalCredential,
17
+ DocumentType,
18
+ NonPersistedDigitalCredential,
19
+ RegulationType,
20
+ } from '../../types'
13
21
 
14
22
  function determineDocumentType(raw: string): DocumentType {
15
23
  const rawDocument = parseRawDocument(raw)
@@ -18,19 +26,26 @@ function determineDocumentType(raw: string): DocumentType {
18
26
  }
19
27
 
20
28
  const hasProof = CredentialMapper.hasProof(rawDocument)
21
- const isCredential = CredentialMapper.isCredential(rawDocument)
29
+ const isCredential = isHex(raw) || ObjectUtils.isBase64(raw) || CredentialMapper.isCredential(rawDocument)
22
30
  const isPresentation = CredentialMapper.isPresentation(rawDocument)
23
31
 
24
32
  if (isCredential) {
25
- return hasProof ? DocumentType.VC : DocumentType.C
33
+ return hasProof || isHex(raw) || ObjectUtils.isBase64(raw) ? DocumentType.VC : DocumentType.C
26
34
  } else if (isPresentation) {
27
35
  return hasProof ? DocumentType.VP : DocumentType.P
28
36
  }
29
37
  throw new Error(`Couldn't determine the type of the credential: ${raw}`)
30
38
  }
31
39
 
32
- function parseRawDocument(raw: string): OriginalVerifiableCredential | OriginalVerifiablePresentation {
33
- if (CredentialMapper.isJwtEncoded(raw) || CredentialMapper.isSdJwtEncoded(raw)) {
40
+ export function isHex(input: string) {
41
+ return input.match(/^([0-9A-Fa-f])+$/g) !== null
42
+ }
43
+
44
+ export function parseRawDocument(raw: string): OriginalVerifiableCredential | OriginalVerifiablePresentation {
45
+ if (isHex(raw) || ObjectUtils.isBase64(raw)) {
46
+ // mso_mdoc
47
+ return raw
48
+ } else if (CredentialMapper.isJwtEncoded(raw) || CredentialMapper.isSdJwtEncoded(raw)) {
34
49
  return raw
35
50
  }
36
51
  try {
@@ -40,6 +55,24 @@ function parseRawDocument(raw: string): OriginalVerifiableCredential | OriginalV
40
55
  }
41
56
  }
42
57
 
58
+ export function ensureRawDocument(input: string | object): string {
59
+ if (typeof input === 'string') {
60
+ if (isHex(input) || ObjectUtils.isBase64(input)) {
61
+ // mso_mdoc
62
+ return input
63
+ } else if (CredentialMapper.isJwtEncoded(input) || CredentialMapper.isSdJwtEncoded(input)) {
64
+ return input
65
+ }
66
+ throw Error('Unknown input to be mapped as rawDocument')
67
+ }
68
+
69
+ try {
70
+ return JSON.stringify(input)
71
+ } catch (e) {
72
+ throw new Error(`Can't stringify to a raw credential: ${input}`)
73
+ }
74
+ }
75
+
43
76
  function determineCredentialDocumentFormat(documentFormat: DocumentFormat): CredentialDocumentFormat {
44
77
  switch (documentFormat) {
45
78
  case DocumentFormat.JSONLD:
@@ -48,6 +81,8 @@ function determineCredentialDocumentFormat(documentFormat: DocumentFormat): Cred
48
81
  return CredentialDocumentFormat.JWT
49
82
  case DocumentFormat.SD_JWT_VC:
50
83
  return CredentialDocumentFormat.SD_JWT
84
+ case DocumentFormat.MSO_MDOC:
85
+ return CredentialDocumentFormat.MSO_MDOC
51
86
  default:
52
87
  throw new Error(`Not supported document format: ${documentFormat}`)
53
88
  }
@@ -77,6 +112,13 @@ function getValidFrom(uniformDocument: IVerifiableCredential | IVerifiablePresen
77
112
  return undefined
78
113
  }
79
114
 
115
+ const safeStringify = (object: any): string => {
116
+ if (typeof object === 'string') {
117
+ return object
118
+ }
119
+ return JSON.stringify(object)
120
+ }
121
+
80
122
  export const nonPersistedDigitalCredentialEntityFromAddArgs = (addCredentialArgs: AddCredentialArgs): NonPersistedDigitalCredential => {
81
123
  const documentType: DocumentType = determineDocumentType(addCredentialArgs.rawDocument)
82
124
  const documentFormat: DocumentFormat = CredentialMapper.detectDocumentType(addCredentialArgs.rawDocument)
@@ -91,14 +133,16 @@ export const nonPersistedDigitalCredentialEntityFromAddArgs = (addCredentialArgs
91
133
  const validFrom: Date | undefined = getValidFrom(uniformDocument)
92
134
  const validUntil: Date | undefined = getValidUntil(uniformDocument)
93
135
  const hash = computeEntryHash(addCredentialArgs.rawDocument)
136
+ const regulationType = addCredentialArgs.regulationType ?? RegulationType.NON_REGULATED
94
137
  return {
95
138
  ...addCredentialArgs,
139
+ regulationType,
96
140
  documentType,
97
141
  documentFormat: determineCredentialDocumentFormat(documentFormat),
98
142
  createdAt: new Date(),
99
143
  credentialId: uniformDocument.id ?? hash,
100
144
  hash,
101
- uniformDocument: JSON.stringify(uniformDocument),
145
+ uniformDocument: safeStringify(uniformDocument),
102
146
  validFrom,
103
147
  ...(validUntil && { validUntil }),
104
148
  lastUpdatedAt: new Date(),
@@ -0,0 +1,19 @@
1
+ import { Hasher } from '@sphereon/ssi-types'
2
+ import sha from 'sha.js'
3
+
4
+ const supportedAlgorithms = ['sha256', 'sha384', 'sha512'] as const
5
+ type SupportedAlgorithms = (typeof supportedAlgorithms)[number]
6
+
7
+ // FIXME this is a weird place for this, but it does have good reach in ssi-sdk cunlike the copy in oid4vc-common
8
+ export const defaultHasher: Hasher = (data, algorithm) => {
9
+ const sanitizedAlgorithm = algorithm.toLowerCase().replace(/[-_]/g, '')
10
+ if (!supportedAlgorithms.includes(sanitizedAlgorithm as SupportedAlgorithms)) {
11
+ throw new Error(`Unsupported hashing algorithm ${algorithm}`)
12
+ }
13
+
14
+ return new Uint8Array(
15
+ sha(sanitizedAlgorithm as SupportedAlgorithms)
16
+ .update(data)
17
+ .digest(),
18
+ )
19
+ }