@idos-network/credentials 1.1.0
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/LICENSE +21 -0
- package/README.md +153 -0
- package/assets/country-codes.xml +271 -0
- package/assets/ed25519-signature-2020-v1.json +93 -0
- package/assets/idos-credential-subject-face-id-v1.json +8 -0
- package/assets/idos-credential-subject-v1.json +41 -0
- package/assets/idos-credential-subject-v2.json +37 -0
- package/assets/idos-credentials-v1.json +9 -0
- package/assets/v1.json +273 -0
- package/dist/builder/index.d.mts +20 -0
- package/dist/builder/index.d.mts.map +1 -0
- package/dist/builder/index.mjs +619 -0
- package/dist/builder/index.mjs.map +1 -0
- package/dist/index.d.mts +1 -0
- package/dist/index.mjs +1 -0
- package/dist/types/index.d.mts +135 -0
- package/dist/types/index.d.mts.map +1 -0
- package/dist/types/index.mjs +63 -0
- package/dist/types/index.mjs.map +1 -0
- package/dist/utils/index.d.mts +33 -0
- package/dist/utils/index.d.mts.map +1 -0
- package/dist/utils/index.mjs +124 -0
- package/dist/utils/index.mjs.map +1 -0
- package/package.json +73 -0
- package/src/builder/builder.test.ts +407 -0
- package/src/builder/index.ts +122 -0
- package/src/builder/loader.ts +76 -0
- package/src/builder/verifier.ts +55 -0
- package/src/index.ts +2 -0
- package/src/types/credentials.ts +282 -0
- package/src/types/index.ts +4 -0
- package/src/types/issuers.ts +21 -0
- package/src/utils/index.ts +277 -0
- package/src/utils/utils.test.ts +303 -0
- package/types/index.d.ts +4 -0
- package/types/internals.d.ts +124 -0
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import type { CredentialSubject } from "../types";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
deriveLevel,
|
|
7
|
+
highestMatchingCredential,
|
|
8
|
+
matchLevelOrHigher,
|
|
9
|
+
pickHighestMatchingLevel,
|
|
10
|
+
recordFilter,
|
|
11
|
+
} from "./";
|
|
12
|
+
|
|
13
|
+
const defaultCredential: CredentialSubject = {
|
|
14
|
+
id: "uuid:1234",
|
|
15
|
+
firstName: "John",
|
|
16
|
+
familyName: "Doe",
|
|
17
|
+
idDocumentType: "PASSPORT",
|
|
18
|
+
dateOfBirth: new Date("1990-01-01"),
|
|
19
|
+
idDocumentCountry: "US",
|
|
20
|
+
idDocumentNumber: "123456789",
|
|
21
|
+
idDocumentFrontFile: Buffer.from("ID Document Front"),
|
|
22
|
+
selfieFile: Buffer.alloc(0),
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
describe("recordFilter", () => {
|
|
26
|
+
[
|
|
27
|
+
[
|
|
28
|
+
{ level: "basic+liveness", status: "approved", issuer: "issuer-a", type: "kyc" },
|
|
29
|
+
{ level: ["basic+liveness", "plus+liveness"], status: ["approved"] },
|
|
30
|
+
{},
|
|
31
|
+
true,
|
|
32
|
+
],
|
|
33
|
+
[
|
|
34
|
+
{ level: "plus+liveness", status: "approved", issuer: "issuer-a", type: "kyc" },
|
|
35
|
+
{ level: ["basic+liveness"], status: ["approved"] },
|
|
36
|
+
{},
|
|
37
|
+
false,
|
|
38
|
+
],
|
|
39
|
+
[
|
|
40
|
+
{ level: "basic+liveness", status: "approved", issuer: "issuer-a", type: "kyc" },
|
|
41
|
+
{ level: ["basic+liveness"], status: ["approved"] },
|
|
42
|
+
{ issuer: ["issuer-b", "issuer-a"] },
|
|
43
|
+
false,
|
|
44
|
+
],
|
|
45
|
+
[
|
|
46
|
+
{ level: "basic+liveness", status: "approved", issuer: "issuer-a", type: "kyc" },
|
|
47
|
+
{ level: ["basic+liveness"], status: ["approved"] },
|
|
48
|
+
{ issuer: ["issuer-b"] },
|
|
49
|
+
true,
|
|
50
|
+
],
|
|
51
|
+
[{ level: "plus+liveness", status: "approved", issuer: "issuer-a", type: "kyc" }, {}, {}, true],
|
|
52
|
+
[
|
|
53
|
+
{ credentialSubject: { nationality: "CZ" } },
|
|
54
|
+
{ "credentialSubject.nationality": ["CZ", "DE"] },
|
|
55
|
+
{},
|
|
56
|
+
true,
|
|
57
|
+
],
|
|
58
|
+
[
|
|
59
|
+
{ credentialSubject: { nationality: "CZ" } },
|
|
60
|
+
{},
|
|
61
|
+
{ "credentialSubject.nationality": ["CZ", "DE"] },
|
|
62
|
+
false,
|
|
63
|
+
],
|
|
64
|
+
].forEach(([publicNotes, pick, omit, expected]) => {
|
|
65
|
+
it(`publicNotes=${JSON.stringify(publicNotes)}, pick=${JSON.stringify(pick)}, omit=${JSON.stringify(omit)} => ${expected}`, () => {
|
|
66
|
+
const result = recordFilter(
|
|
67
|
+
publicNotes as Record<string, unknown>,
|
|
68
|
+
pick as Record<string, unknown[]>,
|
|
69
|
+
omit as Record<string, unknown[]>,
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
expect(result).toBe(expected);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe("matchLevelOrHigher", () => {
|
|
78
|
+
[
|
|
79
|
+
["basic", [], "basic+liveness", true],
|
|
80
|
+
["plus", [], "plus+liveness", true],
|
|
81
|
+
["plus", [], "basic", false],
|
|
82
|
+
["plus", ["liveness"], "plus", false],
|
|
83
|
+
["basic", ["email"], "basic+liveness+email+phoneNumber", true],
|
|
84
|
+
["plus", ["email", "phoneNumber"], "plus+liveness+email+phoneNumber", true],
|
|
85
|
+
["plus", ["email", "phoneNumber"], "plus+phoneNumber", false],
|
|
86
|
+
].forEach(([level, requiredAddons, testLevel, expected]) => {
|
|
87
|
+
it(`level=${level} requiredAddons=[${(requiredAddons as string[]).join(",")}] testLevel=${testLevel} => ${expected}`, () => {
|
|
88
|
+
expect(
|
|
89
|
+
matchLevelOrHigher(
|
|
90
|
+
level as "basic" | "plus",
|
|
91
|
+
requiredAddons as ("liveness" | "email" | "phoneNumber")[],
|
|
92
|
+
testLevel as string,
|
|
93
|
+
),
|
|
94
|
+
).toBe(expected as boolean);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe("pickHighestMatchingLevel", () => {
|
|
100
|
+
[
|
|
101
|
+
[
|
|
102
|
+
["basic+liveness", "plus+liveness+email", "plus+liveness+email+phoneNumber"],
|
|
103
|
+
"plus",
|
|
104
|
+
["email"],
|
|
105
|
+
"plus+liveness+email+phoneNumber",
|
|
106
|
+
],
|
|
107
|
+
[
|
|
108
|
+
["basic+liveness", "plus+liveness+email", "plus+liveness+email+phoneNumber"],
|
|
109
|
+
"plus",
|
|
110
|
+
["email", "phoneNumber"],
|
|
111
|
+
"plus+liveness+email+phoneNumber",
|
|
112
|
+
],
|
|
113
|
+
[
|
|
114
|
+
["basic+liveness", "plus+liveness+email", "plus+liveness+email"],
|
|
115
|
+
"plus",
|
|
116
|
+
["email", "phoneNumber"],
|
|
117
|
+
null,
|
|
118
|
+
],
|
|
119
|
+
[["basic+liveness", "plus+liveness"], "basic", ["liveness"], "plus+liveness"],
|
|
120
|
+
].forEach(([levels, requiredLevel, requiredAddons, expected]) => {
|
|
121
|
+
it(`levels=${levels} requiredLevel=${requiredLevel} requiredAddons=[${(requiredAddons as string[]).join(",")}] => ${expected}`, () => {
|
|
122
|
+
expect(
|
|
123
|
+
pickHighestMatchingLevel(
|
|
124
|
+
levels as string[],
|
|
125
|
+
requiredLevel as "basic" | "plus",
|
|
126
|
+
requiredAddons as ("liveness" | "email" | "phoneNumber")[],
|
|
127
|
+
),
|
|
128
|
+
).toBe(expected);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe("highestMatchingCredential", () => {
|
|
134
|
+
it("choose basic, because plus did not match constraints", () => {
|
|
135
|
+
const credentials = [
|
|
136
|
+
{
|
|
137
|
+
public_notes: JSON.stringify({
|
|
138
|
+
level: "basic+liveness+email",
|
|
139
|
+
issuer: "custom-issuer",
|
|
140
|
+
status: "approved",
|
|
141
|
+
}),
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
public_notes: JSON.stringify({
|
|
145
|
+
level: "plus+liveness",
|
|
146
|
+
issuer: "custom-issuer",
|
|
147
|
+
status: "pending",
|
|
148
|
+
}),
|
|
149
|
+
},
|
|
150
|
+
];
|
|
151
|
+
|
|
152
|
+
const matchedCredentials = highestMatchingCredential(credentials, "basic", {
|
|
153
|
+
addons: ["email"],
|
|
154
|
+
publicNotesConstraint: { status: "approved" },
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
expect(matchedCredentials).toEqual(credentials[0]);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("choose plus+liveness+email+phoneNumber because it has better score", () => {
|
|
161
|
+
const credentials = [
|
|
162
|
+
{
|
|
163
|
+
public_notes: JSON.stringify({
|
|
164
|
+
level: "plus+liveness+email+phoneNumber",
|
|
165
|
+
}),
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
public_notes: JSON.stringify({
|
|
169
|
+
level: "plus+liveness+email",
|
|
170
|
+
}),
|
|
171
|
+
},
|
|
172
|
+
];
|
|
173
|
+
|
|
174
|
+
const matchedCredentials = highestMatchingCredential(credentials, "basic", {
|
|
175
|
+
addons: ["email"],
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
expect(matchedCredentials).toEqual(credentials[0]);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("choose plus+liveness because it has better score", () => {
|
|
182
|
+
const credentials = [
|
|
183
|
+
{
|
|
184
|
+
public_notes: JSON.stringify({
|
|
185
|
+
level: "basic+liveness+email+phoneNumber",
|
|
186
|
+
}),
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
public_notes: JSON.stringify({
|
|
190
|
+
level: "plus+liveness",
|
|
191
|
+
}),
|
|
192
|
+
},
|
|
193
|
+
];
|
|
194
|
+
|
|
195
|
+
const matchedCredentials = highestMatchingCredential(credentials, "basic", {
|
|
196
|
+
addons: ["liveness"],
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
expect(matchedCredentials).toEqual(credentials[1]);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it("choose basic+liveness+email because plus has no email", () => {
|
|
203
|
+
const credentials = [
|
|
204
|
+
{
|
|
205
|
+
public_notes: JSON.stringify({
|
|
206
|
+
level: "plus+liveness",
|
|
207
|
+
}),
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
public_notes: JSON.stringify({
|
|
211
|
+
level: "basic+liveness+email",
|
|
212
|
+
}),
|
|
213
|
+
},
|
|
214
|
+
];
|
|
215
|
+
|
|
216
|
+
const matchedCredentials = highestMatchingCredential(credentials, "basic", {
|
|
217
|
+
addons: ["email"],
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
expect(matchedCredentials).toEqual(credentials[1]);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it("choose nothing because it did not match the constraints", () => {
|
|
224
|
+
const credentials = [
|
|
225
|
+
{
|
|
226
|
+
public_notes: JSON.stringify({
|
|
227
|
+
level: "plus+liveness",
|
|
228
|
+
status: "pending",
|
|
229
|
+
type: "kyc",
|
|
230
|
+
}),
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
public_notes: JSON.stringify({
|
|
234
|
+
level: "basic+liveness+email",
|
|
235
|
+
status: "pending",
|
|
236
|
+
type: "kyc",
|
|
237
|
+
}),
|
|
238
|
+
},
|
|
239
|
+
];
|
|
240
|
+
|
|
241
|
+
const matchedCredentials = highestMatchingCredential(credentials, "basic", {
|
|
242
|
+
addons: ["liveness"],
|
|
243
|
+
publicNotesConstraint: { status: "approved", type: "kyc" },
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
expect(matchedCredentials).toBeUndefined();
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
describe("deriveLevel", () => {
|
|
251
|
+
it("basic only", () => {
|
|
252
|
+
expect(
|
|
253
|
+
deriveLevel({
|
|
254
|
+
...defaultCredential,
|
|
255
|
+
// @ts-expect-error - to test absence of selfieFile
|
|
256
|
+
selfieFile: undefined,
|
|
257
|
+
}),
|
|
258
|
+
).toBe("basic");
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it("basic+liveness", () => {
|
|
262
|
+
expect(
|
|
263
|
+
deriveLevel({
|
|
264
|
+
...defaultCredential,
|
|
265
|
+
}),
|
|
266
|
+
).toBe("basic+liveness");
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it("plus+liveness", () => {
|
|
270
|
+
expect(
|
|
271
|
+
deriveLevel({
|
|
272
|
+
...defaultCredential,
|
|
273
|
+
residentialAddress: {
|
|
274
|
+
street: "123 Main St",
|
|
275
|
+
city: "Anytown",
|
|
276
|
+
postalCode: "12345",
|
|
277
|
+
country: "US",
|
|
278
|
+
proofCategory: "UTILITY_BILL",
|
|
279
|
+
proofFile: Buffer.from("Utility Bill"),
|
|
280
|
+
},
|
|
281
|
+
}),
|
|
282
|
+
).toBe("plus+liveness");
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it("plus+liveness+phoneNumber", () => {
|
|
286
|
+
expect(
|
|
287
|
+
deriveLevel({
|
|
288
|
+
...defaultCredential,
|
|
289
|
+
selfieFile: Buffer.from("Selfie"),
|
|
290
|
+
phoneNumber: "+1234567890",
|
|
291
|
+
email: "john.doe@example.com",
|
|
292
|
+
residentialAddress: {
|
|
293
|
+
street: "123 Main St",
|
|
294
|
+
city: "Anytown",
|
|
295
|
+
postalCode: "12345",
|
|
296
|
+
country: "US",
|
|
297
|
+
proofCategory: "UTILITY_BILL",
|
|
298
|
+
proofFile: Buffer.from("Utility Bill"),
|
|
299
|
+
},
|
|
300
|
+
}),
|
|
301
|
+
).toBe("plus+liveness+email+phoneNumber");
|
|
302
|
+
});
|
|
303
|
+
});
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// Those are the types for the digitalbazaar libraries.
|
|
2
|
+
// There are missing a bunch of types, it's just what we need.
|
|
3
|
+
declare module "@digitalbazaar/ed25519-signature-2020" {
|
|
4
|
+
export interface Ed25519Signature2020Options {
|
|
5
|
+
key: Ed25519VerificationKey2020;
|
|
6
|
+
verificationMethod?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export declare class Ed25519Signature2020 {
|
|
10
|
+
constructor(options: Ed25519Signature2020Options);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
declare module "@digitalbazaar/ed25519-verification-key-2020" {
|
|
15
|
+
export interface Ed25519VerificationKey2020Options {
|
|
16
|
+
id?: string;
|
|
17
|
+
controller: string;
|
|
18
|
+
publicKeyMultibase: string;
|
|
19
|
+
privateKeyMultibase?: string;
|
|
20
|
+
type: "Ed25519VerificationKey2020";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface Ed25519VerificationKey2020GenerateOptions {
|
|
24
|
+
id?: string;
|
|
25
|
+
controller?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export declare class Ed25519VerificationKey2020 {
|
|
29
|
+
public static from(
|
|
30
|
+
options: Ed25519VerificationKey2020Options,
|
|
31
|
+
): Promise<Ed25519VerificationKey2020>;
|
|
32
|
+
public static generate(
|
|
33
|
+
options: Ed25519VerificationKey2020GenerateOptions,
|
|
34
|
+
): Promise<Ed25519VerificationKey2020>;
|
|
35
|
+
public controller: string;
|
|
36
|
+
public id: string;
|
|
37
|
+
public publicKeyMultibase: string;
|
|
38
|
+
public privateKeyMultibase?: string;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
declare module "@digitalbazaar/vc" {
|
|
43
|
+
export interface IssueOptions {
|
|
44
|
+
credential: object;
|
|
45
|
+
suite: Ed25519Signature2020;
|
|
46
|
+
documentLoader: JsonLDDocumentLoaderInstance;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface VerifiedCredentialsProof {
|
|
50
|
+
type: string;
|
|
51
|
+
created: string;
|
|
52
|
+
verificationMethod: string;
|
|
53
|
+
proofValue: string;
|
|
54
|
+
proofPurpose: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface VerifiedCredentials<K> {
|
|
58
|
+
"@context": string[];
|
|
59
|
+
type: string[];
|
|
60
|
+
issuer: string;
|
|
61
|
+
id: string;
|
|
62
|
+
level: string;
|
|
63
|
+
issued: string;
|
|
64
|
+
approvedAt: string;
|
|
65
|
+
expirationDate: string;
|
|
66
|
+
credentialSubject: K;
|
|
67
|
+
issuanceDate: string;
|
|
68
|
+
proof: VerifiedCredentialsProof;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// oxlint-disable-next-line typescript/no-explicit-any -- I don't know the right type
|
|
72
|
+
export declare function issue<K = any>(options: IssueOptions): Promise<VerifiedCredentials<K>>;
|
|
73
|
+
|
|
74
|
+
export interface VerifyCredentialOptions<K> {
|
|
75
|
+
credential: VerifiedCredentials<K>;
|
|
76
|
+
suite: Ed25519Signature2020;
|
|
77
|
+
controller: object;
|
|
78
|
+
documentLoader: JsonLDDocumentLoaderInstance;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface VerifyCredentialError {
|
|
82
|
+
name: string;
|
|
83
|
+
message: string;
|
|
84
|
+
stack: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface VerifyCredentialResult {
|
|
88
|
+
verified: boolean;
|
|
89
|
+
error?: {
|
|
90
|
+
name: string;
|
|
91
|
+
errors: VerifyCredentialError[];
|
|
92
|
+
};
|
|
93
|
+
results: {
|
|
94
|
+
proof: VerifiedCredentialsProof;
|
|
95
|
+
}[];
|
|
96
|
+
purposeResult: {
|
|
97
|
+
valid: boolean;
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export declare function verifyCredential<K>(
|
|
102
|
+
options: VerifyCredentialOptions<K>,
|
|
103
|
+
): Promise<VerifyCredentialResult>;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
declare module "jsonld-document-loader" {
|
|
107
|
+
export interface JsonLdDocument {
|
|
108
|
+
contextUrl?: string | null;
|
|
109
|
+
// oxlint-disable-next-line typescript/no-explicit-any -- I don't know the right type
|
|
110
|
+
document: any;
|
|
111
|
+
documentUrl: string;
|
|
112
|
+
tag?: string;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export type JsonLDDocumentLoaderInstance = (url: string) => Promise<JsonLdDocument>;
|
|
116
|
+
|
|
117
|
+
export class JsonLdDocumentLoader {
|
|
118
|
+
constructor();
|
|
119
|
+
|
|
120
|
+
addStatic(url: string, document: object): void;
|
|
121
|
+
|
|
122
|
+
build(): JsonLDDocumentLoaderInstance;
|
|
123
|
+
}
|
|
124
|
+
}
|