@idos-network/credentials 1.5.0 → 2.2.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.
Files changed (82) hide show
  1. package/README.md +289 -85
  2. package/assets/idos-credential-subject-edd-v1.json +10 -0
  3. package/assets/idos-credential-subject-v1.json +1 -1
  4. package/assets/idos-credential-subject-v2.json +1 -1
  5. package/assets/idos-credential-subject-v3.json +83 -0
  6. package/assets/idos-credentials-v1.json +2 -2
  7. package/assets/idos-credentials-v2.json +10 -0
  8. package/dist/index-0NqXMjNk.d.mts +395 -0
  9. package/dist/index-0NqXMjNk.d.mts.map +1 -0
  10. package/dist/index-WW372Tf5.d.mts +57 -0
  11. package/dist/index-WW372Tf5.d.mts.map +1 -0
  12. package/dist/index.mjs +1 -1
  13. package/dist/loader-Cg6hs1-P.mjs +2 -0
  14. package/dist/loader-Cg6hs1-P.mjs.map +1 -0
  15. package/dist/parser/index.d.mts +19 -0
  16. package/dist/parser/index.d.mts.map +1 -0
  17. package/dist/parser/index.mjs +2 -0
  18. package/dist/parser/index.mjs.map +1 -0
  19. package/dist/schemas/index.d.mts +2 -0
  20. package/dist/schemas/index.mjs +1 -0
  21. package/dist/schemas-BVGuUlPe.mjs +2 -0
  22. package/dist/schemas-BVGuUlPe.mjs.map +1 -0
  23. package/dist/types/index.d.mts +2 -134
  24. package/dist/types/index.mjs +1 -63
  25. package/dist/utils/index.d.mts +37 -15
  26. package/dist/utils/index.d.mts.map +1 -1
  27. package/dist/utils/index.mjs +1 -116
  28. package/dist/utils/index.mjs.map +1 -1
  29. package/dist/verifier/index.d.mts +8 -0
  30. package/dist/verifier/index.d.mts.map +1 -0
  31. package/dist/verifier/index.mjs +2 -0
  32. package/dist/verifier/index.mjs.map +1 -0
  33. package/package.json +21 -9
  34. package/src/parser/index.test.ts +266 -0
  35. package/src/parser/index.ts +69 -0
  36. package/src/schemas/EddV1/index.test.ts +40 -0
  37. package/src/schemas/EddV1/index.ts +53 -0
  38. package/src/schemas/Envelope.ts +37 -0
  39. package/src/schemas/FaceIdV1/index.test.ts +26 -0
  40. package/src/schemas/FaceIdV1/index.ts +33 -0
  41. package/src/schemas/KycV1/IdDocument.ts +41 -0
  42. package/src/schemas/KycV1/ResidentialAddress.ts +56 -0
  43. package/src/schemas/KycV1/Subject.ts +86 -0
  44. package/src/schemas/KycV1/index.test.ts +156 -0
  45. package/src/schemas/KycV1/index.ts +40 -0
  46. package/src/schemas/KycV2/Subject.ts +67 -0
  47. package/src/schemas/KycV2/index.test.ts +162 -0
  48. package/src/schemas/KycV2/index.ts +45 -0
  49. package/src/schemas/KycV3/Biometric.ts +14 -0
  50. package/src/schemas/KycV3/Contact.ts +13 -0
  51. package/src/schemas/KycV3/EDD.ts +32 -0
  52. package/src/schemas/KycV3/IdDocument.ts +92 -0
  53. package/src/schemas/KycV3/Onboarding.ts +36 -0
  54. package/src/schemas/KycV3/Person.ts +105 -0
  55. package/src/schemas/KycV3/ResidentialAddress.ts +73 -0
  56. package/src/schemas/KycV3/Screening.ts +27 -0
  57. package/src/schemas/KycV3/SourceOfWealth.ts +44 -0
  58. package/src/schemas/KycV3/Subject.ts +54 -0
  59. package/src/schemas/KycV3/index.test.ts +682 -0
  60. package/src/schemas/KycV3/index.ts +108 -0
  61. package/src/schemas/VerifiableCredential.ts +232 -0
  62. package/src/schemas/enums.ts +126 -0
  63. package/src/schemas/index.ts +14 -0
  64. package/src/schemas/integration.test.ts +251 -0
  65. package/src/schemas/types.ts +11 -0
  66. package/src/schemas/utils.ts +274 -0
  67. package/src/types/credentials.ts +18 -249
  68. package/src/utils/{utils.test.ts → index.test.ts} +59 -67
  69. package/src/utils/index.ts +129 -134
  70. package/src/utils/issuer.ts +42 -0
  71. package/src/{builder → utils}/loader.ts +22 -17
  72. package/src/verifier/index.test.ts +149 -0
  73. package/src/{builder/verifier.ts → verifier/index.ts} +4 -8
  74. package/types/internals.d.ts +11 -6
  75. package/dist/builder/index.d.mts +0 -19
  76. package/dist/builder/index.d.mts.map +0 -1
  77. package/dist/builder/index.mjs +0 -619
  78. package/dist/builder/index.mjs.map +0 -1
  79. package/dist/types/index.d.mts.map +0 -1
  80. package/dist/types/index.mjs.map +0 -1
  81. package/src/builder/builder.test.ts +0 -407
  82. package/src/builder/index.ts +0 -122
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/types/credentials.ts"],"sourcesContent":["import * as z from \"zod\";\n\nexport type idOSCredential = {\n id: string;\n user_id: string;\n public_notes: string;\n content: string;\n encryptor_public_key: string;\n issuer_auth_public_key: string;\n original_id?: string | null;\n};\n\nexport type InsertableIDOSCredential = Omit<idOSCredential, \"id\" | \"original_id\"> & {\n id?: idOSCredential[\"id\"];\n content_hash?: string;\n public_notes_signature: string;\n broader_signature: string;\n};\n\n// https://github.com/colinhacks/zod/issues/3751\nexport const CredentialFieldsSchema: z.ZodObject<{\n id: z.ZodString;\n level: z.ZodString;\n issued: z.ZodOptional<z.ZodDate>;\n approvedAt: z.ZodOptional<z.ZodDate>;\n expirationDate: z.ZodOptional<z.ZodDate>;\n}> = z.object({\n id: z.string(),\n\n /* Level of KYC verification performed (e.g., basic, intermediate, advanced). */\n level: z.string(),\n\n /* @default Date.now() */\n issued: z.date().optional(),\n\n /* Date the credential was approved. */\n approvedAt: z.date().optional(),\n\n /* Date the credential was revoked. */\n expirationDate: z.date().optional(),\n});\n\nexport type CredentialFields = z.infer<typeof CredentialFieldsSchema>;\n\n// https://github.com/colinhacks/zod/issues/3751\nexport const CredentialResidentialAddressSchema: z.ZodObject<{\n street: z.ZodString;\n houseNumber: z.ZodOptional<z.ZodString>;\n additionalAddressInfo: z.ZodOptional<z.ZodString>;\n region: z.ZodOptional<z.ZodString>;\n city: z.ZodString;\n postalCode: z.ZodOptional<z.ZodString>;\n country: z.ZodString;\n proofCategory: z.ZodString;\n proofDateOfIssue: z.ZodOptional<z.ZodDate>;\n proofFile: z.ZodType<Buffer<ArrayBufferLike>>;\n}> = z.object({\n /* Street address. */\n street: z.string(),\n\n /* House number. */\n houseNumber: z.string().optional(),\n\n /* Additional address information (e.g., apartment number). */\n additionalAddressInfo: z.string().optional(),\n\n /* Region (e.g., state, province). */\n region: z.string().optional(),\n\n /* Locality (e.g., city, town). */\n city: z.string(),\n\n /* Postal code. */\n postalCode: z.string().optional(),\n\n /* Country (ISO 3166-1 alpha-2). */\n country: z.string().min(2).max(2),\n\n /* Residential Address Proof Category\tType of document provided to verify the address(e.g., utility bill, bank statement). */\n proofCategory: z.string(),\n\n /* Residential Address Proof Date Of Issue\tDate the address proof document was issued. */\n proofDateOfIssue: z.date().optional(),\n\n /* Residential Address Proof File or URL of the document provided as address proof. */\n proofFile: z.instanceof(Buffer),\n});\n\nexport type CredentialResidentialAddress = z.infer<typeof CredentialResidentialAddressSchema>;\n\nexport const IDDocumentTypeSchema: z.ZodEnum<{\n PASSPORT: \"PASSPORT\";\n DRIVERS: \"DRIVERS\";\n ID_CARD: \"ID_CARD\";\n VOTING_CARD: \"VOTING_CARD\";\n PAN_CARD: \"PAN_CARD\";\n INTERNAL_PASSPORT: \"INTERNAL_PASSPORT\";\n RESIDENCE_PERMIT: \"RESIDENCE_PERMIT\";\n}> = z.enum([\n \"PASSPORT\",\n \"DRIVERS\",\n \"ID_CARD\",\n \"VOTING_CARD\",\n \"PAN_CARD\",\n \"INTERNAL_PASSPORT\",\n \"RESIDENCE_PERMIT\",\n]);\nexport type IDDocumentType = z.infer<typeof IDDocumentTypeSchema>;\n\nexport const GenderSchema: z.ZodEnum<{\n M: \"M\";\n F: \"F\";\n OTHER: \"OTHER\";\n}> = z.enum([\"M\", \"F\", \"OTHER\"]);\nexport type Gender = z.infer<typeof GenderSchema>;\n\n// https://github.com/colinhacks/zod/issues/3751\nexport const CredentialSubjectSchema: z.ZodObject<{\n id: z.ZodString;\n // Indonesian and Indian did not always have first name\n firstName: z.ZodOptional<z.ZodString>;\n middleName: z.ZodOptional<z.ZodString>;\n ssn: z.ZodOptional<z.ZodString>;\n gender: z.ZodOptional<typeof GenderSchema>;\n nationality: z.ZodOptional<z.ZodString>;\n // Indonesian and Indian did not always have family name\n familyName: z.ZodOptional<z.ZodString>;\n maidenName: z.ZodOptional<z.ZodString>;\n email: z.ZodOptional<z.ZodEmail>;\n phoneNumber: z.ZodOptional<z.ZodString>;\n dateOfBirth: z.ZodDate;\n placeOfBirth: z.ZodOptional<z.ZodString>;\n idDocumentCountry: z.ZodString;\n idDocumentNumber: z.ZodString;\n idDocumentType: typeof IDDocumentTypeSchema;\n idDocumentDateOfIssue: z.ZodOptional<z.ZodDate>;\n idDocumentDateOfExpiry: z.ZodOptional<z.ZodDate>;\n idDocumentFrontFile: z.ZodType<Buffer<ArrayBufferLike>>;\n idDocumentBackFile: z.ZodOptional<z.ZodType<Buffer<ArrayBufferLike>>>;\n selfieFile: z.ZodType<Buffer<ArrayBufferLike>>;\n residentialAddress: z.ZodOptional<typeof CredentialResidentialAddressSchema>;\n}> = z\n .object({\n /* ID(unique credential)\tUnique identifier for the credential itself. */\n id: z.string(),\n\n /* First name. */\n firstName: z.string().optional(),\n\n /* Middle name. */\n middleName: z.string().optional(),\n\n /* Social-security-number (without dashes) */\n ssn: z.string().min(9).max(9).optional(),\n\n /* Nationality (ISO 3166-1 alpha-2). */\n nationality: z.string().min(2).max(2).optional(),\n\n /* Gender (M or F, empty if not provided). */\n gender: GenderSchema.optional(),\n\n /* Family name. */\n familyName: z.string().optional(),\n\n /* Maiden name. */\n maidenName: z.string().optional(),\n\n /* Date of birth. */\n dateOfBirth: z.date(),\n\n /* Place of birth. */\n placeOfBirth: z.string().optional(),\n\n /* Email. */\n email: z.email().optional(),\n\n /* Phone number. */\n phoneNumber: z.string().optional(),\n\n /* Country that issued the identity document (ISO 3166-1 alpha-2). */\n idDocumentCountry: z.string().min(2).max(2),\n\n /* ID Document Number\tUnique number on the identity document. */\n idDocumentNumber: z.string(),\n\n /* ID Document Type\tType of identity document(e.g., Passport, Driver's License, National ID). */\n idDocumentType: IDDocumentTypeSchema,\n\n /* ID Document Date of Issue\tDate the identity document was issued. */\n idDocumentDateOfIssue: z.date().optional(),\n\n /* ID Document Date of Expiry\tExpiration date of the identity document - if applicable. */\n idDocumentDateOfExpiry: z.date().optional(),\n\n /* ID Document Front File\tBuffer with file representing the front of the identity document. */\n idDocumentFrontFile: z.instanceof(Buffer),\n\n /* ID Document Back File\tBuffer with file representing the back of the identity document - if applicable. */\n idDocumentBackFile: z.instanceof(Buffer).optional(),\n\n /* (ID Document) Selfie File\tBuffer with selfie with the identity document for verification purposes. */\n selfieFile: z.instanceof(Buffer),\n\n /* Residential Address Full residential address of the individual - if applicable. */\n residentialAddress: CredentialResidentialAddressSchema.optional(),\n })\n .refine(\n // At least one of firstName or familyName must be present,\n (data) => data.firstName || data.familyName,\n { message: \"At least one of firstName or familyName must be provided\" },\n );\n\nexport type CredentialSubject = z.infer<typeof CredentialSubjectSchema>;\n\nexport interface VerifiableCredentialSubject extends Omit<\n CredentialSubject,\n | \"residentialAddress\"\n | \"idDocumentBackFile\"\n | \"idDocumentFrontFile\"\n | \"selfieFile\"\n | \"idDocumentDateOfIssue\"\n | \"idDocumentDateOfExpiry\"\n | \"dateOfBirth\"\n> {\n \"@context\": string;\n // Files are strings in verifiable credentials, but as an input they should be buffers\n idDocumentFrontFile?: string;\n idDocumentBackFile?: string;\n selfieFile?: string;\n // Dates are strings in verifiable credentials\n dateOfBirth?: string;\n idDocumentDateOfIssue?: string;\n idDocumentDateOfExpiry?: string;\n // This is the residential address flattened fields\n residentialAddressStreet?: string;\n residentialAddressHouseNumber?: string;\n residentialAddressAdditionalAddressInfo?: string;\n residentialAddressRegion?: string;\n residentialAddressCity?: string;\n residentialAddressPostalCode?: string;\n residentialAddressCountry?: string;\n residentialAddressProofCategory?: string;\n // Dates are strings in verifiable credentials\n residentialAddressProofDateOfIssue?: string;\n // Files are strings in verifiable credentials, but as an input they should be buffers\n residentialAddressProofFile?: string;\n}\n\n// TODO: This is a stub of the types for @digitalbazaar/vc\n// when they introduce TypeScript support we should remove this\n// The copy is here because `types.d.ts` file is not bundled.\nexport interface VerifiedCredentialsProof {\n type: string;\n created: string;\n verificationMethod: string;\n proofValue: string;\n proofPurpose: string;\n}\n\nexport const CredentialSubjectFaceIdSchema: z.ZodObject<{\n faceSignUserId: z.ZodString;\n}> = z.object({\n faceSignUserId: z.string(),\n});\n\nexport type CredentialSubjectFaceId = z.infer<typeof CredentialSubjectFaceIdSchema>;\n\nexport interface VerifiedCredentials<K> {\n \"@context\": string[];\n type: string[];\n issuer: string;\n id: string;\n level: string;\n issued: string;\n approvedAt: string;\n expirationDate: string;\n credentialSubject: K;\n issuanceDate: string;\n proof: VerifiedCredentialsProof;\n}\n\nexport type VerifiableCredential<K> = VerifiedCredentials<K>;\n"],"mappings":";;AAoBA,MAAa,yBAMR,EAAE,OAAO;CACZ,IAAI,EAAE,OAAO;CAGb,OAAO,EAAE,OAAO;CAGhB,QAAQ,EAAE,KAAK,CAAC,CAAC,SAAS;CAG1B,YAAY,EAAE,KAAK,CAAC,CAAC,SAAS;CAG9B,gBAAgB,EAAE,KAAK,CAAC,CAAC,SAAS;AACpC,CAAC;AAKD,MAAa,qCAWR,EAAE,OAAO;CAEZ,QAAQ,EAAE,OAAO;CAGjB,aAAa,EAAE,OAAO,CAAC,CAAC,SAAS;CAGjC,uBAAuB,EAAE,OAAO,CAAC,CAAC,SAAS;CAG3C,QAAQ,EAAE,OAAO,CAAC,CAAC,SAAS;CAG5B,MAAM,EAAE,OAAO;CAGf,YAAY,EAAE,OAAO,CAAC,CAAC,SAAS;CAGhC,SAAS,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;CAGhC,eAAe,EAAE,OAAO;CAGxB,kBAAkB,EAAE,KAAK,CAAC,CAAC,SAAS;CAGpC,WAAW,EAAE,WAAW,MAAM;AAChC,CAAC;AAID,MAAa,uBAQR,EAAE,KAAK;CACV;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AAGD,MAAa,eAIR,EAAE,KAAK;CAAC;CAAK;CAAK;AAAO,CAAC;AAI/B,MAAa,0BAwBR,EACF,OAAO;CAEN,IAAI,EAAE,OAAO;CAGb,WAAW,EAAE,OAAO,CAAC,CAAC,SAAS;CAG/B,YAAY,EAAE,OAAO,CAAC,CAAC,SAAS;CAGhC,KAAK,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;CAGvC,aAAa,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;CAG/C,QAAQ,aAAa,SAAS;CAG9B,YAAY,EAAE,OAAO,CAAC,CAAC,SAAS;CAGhC,YAAY,EAAE,OAAO,CAAC,CAAC,SAAS;CAGhC,aAAa,EAAE,KAAK;CAGpB,cAAc,EAAE,OAAO,CAAC,CAAC,SAAS;CAGlC,OAAO,EAAE,MAAM,CAAC,CAAC,SAAS;CAG1B,aAAa,EAAE,OAAO,CAAC,CAAC,SAAS;CAGjC,mBAAmB,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;CAG1C,kBAAkB,EAAE,OAAO;CAG3B,gBAAgB;CAGhB,uBAAuB,EAAE,KAAK,CAAC,CAAC,SAAS;CAGzC,wBAAwB,EAAE,KAAK,CAAC,CAAC,SAAS;CAG1C,qBAAqB,EAAE,WAAW,MAAM;CAGxC,oBAAoB,EAAE,WAAW,MAAM,CAAC,CAAC,SAAS;CAGlD,YAAY,EAAE,WAAW,MAAM;CAG/B,oBAAoB,mCAAmC,SAAS;AAClE,CAAC,CAAC,CACD,QAEE,SAAS,KAAK,aAAa,KAAK,YACjC,EAAE,SAAS,2DAA2D,CACxE;AAiDF,MAAa,gCAER,EAAE,OAAO,EACZ,gBAAgB,EAAE,OAAO,EAC3B,CAAC"}
@@ -1,407 +0,0 @@
1
- import { Ed25519VerificationKey2020 } from "@digitalbazaar/ed25519-verification-key-2020";
2
- import { fileToBase85 } from "@idos-network/utils/codecs";
3
- import crypto from "node:crypto";
4
- import { describe, expect, it } from "vitest";
5
- import { ZodError } from "zod";
6
-
7
- import { buildCredential, buildFaceIdCredential } from ".";
8
- import { verifyCredential } from "./verifier";
9
-
10
- describe("verifiableCredentials", () => {
11
- describe("face-scan-id", () => {
12
- it("should raise an error if the fields are invalid", async () => {
13
- expect.assertions(4); // catch
14
-
15
- const id = "z6MkszZtxCmA2Ce4vUV132PCuLQmwnaDD5mw2L23fGNnsiX3";
16
- const issuer = "https://vc-issuers.cool.id/idos";
17
-
18
- const validKey = await Ed25519VerificationKey2020.generate({
19
- id: `${issuer}/keys/1`,
20
- controller: `${issuer}/issuers/1`,
21
- });
22
-
23
- try {
24
- await buildFaceIdCredential(
25
- {
26
- id: `${issuer}/credentials/${id}`,
27
- level: "human",
28
- issued: new Date("2022-01-01"),
29
- approvedAt: new Date("2022-01-01"),
30
- expirationDate: new Date("2030-01-01"),
31
- },
32
- {
33
- // @ts-expect-error
34
- id: `uuid:${id}`,
35
- },
36
- validKey,
37
- );
38
- } catch (error) {
39
- expect(error).toBeInstanceOf(ZodError);
40
- const zodError = error as ZodError;
41
- expect(zodError.issues).toHaveLength(1);
42
-
43
- const faceSignUserId = zodError.issues.find((error) => error.path[0] === "faceSignUserId");
44
- expect(faceSignUserId).toBeDefined();
45
- expect(faceSignUserId?.message).toContain(
46
- "Invalid input: expected string, received undefined",
47
- );
48
- }
49
- });
50
-
51
- it("should create and verify a verifiable vc", async () => {
52
- const id = "z6MkszZtxCmA2Ce4vUV132PCuLQmwnaDD5mw2L23fGNnsiX3";
53
- const issuer = "https://vc-issuers.cool.id/idos";
54
- const issuer2 = "https://vc-issuers.cool.id/idos2";
55
-
56
- const validKey = await Ed25519VerificationKey2020.generate({
57
- id: `${issuer}/keys/1`,
58
- controller: `${issuer}/issuers/1`,
59
- });
60
-
61
- const anotherKey = await Ed25519VerificationKey2020.generate({
62
- id: `${issuer2}/keys/1`,
63
- controller: `${issuer2}/issuers/1`,
64
- });
65
-
66
- const faceSignUserId = crypto.randomUUID();
67
-
68
- const data = await buildFaceIdCredential(
69
- {
70
- id: `${issuer}/credentials/${id}`,
71
- level: "human",
72
- issued: new Date("2022-01-01"),
73
- approvedAt: new Date("2022-01-01"),
74
- expirationDate: new Date("2030-01-01"),
75
- },
76
- {
77
- faceSignUserId,
78
- },
79
- validKey,
80
- );
81
-
82
- // Check if residential address is properly prefixed
83
- expect(data.credentialSubject.faceSignUserId).toBe(faceSignUserId);
84
-
85
- // Check if proof is properly constructed
86
- expect(data.proof.proofPurpose).toBe("assertionMethod");
87
- expect(data.proof.type).toBe("Ed25519Signature2020");
88
- expect(data.proof.verificationMethod).toBe("https://vc-issuers.cool.id/idos/keys/1");
89
-
90
- const allowedIssuers = [anotherKey, validKey];
91
-
92
- const [verified, validResults] = await verifyCredential(data, allowedIssuers);
93
- expect(verified).toBe(true);
94
-
95
- const validResultsArray = validResults.values().toArray();
96
- expect(validResultsArray).toHaveLength(2);
97
- expect(validResultsArray[0].verified).toBe(false);
98
- expect(validResultsArray[1].verified).toBe(true);
99
- });
100
-
101
- it("tempered VCs will be rejected", async () => {
102
- const id = "z6MkszZtxCmA2Ce4vUV132PCuLQmwnaDD5mw2L23fGNnsiX3";
103
- const issuer = "https://vc-issuers.cool.id/idos";
104
-
105
- const validKey = await Ed25519VerificationKey2020.generate({
106
- id: `${issuer}/keys/1`,
107
- controller: `${issuer}/issuers/1`,
108
- });
109
-
110
- const faceSignUserId = crypto.randomUUID();
111
- const faceSignUserId2 = crypto.randomUUID();
112
-
113
- const data = await buildFaceIdCredential(
114
- {
115
- id: `${issuer}/credentials/${id}`,
116
- level: "human",
117
- issued: new Date("2022-01-01"),
118
- approvedAt: new Date("2022-01-01"),
119
- expirationDate: new Date("2030-01-01"),
120
- },
121
- {
122
- faceSignUserId,
123
- },
124
- validKey,
125
- );
126
-
127
- // Check validity
128
- const allowedIssuers = [validKey];
129
- const [verified] = await verifyCredential(data, allowedIssuers);
130
- expect(verified).toBe(true);
131
-
132
- // Modify data
133
- data.credentialSubject.faceSignUserId = faceSignUserId2;
134
- const [verified2] = await verifyCredential(data, allowedIssuers);
135
- expect(verified2).toBe(false);
136
- });
137
- });
138
-
139
- describe("credentials", () => {
140
- it("should raise an error if the fields are invalid", async () => {
141
- expect.assertions(12); // catch
142
-
143
- const id = "z6MkszZtxCmA2Ce4vUV132PCuLQmwnaDD5mw2L23fGNnsiX3";
144
- const issuer = "https://vc-issuers.cool.id/idos";
145
-
146
- const validKey = await Ed25519VerificationKey2020.generate({
147
- id: `${issuer}/keys/1`,
148
- controller: `${issuer}/issuers/1`,
149
- });
150
-
151
- try {
152
- await buildCredential(
153
- {
154
- id: `${issuer}/credentials/${id}`,
155
- level: "human",
156
- issued: new Date("2022-01-01"),
157
- approvedAt: new Date("2022-01-01"),
158
- expirationDate: new Date("2030-01-01"),
159
- },
160
- {
161
- id: `uuid:${id}`,
162
- firstName: "John",
163
- familyName: "Lennon",
164
- // @ts-expect-error - This is a test, so the error is expected, since data is invalid
165
- gender: "MALE",
166
- email: "john.lennon@example.com",
167
- phoneNumber: "+1234567890",
168
- ssn: "203-456",
169
- dateOfBirth: new Date("2025-02-30"),
170
- placeOfBirth: "New York, NY",
171
- idDocumentCountry: "DEU",
172
- idDocumentNumber: "123456789",
173
- idDocumentType: "PASSPORT",
174
- idDocumentDateOfIssue: new Date("2022-01-01"),
175
- idDocumentDateOfExpiry: new Date("2025-01-01"),
176
- idDocumentFrontFile: Buffer.from("Front of ID document"),
177
- residentialAddress: {
178
- street: "Main St",
179
- houseNumber: "123",
180
- additionalAddressInfo: "Apt 1",
181
- city: "New York",
182
- postalCode: "10001",
183
- country: "USA",
184
- proofCategory: "Utility Bill",
185
- proofDateOfIssue: new Date("2022-01-01"),
186
- proofFile: Buffer.from("Proof of address"),
187
- },
188
- },
189
- validKey,
190
- );
191
- } catch (error) {
192
- expect(error).toBeInstanceOf(ZodError);
193
- const zodError = error as ZodError;
194
- expect(zodError.issues).toHaveLength(5);
195
-
196
- // idDocumentCountry
197
- const idDocumentCountryError = zodError.issues.find(
198
- (error) => error.path[0] === "idDocumentCountry",
199
- );
200
- expect(idDocumentCountryError).toBeDefined();
201
- expect(idDocumentCountryError?.message).toContain(
202
- "Too big: expected string to have <=2 characters",
203
- );
204
-
205
- // selfieFile
206
- const selfieFileError = zodError.issues.find((error) => error.path[0] === "selfieFile");
207
- expect(selfieFileError).toBeDefined();
208
- expect(selfieFileError?.message).toContain("expected Buffer, received undefined");
209
-
210
- // residentialAddress country
211
- const residentialAddressCountryError = zodError.issues.find(
212
- (error) => error.path[0] === "residentialAddress",
213
- );
214
- expect(residentialAddressCountryError).toBeDefined();
215
- expect(residentialAddressCountryError?.message).toContain(
216
- "Too big: expected string to have <=2 characters",
217
- );
218
-
219
- // gender
220
- const genderError = zodError.issues.find((error) => error.path[0] === "gender");
221
- expect(genderError).toBeDefined();
222
- expect(genderError?.message).toContain('Invalid option: expected one of "M"|"F"|"OTHER"');
223
-
224
- // ssn
225
- const ssnError = zodError.issues.find((error) => error.path[0] === "ssn");
226
- expect(ssnError).toBeDefined();
227
- expect(ssnError?.message).toContain("Too small: expected string to have >=9 characters");
228
- }
229
- });
230
-
231
- it("test refinement (at least one of firstName or familyName must be provided)", async () => {
232
- const id = "z6MkszZtxCmA2Ce4vUV132PCuLQmwnaDD5mw2L23fGNnsiX3";
233
- const issuer = "https://vc-issuers.cool.id/idos";
234
-
235
- const validKey = await Ed25519VerificationKey2020.generate({
236
- id: `${issuer}/keys/1`,
237
- controller: `${issuer}/issuers/1`,
238
- });
239
-
240
- expect.assertions(3); // catch
241
-
242
- try {
243
- await buildCredential(
244
- {
245
- id: `${issuer}/credentials/${id}`,
246
- level: "human",
247
- issued: new Date("2022-01-01"),
248
- approvedAt: new Date("2022-01-01"),
249
- expirationDate: new Date("2030-01-01"),
250
- },
251
- {
252
- id: `uuid:${id}`,
253
- ssn: "123456789",
254
- firstName: "",
255
- familyName: undefined,
256
- gender: "M",
257
- nationality: "US",
258
- email: "john.lennon@example.com",
259
- phoneNumber: "+1234567890",
260
- dateOfBirth: new Date("1980-01-01"),
261
- placeOfBirth: "New York, NY",
262
- idDocumentCountry: "US",
263
- idDocumentNumber: "123456789",
264
- idDocumentType: "PASSPORT",
265
- idDocumentDateOfIssue: new Date("2022-01-01"),
266
- idDocumentDateOfExpiry: new Date("2025-01-01"),
267
- idDocumentFrontFile: Buffer.from("Front of ID document"),
268
- idDocumentBackFile: Buffer.from("Back of ID document"),
269
- selfieFile: Buffer.from("Selfie"),
270
- },
271
- validKey,
272
- );
273
- } catch (error) {
274
- expect(error).toBeInstanceOf(ZodError);
275
- const zodError = error as ZodError;
276
- expect(zodError.issues).toHaveLength(1);
277
-
278
- const refinementError = zodError.issues.find(
279
- (error) => error.message === "At least one of firstName or familyName must be provided",
280
- );
281
- expect(refinementError).toBeDefined();
282
- }
283
- });
284
-
285
- it("should create and verify a verifiable vc", async () => {
286
- const id = "z6MkszZtxCmA2Ce4vUV132PCuLQmwnaDD5mw2L23fGNnsiX3";
287
- const issuer = "https://vc-issuers.cool.id/idos";
288
- const issuer2 = "https://vc-issuers.cool.id/idos2";
289
-
290
- const validKey = await Ed25519VerificationKey2020.generate({
291
- id: `${issuer}/keys/1`,
292
- controller: `${issuer}/issuers/1`,
293
- });
294
-
295
- const anotherKey = await Ed25519VerificationKey2020.generate({
296
- id: `${issuer2}/keys/1`,
297
- controller: `${issuer2}/issuers/1`,
298
- });
299
-
300
- const data = await buildCredential(
301
- {
302
- id: `${issuer}/credentials/${id}`,
303
- level: "human",
304
- issued: new Date("2022-01-01"),
305
- approvedAt: new Date("2022-01-01"),
306
- expirationDate: new Date("2030-01-01"),
307
- },
308
- {
309
- id: `uuid:${id}`,
310
- firstName: "John",
311
- middleName: "Paul",
312
- familyName: "Lennon",
313
- ssn: "123456789",
314
- gender: "M",
315
- nationality: "US",
316
- email: "john.lennon@example.com",
317
- phoneNumber: "+1234567890",
318
- dateOfBirth: new Date("1980-01-01"),
319
- placeOfBirth: "New York, NY",
320
- idDocumentCountry: "US",
321
- idDocumentNumber: "123456789",
322
- idDocumentType: "PASSPORT",
323
- idDocumentDateOfIssue: new Date("2022-01-01"),
324
- idDocumentDateOfExpiry: new Date("2025-01-01"),
325
- idDocumentFrontFile: Buffer.from("Front of ID document"),
326
- idDocumentBackFile: Buffer.from("Back of ID document"),
327
- selfieFile: Buffer.from("Selfie"),
328
- residentialAddress: {
329
- street: "Main St",
330
- houseNumber: "123",
331
- additionalAddressInfo: "Apt 1",
332
- region: "NY",
333
- city: "New York",
334
- postalCode: "10001",
335
- country: "US",
336
- proofCategory: "Utility Bill",
337
- proofDateOfIssue: new Date("2022-01-01"),
338
- proofFile: Buffer.from("Proof of address"),
339
- },
340
- },
341
- validKey,
342
- );
343
-
344
- // Check if residential address is properly prefixed
345
- expect(data.credentialSubject.residentialAddressStreet).toBe("Main St");
346
- expect(data.credentialSubject.residentialAddressHouseNumber).toBe("123");
347
- expect(data.credentialSubject.residentialAddressAdditionalAddressInfo).toBe("Apt 1");
348
- expect(data.credentialSubject.residentialAddressCity).toBe("New York");
349
- expect(data.credentialSubject.residentialAddressPostalCode).toBe("10001");
350
- expect(data.credentialSubject.residentialAddressCountry).toBe("US");
351
- expect(data.credentialSubject.residentialAddressRegion).toBe("NY");
352
- expect(data.credentialSubject.residentialAddressProofCategory).toBe("Utility Bill");
353
- expect(data.credentialSubject.residentialAddressProofDateOfIssue).toEqual(
354
- new Date("2022-01-01").toISOString(),
355
- );
356
- expect(data.credentialSubject.residentialAddressProofFile).toEqual(
357
- fileToBase85(Buffer.from("Proof of address")),
358
- );
359
-
360
- expect(data.credentialSubject.email).toBe("john.lennon@example.com");
361
- expect(data.credentialSubject.phoneNumber).toBe("+1234567890");
362
-
363
- // Check if proof is properly constructed
364
- expect(data.proof.proofPurpose).toBe("assertionMethod");
365
- expect(data.proof.type).toBe("Ed25519Signature2020");
366
- expect(data.proof.verificationMethod).toBe("https://vc-issuers.cool.id/idos/keys/1");
367
-
368
- const allowedIssuers = [anotherKey, validKey];
369
-
370
- const [verified, validResults] = await verifyCredential(data, allowedIssuers);
371
- expect(verified).toBe(true);
372
-
373
- const validResultsArray = validResults.values().toArray();
374
- expect(validResultsArray).toHaveLength(2);
375
- expect(validResultsArray[0].verified).toBe(false);
376
- expect(validResultsArray[1].verified).toBe(true);
377
-
378
- // Invalid issuer
379
- const invalidIssuers = [
380
- {
381
- // Just the wrong issuer
382
- issuer: issuer2,
383
- publicKeyMultibase: anotherKey.publicKeyMultibase,
384
- },
385
- ];
386
-
387
- const [invalidVerified, invalidResults] = await verifyCredential(data, invalidIssuers);
388
- expect(invalidVerified).toBe(false);
389
-
390
- const invalidResultsArray = invalidResults.values().toArray();
391
- expect(invalidResultsArray).toHaveLength(1);
392
- expect(invalidResultsArray[0].verified).toBe(false);
393
- expect(invalidResultsArray[0].error?.errors?.[0]?.message).toContain("not verify any proofs");
394
-
395
- // Accepts issuer as issuer object
396
- const validIssuer = [
397
- {
398
- issuer,
399
- publicKeyMultibase: validKey.publicKeyMultibase,
400
- },
401
- ];
402
-
403
- const [validIssuerVerified] = await verifyCredential(data, validIssuer);
404
- expect(validIssuerVerified).toBe(true);
405
- });
406
- });
407
- });
@@ -1,122 +0,0 @@
1
- import { Ed25519Signature2020 } from "@digitalbazaar/ed25519-signature-2020";
2
- import * as vc from "@digitalbazaar/vc";
3
-
4
- import type { VerifyCredentialResult } from "./verifier";
5
-
6
- import {
7
- type AvailableIssuerType,
8
- type CredentialFields,
9
- CredentialFieldsSchema,
10
- type CredentialSubject,
11
- type CredentialSubjectFaceId,
12
- CredentialSubjectFaceIdSchema,
13
- CredentialSubjectSchema,
14
- type VerifiableCredential,
15
- type VerifiableCredentialSubject,
16
- } from "../types";
17
- import { convertValues, issuerToKey } from "../utils";
18
- import {
19
- CONTEXT_IDOS_CREDENTIAL,
20
- CONTEXT_IDOS_CREDENTIAL_FACE_ID,
21
- CONTEXT_IDOS_CREDENTIAL_SUBJECT,
22
- CONTEXT_IDOS_SIGNATURE,
23
- CONTEXT_V1,
24
- defaultDocumentLoader,
25
- } from "./loader";
26
- import { verifyCredential } from "./verifier";
27
-
28
- export type Credential = VerifiableCredential<VerifiableCredentialSubject>;
29
- export type FaceIdCredential = VerifiableCredential<CredentialSubjectFaceId>;
30
-
31
- export type CredentialBuilder<TInput, TOutput = TInput> = (
32
- fields: CredentialFields,
33
- subject: TInput,
34
- issuer: AvailableIssuerType,
35
- validate?: boolean,
36
- ) => Promise<VerifiableCredential<TOutput>>;
37
-
38
- export type CredentialConverter<TInput> = (
39
- subject: TInput,
40
- validate: boolean,
41
- // oxlint-disable-next-line typescript/no-explicit-any -- any is needed here
42
- ) => Record<string, any>;
43
-
44
- function genericCredentialBuilder<TInput, TOutput = TInput>(
45
- credentialConverter: CredentialConverter<TInput>,
46
- ): CredentialBuilder<TInput, TOutput> {
47
- async function builder(
48
- fields: CredentialFields,
49
- subject: TInput,
50
- issuer: AvailableIssuerType,
51
- validate = true,
52
- ) {
53
- if (validate) {
54
- // This raises an z.ZodError exception if the fields are invalid
55
- CredentialFieldsSchema.parse(fields);
56
- }
57
-
58
- const credentialSubject = credentialConverter(subject, validate);
59
-
60
- const key = await issuerToKey(issuer);
61
-
62
- // Create credentials container
63
- const credential = {
64
- "@context": [CONTEXT_V1, CONTEXT_IDOS_CREDENTIAL, CONTEXT_IDOS_SIGNATURE],
65
- type: ["VerifiableCredential"],
66
- issuer: key.controller,
67
- ...convertValues(fields),
68
- credentialSubject,
69
- };
70
-
71
- const suite = new Ed25519Signature2020({ key });
72
-
73
- return vc.issue<TOutput>({
74
- credential,
75
- suite,
76
- documentLoader: defaultDocumentLoader,
77
- });
78
- }
79
-
80
- return builder;
81
- }
82
-
83
- export const credentialSubjectConverter: CredentialConverter<CredentialSubject> = (
84
- subject,
85
- validate,
86
- ) => {
87
- if (validate) {
88
- CredentialSubjectSchema.parse(subject);
89
- }
90
-
91
- const { residentialAddress, ...subjectData } = subject;
92
- return {
93
- "@context": CONTEXT_IDOS_CREDENTIAL_SUBJECT,
94
- ...convertValues(subjectData),
95
- ...(residentialAddress ? convertValues(residentialAddress, "residentialAddress") : {}),
96
- };
97
- };
98
-
99
- export const credentialFaceIdSubjectConverter: CredentialConverter<CredentialSubjectFaceId> = (
100
- subject,
101
- validate,
102
- ) => {
103
- if (validate) {
104
- CredentialSubjectFaceIdSchema.parse(subject);
105
- }
106
-
107
- return {
108
- "@context": CONTEXT_IDOS_CREDENTIAL_FACE_ID,
109
- ...convertValues(subject),
110
- };
111
- };
112
-
113
- export const buildCredential: CredentialBuilder<CredentialSubject, VerifiableCredentialSubject> =
114
- genericCredentialBuilder<CredentialSubject, VerifiableCredentialSubject>(
115
- credentialSubjectConverter,
116
- );
117
-
118
- export const buildFaceIdCredential: CredentialBuilder<CredentialSubjectFaceId> =
119
- genericCredentialBuilder<CredentialSubjectFaceId>(credentialFaceIdSubjectConverter);
120
-
121
- export type { VerifyCredentialResult };
122
- export { verifyCredential };