@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.
- package/dist/digitalCredential/DigitalCredentialStore.d.ts +5 -1
- package/dist/digitalCredential/DigitalCredentialStore.d.ts.map +1 -1
- package/dist/digitalCredential/DigitalCredentialStore.js +57 -8
- package/dist/digitalCredential/DigitalCredentialStore.js.map +1 -1
- package/dist/entities/digitalCredential/DigitalCredentialEntity.d.ts +9 -1
- package/dist/entities/digitalCredential/DigitalCredentialEntity.d.ts.map +1 -1
- package/dist/entities/digitalCredential/DigitalCredentialEntity.js +32 -0
- package/dist/entities/digitalCredential/DigitalCredentialEntity.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/migrations/postgres/1708525189001-CreateDigitalCredential.d.ts.map +1 -1
- package/dist/migrations/postgres/1708525189001-CreateDigitalCredential.js +16 -4
- package/dist/migrations/postgres/1708525189001-CreateDigitalCredential.js.map +1 -1
- package/dist/migrations/sqlite/1708525189002-CreateDigitalCredential.d.ts.map +1 -1
- package/dist/migrations/sqlite/1708525189002-CreateDigitalCredential.js +15 -5
- package/dist/migrations/sqlite/1708525189002-CreateDigitalCredential.js.map +1 -1
- package/dist/types/contact/contact.d.ts +2 -2
- package/dist/types/contact/contact.d.ts.map +1 -1
- package/dist/types/digitalCredential/IAbstractDigitalCredentialStore.d.ts +5 -1
- package/dist/types/digitalCredential/IAbstractDigitalCredentialStore.d.ts.map +1 -1
- package/dist/types/digitalCredential/digitalCredential.d.ts +20 -3
- package/dist/types/digitalCredential/digitalCredential.d.ts.map +1 -1
- package/dist/types/digitalCredential/digitalCredential.js +13 -5
- package/dist/types/digitalCredential/digitalCredential.js.map +1 -1
- package/dist/utils/digitalCredential/MappingUtils.d.ts +4 -0
- package/dist/utils/digitalCredential/MappingUtils.d.ts.map +1 -1
- package/dist/utils/digitalCredential/MappingUtils.js +44 -6
- package/dist/utils/digitalCredential/MappingUtils.js.map +1 -1
- package/dist/utils/hasher.d.ts +3 -0
- package/dist/utils/hasher.d.ts.map +1 -0
- package/dist/utils/hasher.js +20 -0
- package/dist/utils/hasher.js.map +1 -0
- package/package.json +11 -8
- package/src/__tests__/digitalCredential.entities.test.ts +12 -0
- package/src/__tests__/digitalCredential.store.test.ts +22 -0
- package/src/digitalCredential/DigitalCredentialStore.ts +66 -10
- package/src/entities/digitalCredential/DigitalCredentialEntity.ts +25 -0
- package/src/index.ts +2 -0
- package/src/migrations/postgres/1708525189001-CreateDigitalCredential.ts +17 -4
- package/src/migrations/sqlite/1708525189002-CreateDigitalCredential.ts +15 -5
- package/src/types/contact/contact.ts +2 -2
- package/src/types/digitalCredential/IAbstractDigitalCredentialStore.ts +5 -1
- package/src/types/digitalCredential/digitalCredential.ts +21 -5
- package/src/utils/digitalCredential/MappingUtils.ts +50 -6
- 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
|
|
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
|
|
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
|
|
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
|
|
88
|
-
|
|
89
|
-
|
|
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
|
@@ -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 "
|
|
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', '
|
|
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
|
|
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
|
-
"
|
|
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
|
|
17
|
-
"
|
|
18
|
-
"
|
|
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 {
|
|
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:
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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 {
|
|
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
|
|
33
|
-
|
|
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:
|
|
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
|
+
}
|