@sd-jwt/sd-jwt-vc 0.17.2-next.4 → 0.17.2-next.7
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/index.d.mts +282 -104
- package/dist/index.d.ts +282 -104
- package/dist/index.js +338 -52
- package/dist/index.mjs +317 -52
- package/package.json +8 -7
- package/src/sd-jwt-vc-config.ts +2 -0
- package/src/sd-jwt-vc-instance.ts +273 -69
- package/src/sd-jwt-vc-type-metadata-format.ts +106 -54
- package/src/test/vct.spec.ts +491 -19
- package/src/verification-result.ts +3 -2
|
@@ -6,15 +6,21 @@ import {
|
|
|
6
6
|
type StatusListJWTPayload,
|
|
7
7
|
} from '@sd-jwt/jwt-status-list';
|
|
8
8
|
import type { DisclosureFrame, Hasher, Verifier } from '@sd-jwt/types';
|
|
9
|
-
import {
|
|
9
|
+
import { SDJWTException } from '@sd-jwt/utils';
|
|
10
|
+
import z from 'zod';
|
|
10
11
|
import type {
|
|
11
12
|
SDJWTVCConfig,
|
|
12
13
|
StatusListFetcher,
|
|
13
14
|
StatusValidator,
|
|
14
15
|
} from './sd-jwt-vc-config';
|
|
15
16
|
import type { SdJwtVcPayload } from './sd-jwt-vc-payload';
|
|
16
|
-
import
|
|
17
|
-
|
|
17
|
+
import {
|
|
18
|
+
type Claim,
|
|
19
|
+
type ClaimPath,
|
|
20
|
+
type ResolvedTypeMetadata,
|
|
21
|
+
type TypeMetadataFormat,
|
|
22
|
+
TypeMetadataFormatSchema,
|
|
23
|
+
} from './sd-jwt-vc-type-metadata-format';
|
|
18
24
|
import type { VerificationResult } from './verification-result';
|
|
19
25
|
|
|
20
26
|
export class SDJwtVcInstance extends SDJwtInstance<SdJwtVcPayload> {
|
|
@@ -121,7 +127,8 @@ export class SDJwtVcInstance extends SDJwtInstance<SdJwtVcPayload> {
|
|
|
121
127
|
|
|
122
128
|
await this.verifyStatus(result, options);
|
|
123
129
|
if (this.userConfig.loadTypeMetadataFormat) {
|
|
124
|
-
await this.
|
|
130
|
+
const resolvedTypeMetadata = await this.fetchVct(result);
|
|
131
|
+
result.typeMetadata = resolvedTypeMetadata;
|
|
125
132
|
}
|
|
126
133
|
return result;
|
|
127
134
|
}
|
|
@@ -134,7 +141,9 @@ export class SDJwtVcInstance extends SDJwtInstance<SdJwtVcPayload> {
|
|
|
134
141
|
* @param encodedSDJwt
|
|
135
142
|
* @returns
|
|
136
143
|
*/
|
|
137
|
-
async getVct(
|
|
144
|
+
async getVct(
|
|
145
|
+
encodedSDJwt: string,
|
|
146
|
+
): Promise<ResolvedTypeMetadata | undefined> {
|
|
138
147
|
// Call the parent class's verify method
|
|
139
148
|
const { payload, header } = await SDJwt.extractJwt<
|
|
140
149
|
Record<string, unknown>,
|
|
@@ -164,24 +173,24 @@ export class SDJwtVcInstance extends SDJwtInstance<SdJwtVcPayload> {
|
|
|
164
173
|
url: string,
|
|
165
174
|
integrity?: string,
|
|
166
175
|
) {
|
|
167
|
-
if (integrity)
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
176
|
+
if (!integrity) return;
|
|
177
|
+
|
|
178
|
+
// validate the integrity of the response according to https://www.w3.org/TR/SRI/
|
|
179
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
180
|
+
const alg = integrity.split('-')[0];
|
|
181
|
+
//TODO: error handling when a hasher is passed that is not supporting the required algorithm according to the spec
|
|
182
|
+
const hashBuffer = await (this.userConfig.hasher as Hasher)(
|
|
183
|
+
arrayBuffer,
|
|
184
|
+
alg,
|
|
185
|
+
);
|
|
186
|
+
const integrityHash = integrity.split('-')[1];
|
|
187
|
+
const hash = Array.from(new Uint8Array(hashBuffer))
|
|
188
|
+
.map((byte) => byte.toString(16).padStart(2, '0'))
|
|
189
|
+
.join('');
|
|
190
|
+
if (hash !== integrityHash) {
|
|
191
|
+
throw new Error(
|
|
192
|
+
`Integrity check for ${url} failed: is ${hash}, but expected ${integrityHash}`,
|
|
175
193
|
);
|
|
176
|
-
const integrityHash = integrity.split('-')[1];
|
|
177
|
-
const hash = Array.from(new Uint8Array(hashBuffer))
|
|
178
|
-
.map((byte) => byte.toString(16).padStart(2, '0'))
|
|
179
|
-
.join('');
|
|
180
|
-
if (hash !== integrityHash) {
|
|
181
|
-
throw new Error(
|
|
182
|
-
`Integrity check for ${url} failed: is ${hash}, but expected ${integrityHash}`,
|
|
183
|
-
);
|
|
184
|
-
}
|
|
185
194
|
}
|
|
186
195
|
}
|
|
187
196
|
|
|
@@ -190,7 +199,10 @@ export class SDJwtVcInstance extends SDJwtInstance<SdJwtVcPayload> {
|
|
|
190
199
|
* @param url
|
|
191
200
|
* @returns
|
|
192
201
|
*/
|
|
193
|
-
private async
|
|
202
|
+
private async fetchWithIntegrity(
|
|
203
|
+
url: string,
|
|
204
|
+
integrity?: string,
|
|
205
|
+
): Promise<unknown> {
|
|
194
206
|
try {
|
|
195
207
|
const response = await fetch(url, {
|
|
196
208
|
signal: AbortSignal.timeout(this.userConfig.timeout ?? 10000),
|
|
@@ -202,7 +214,9 @@ export class SDJwtVcInstance extends SDJwtInstance<SdJwtVcPayload> {
|
|
|
202
214
|
);
|
|
203
215
|
}
|
|
204
216
|
await this.validateIntegrity(response.clone(), url, integrity);
|
|
205
|
-
|
|
217
|
+
const data = await response.json();
|
|
218
|
+
|
|
219
|
+
return data;
|
|
206
220
|
} catch (error) {
|
|
207
221
|
if ((error as Error).name === 'TimeoutError') {
|
|
208
222
|
throw new Error(`Request to ${url} timed out`);
|
|
@@ -213,76 +227,266 @@ export class SDJwtVcInstance extends SDJwtInstance<SdJwtVcPayload> {
|
|
|
213
227
|
|
|
214
228
|
/**
|
|
215
229
|
* Verifies the VCT of the SD-JWT-VC. Returns the type metadata format.
|
|
230
|
+
* Resolves the full extends chain according to spec sections 6.4, 8.2, and 9.5.
|
|
216
231
|
* @param result
|
|
217
232
|
* @returns
|
|
218
233
|
*/
|
|
219
|
-
private async
|
|
234
|
+
private async fetchVct(
|
|
220
235
|
result: VerificationResult,
|
|
221
|
-
): Promise<
|
|
222
|
-
const typeMetadataFormat = await this.
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
236
|
+
): Promise<ResolvedTypeMetadata | undefined> {
|
|
237
|
+
const typeMetadataFormat = await this.fetchSingleVct(
|
|
238
|
+
result.payload.vct,
|
|
239
|
+
result.payload['vct#integrity'],
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
if (!typeMetadataFormat) return undefined;
|
|
243
|
+
|
|
244
|
+
// If there's no extends
|
|
245
|
+
if (!typeMetadataFormat.extends) {
|
|
246
|
+
return {
|
|
247
|
+
mergedTypeMetadata: typeMetadataFormat,
|
|
248
|
+
typeMetadataChain: [typeMetadataFormat],
|
|
249
|
+
vctValues: [typeMetadataFormat.vct],
|
|
250
|
+
};
|
|
227
251
|
}
|
|
228
252
|
|
|
229
|
-
|
|
253
|
+
// Resolve the full VCT chain if extends is present
|
|
254
|
+
return this.resolveVctExtendsChain(typeMetadataFormat);
|
|
230
255
|
}
|
|
231
256
|
|
|
232
257
|
/**
|
|
233
|
-
*
|
|
234
|
-
* @param
|
|
235
|
-
* @
|
|
258
|
+
* Checks if two claim paths are equal by comparing each element.
|
|
259
|
+
* @param path1 First claim path
|
|
260
|
+
* @param path2 Second claim path
|
|
261
|
+
* @returns True if paths are equal, false otherwise
|
|
236
262
|
*/
|
|
237
|
-
private
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
263
|
+
private claimPathsEqual(path1: ClaimPath, path2: ClaimPath): boolean {
|
|
264
|
+
if (path1.length !== path2.length) return false;
|
|
265
|
+
return path1.every((element, index) => element === path2[index]);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Validates that extending claim metadata respects the constraints from spec section 9.5.1.
|
|
270
|
+
* @param baseClaim The base claim metadata
|
|
271
|
+
* @param extendingClaim The extending claim metadata
|
|
272
|
+
* @throws SDJWTException if validation fails
|
|
273
|
+
*/
|
|
274
|
+
private validateClaimExtension(
|
|
275
|
+
baseClaim: Claim,
|
|
276
|
+
extendingClaim: Claim,
|
|
277
|
+
): void {
|
|
278
|
+
// Validate 'sd' property constraints (section 9.5.1)
|
|
279
|
+
if (baseClaim.sd && extendingClaim.sd) {
|
|
280
|
+
// Cannot change from 'always' or 'never' to a different value
|
|
281
|
+
if (
|
|
282
|
+
(baseClaim.sd === 'always' || baseClaim.sd === 'never') &&
|
|
283
|
+
baseClaim.sd !== extendingClaim.sd
|
|
284
|
+
) {
|
|
285
|
+
const pathStr = JSON.stringify(extendingClaim.path);
|
|
286
|
+
throw new SDJWTException(
|
|
287
|
+
`Cannot change 'sd' property from '${baseClaim.sd}' to '${extendingClaim.sd}' for claim at path ${pathStr}`,
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Merges two type metadata formats, with the extending metadata overriding the base metadata.
|
|
295
|
+
* According to spec section 9.5:
|
|
296
|
+
* - All claim metadata from the extended type are inherited
|
|
297
|
+
* - The child type can add new claims or properties
|
|
298
|
+
* - If the child type defines claim metadata with the same path as the extended type,
|
|
299
|
+
* the child type's object will override the corresponding object from the extended type
|
|
300
|
+
* According to spec section 9.5.1:
|
|
301
|
+
* - sd property can only be changed from 'allowed' (or omitted) to 'always' or 'never'
|
|
302
|
+
* - sd property cannot be changed from 'always' or 'never' to a different value
|
|
303
|
+
* According to spec section 8.2:
|
|
304
|
+
* - If the extending type defines its own display property, the original display metadata is ignored
|
|
305
|
+
* Note: The spec also mentions 'mandatory' property constraints, but this is not currently
|
|
306
|
+
* defined in the Claim type and will be validated when that property is added to the type.
|
|
307
|
+
* @param base The base type metadata format
|
|
308
|
+
* @param extending The extending type metadata format
|
|
309
|
+
* @returns The merged type metadata format
|
|
310
|
+
*/
|
|
311
|
+
private mergeTypeMetadata(
|
|
312
|
+
base: TypeMetadataFormat,
|
|
313
|
+
extending: TypeMetadataFormat,
|
|
314
|
+
): TypeMetadataFormat {
|
|
315
|
+
// Start with a shallow copy of the extending metadata
|
|
316
|
+
// All properties that don't have explicit processing logic for merging
|
|
317
|
+
// will only be shallow copied, and the extending metadata will take precedence.
|
|
318
|
+
const merged: TypeMetadataFormat = { ...extending };
|
|
319
|
+
|
|
320
|
+
// Merge claims arrays if both exist
|
|
321
|
+
if (base.claims || extending.claims) {
|
|
322
|
+
const baseClaims = base.claims ?? [];
|
|
323
|
+
const extendingClaims = extending.claims ?? [];
|
|
324
|
+
|
|
325
|
+
// Validate extending claims that override base claims
|
|
326
|
+
for (const extendingClaim of extendingClaims) {
|
|
327
|
+
const matchingBaseClaim = baseClaims.find((baseClaim) =>
|
|
328
|
+
this.claimPathsEqual(baseClaim.path, extendingClaim.path),
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
if (matchingBaseClaim) {
|
|
332
|
+
this.validateClaimExtension(matchingBaseClaim, extendingClaim);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Build final claims array preserving order
|
|
337
|
+
// Start with base claims, replacing any that are overridden
|
|
338
|
+
const mergedClaims: typeof baseClaims = [];
|
|
339
|
+
const extendedClaimsWithoutBase = [...extendingClaims];
|
|
340
|
+
|
|
341
|
+
// Add base claims, replacing with extending version if path matches
|
|
342
|
+
for (const baseClaim of baseClaims) {
|
|
343
|
+
const extendingClaimIndex = extendedClaimsWithoutBase.findIndex(
|
|
344
|
+
(extendingClaim) =>
|
|
345
|
+
this.claimPathsEqual(baseClaim.path, extendingClaim.path),
|
|
346
|
+
);
|
|
347
|
+
const extendingClaim =
|
|
348
|
+
extendingClaimIndex !== -1
|
|
349
|
+
? extendedClaimsWithoutBase[extendingClaimIndex]
|
|
350
|
+
: undefined;
|
|
351
|
+
|
|
352
|
+
// Remove item from the array
|
|
353
|
+
if (extendingClaim) {
|
|
354
|
+
extendedClaimsWithoutBase.splice(extendingClaimIndex, 1);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Prefer extending claim, otherwise use base claim
|
|
358
|
+
mergedClaims.push(extendingClaim ?? baseClaim);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Add all remaining claims at the end
|
|
362
|
+
mergedClaims.push(...extendedClaimsWithoutBase);
|
|
363
|
+
|
|
364
|
+
merged.claims = mergedClaims;
|
|
242
365
|
}
|
|
243
366
|
|
|
244
|
-
|
|
245
|
-
|
|
367
|
+
// Handle display metadata (section 8.2)
|
|
368
|
+
// If extending type doesn't define display, inherit from base
|
|
369
|
+
if (!extending.display && base.display) {
|
|
370
|
+
merged.display = base.display;
|
|
246
371
|
}
|
|
247
372
|
|
|
248
|
-
|
|
249
|
-
this.userConfig.vctFetcher ??
|
|
250
|
-
((uri, integrity) => this.fetch(uri, integrity));
|
|
251
|
-
return fetcher(result.payload.vct, result.payload['vct#integrity']);
|
|
373
|
+
return merged;
|
|
252
374
|
}
|
|
253
375
|
|
|
254
376
|
/**
|
|
255
|
-
*
|
|
256
|
-
*
|
|
257
|
-
* @param
|
|
377
|
+
* Resolves the full VCT chain by recursively fetching extended type metadata.
|
|
378
|
+
* Implements security considerations from spec section 10.3 for circular dependencies.
|
|
379
|
+
* @param vct The VCT URI to resolve
|
|
380
|
+
* @param integrity Optional integrity metadata for the VCT
|
|
381
|
+
* @param depth Current depth in the chain
|
|
382
|
+
* @param visitedVcts Set of already visited VCT URIs to detect circular dependencies
|
|
383
|
+
* @returns The fully resolved and merged type metadata format
|
|
258
384
|
*/
|
|
259
|
-
private async
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
385
|
+
private async resolveVctExtendsChain(
|
|
386
|
+
parentTypeMetadata: TypeMetadataFormat,
|
|
387
|
+
// We start at one, as the base is already fetched when this method is first called
|
|
388
|
+
depth: number = 1,
|
|
389
|
+
// By default include the parent vct, in case of the first call
|
|
390
|
+
visitedVcts: Set<string> = new Set(parentTypeMetadata.vct),
|
|
391
|
+
): Promise<ResolvedTypeMetadata> {
|
|
392
|
+
const maxDepth = this.userConfig.maxVctExtendsDepth ?? 5;
|
|
393
|
+
|
|
394
|
+
// Check max depth (security consideration from spec section 10.3)
|
|
395
|
+
if (maxDepth !== -1 && depth > maxDepth) {
|
|
396
|
+
throw new SDJWTException(
|
|
397
|
+
`Maximum VCT extends depth of ${maxDepth} exceeded`,
|
|
398
|
+
);
|
|
399
|
+
}
|
|
264
400
|
|
|
265
|
-
if (!
|
|
266
|
-
throw new
|
|
401
|
+
if (!parentTypeMetadata.extends) {
|
|
402
|
+
throw new SDJWTException(
|
|
403
|
+
`Type metadata for vct '${parentTypeMetadata.vct}' has no 'extends' field. Unable to resolve extended type metadata document.`,
|
|
404
|
+
);
|
|
267
405
|
}
|
|
268
406
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
407
|
+
// Check for circular dependencies (security consideration from spec section 10.3)
|
|
408
|
+
if (visitedVcts.has(parentTypeMetadata.extends)) {
|
|
409
|
+
throw new SDJWTException(
|
|
410
|
+
`Circular dependency detected in VCT extends chain: ${parentTypeMetadata.extends}`,
|
|
411
|
+
);
|
|
412
|
+
}
|
|
274
413
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
414
|
+
// Mark this VCT as visited
|
|
415
|
+
visitedVcts.add(parentTypeMetadata.extends);
|
|
416
|
+
|
|
417
|
+
const extendedTypeMetadata = await this.fetchSingleVct(
|
|
418
|
+
parentTypeMetadata.extends,
|
|
419
|
+
parentTypeMetadata['extends#integrity'],
|
|
420
|
+
);
|
|
421
|
+
|
|
422
|
+
// While top-level vct MAY return null (meaning there's no vct type metadata)
|
|
423
|
+
// The extends value ALWAYS must resolve to a value. A custom user provided resolver
|
|
424
|
+
// can return a minimal on-demand type metadata document if it wants to support this use case
|
|
425
|
+
if (!extendedTypeMetadata) {
|
|
426
|
+
throw new SDJWTException(
|
|
427
|
+
`Resolving VCT extends value '${parentTypeMetadata.extends}' resulted in an undefined result.`,
|
|
428
|
+
);
|
|
429
|
+
}
|
|
280
430
|
|
|
281
|
-
|
|
282
|
-
|
|
431
|
+
let resolvedTypeMetadata: ResolvedTypeMetadata;
|
|
432
|
+
|
|
433
|
+
// If this type extends another, recursively resolve the chain
|
|
434
|
+
// We MUST first process the lower level document before processing
|
|
435
|
+
// the higher level document
|
|
436
|
+
if (extendedTypeMetadata.extends) {
|
|
437
|
+
resolvedTypeMetadata = await this.resolveVctExtendsChain(
|
|
438
|
+
extendedTypeMetadata,
|
|
439
|
+
depth + 1,
|
|
440
|
+
visitedVcts,
|
|
441
|
+
);
|
|
442
|
+
} else {
|
|
443
|
+
resolvedTypeMetadata = {
|
|
444
|
+
mergedTypeMetadata: extendedTypeMetadata,
|
|
445
|
+
typeMetadataChain: [extendedTypeMetadata],
|
|
446
|
+
vctValues: [extendedTypeMetadata.vct],
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const mergedTypeMetadata = this.mergeTypeMetadata(
|
|
451
|
+
resolvedTypeMetadata.mergedTypeMetadata,
|
|
452
|
+
parentTypeMetadata,
|
|
453
|
+
);
|
|
454
|
+
|
|
455
|
+
return {
|
|
456
|
+
mergedTypeMetadata: mergedTypeMetadata,
|
|
457
|
+
typeMetadataChain: [
|
|
458
|
+
parentTypeMetadata,
|
|
459
|
+
...resolvedTypeMetadata.typeMetadataChain,
|
|
460
|
+
],
|
|
461
|
+
vctValues: [parentTypeMetadata.vct, ...resolvedTypeMetadata.vctValues],
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Fetches and verifies the VCT Metadata for a VCT value.
|
|
467
|
+
* @param result
|
|
468
|
+
* @returns
|
|
469
|
+
*/
|
|
470
|
+
private async fetchSingleVct(
|
|
471
|
+
vct: string,
|
|
472
|
+
integrity?: string,
|
|
473
|
+
): Promise<TypeMetadataFormat | undefined> {
|
|
474
|
+
const fetcher =
|
|
475
|
+
this.userConfig.vctFetcher ??
|
|
476
|
+
((uri, integrity) => this.fetchWithIntegrity(uri, integrity));
|
|
477
|
+
|
|
478
|
+
// Data may be undefined
|
|
479
|
+
const data = await fetcher(vct, integrity);
|
|
480
|
+
if (!data) return undefined;
|
|
481
|
+
|
|
482
|
+
const validated = TypeMetadataFormatSchema.safeParse(data);
|
|
483
|
+
if (!validated.success) {
|
|
484
|
+
throw new SDJWTException(
|
|
485
|
+
`Invalid VCT type metadata for vct '${vct}':\n${z.prettifyError(validated.error)}`,
|
|
486
|
+
);
|
|
283
487
|
}
|
|
284
488
|
|
|
285
|
-
return
|
|
489
|
+
return validated.data;
|
|
286
490
|
}
|
|
287
491
|
|
|
288
492
|
/**
|
|
@@ -1,144 +1,196 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Logo metadata used in rendering a credential.
|
|
3
5
|
*/
|
|
4
|
-
export
|
|
6
|
+
export const LogoSchema = z.object({
|
|
5
7
|
/** REQUIRED. A URI pointing to the logo image. */
|
|
6
|
-
uri: string
|
|
8
|
+
uri: z.string(),
|
|
7
9
|
/** OPTIONAL. An "integrity metadata" string as described in Section 7. */
|
|
8
|
-
'uri#integrity'
|
|
10
|
+
'uri#integrity': z.string().optional(),
|
|
9
11
|
/** OPTIONAL. A string containing alternative text for the logo image. */
|
|
10
|
-
alt_text
|
|
11
|
-
};
|
|
12
|
+
alt_text: z.string().optional(),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export type Logo = z.infer<typeof LogoSchema>;
|
|
12
16
|
|
|
13
17
|
/**
|
|
14
18
|
* The simple rendering method is intended for applications that do not support SVG.
|
|
15
19
|
*/
|
|
16
|
-
export
|
|
20
|
+
export const SimpleRenderingSchema = z.object({
|
|
17
21
|
/** OPTIONAL. Logo metadata to display for the credential. */
|
|
18
|
-
logo
|
|
22
|
+
logo: LogoSchema.optional(),
|
|
19
23
|
/** OPTIONAL. RGB color value for the credential background (e.g., "#FFFFFF"). */
|
|
20
|
-
background_color
|
|
24
|
+
background_color: z.string().optional(),
|
|
21
25
|
/** OPTIONAL. RGB color value for the credential text (e.g., "#000000"). */
|
|
22
|
-
text_color
|
|
23
|
-
};
|
|
26
|
+
text_color: z.string().optional(),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export type SimpleRendering = z.infer<typeof SimpleRenderingSchema>;
|
|
24
30
|
|
|
25
31
|
/** Enum of valid values for rendering orientation. */
|
|
26
|
-
|
|
32
|
+
export const OrientationSchema = z.enum(['portrait', 'landscape']);
|
|
27
33
|
|
|
28
34
|
/** Enum of valid values for rendering color schemes. */
|
|
29
|
-
|
|
35
|
+
export const ColorSchemeSchema = z.enum(['light', 'dark']);
|
|
30
36
|
|
|
31
37
|
/** Enum of valid values for rendering contrast. */
|
|
32
|
-
|
|
38
|
+
export const ContrastSchema = z.enum(['normal', 'high']);
|
|
33
39
|
|
|
34
40
|
/**
|
|
35
41
|
* Properties that describe the display preferences for an SVG template rendering.
|
|
36
42
|
*/
|
|
37
|
-
export
|
|
43
|
+
export const SvgTemplatePropertiesSchema = z.object({
|
|
38
44
|
/** OPTIONAL. Orientation optimized for the template. */
|
|
39
|
-
orientation
|
|
45
|
+
orientation: OrientationSchema.optional(),
|
|
40
46
|
/** OPTIONAL. Color scheme optimized for the template. */
|
|
41
|
-
color_scheme
|
|
47
|
+
color_scheme: ColorSchemeSchema.optional(),
|
|
42
48
|
/** OPTIONAL. Contrast level optimized for the template. */
|
|
43
|
-
contrast
|
|
44
|
-
};
|
|
49
|
+
contrast: ContrastSchema.optional(),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
export type SvgTemplateProperties = z.infer<typeof SvgTemplatePropertiesSchema>;
|
|
45
53
|
|
|
46
54
|
/**
|
|
47
55
|
* SVG rendering metadata containing URI and optional integrity and properties.
|
|
48
56
|
*/
|
|
49
|
-
export
|
|
57
|
+
export const SvgTemplateRenderingSchema = z.object({
|
|
50
58
|
/** REQUIRED. A URI pointing to the SVG template. */
|
|
51
|
-
uri: string
|
|
59
|
+
uri: z.string(),
|
|
52
60
|
/** OPTIONAL. An "integrity metadata" string as described in Section 7. */
|
|
53
|
-
'uri#integrity'
|
|
61
|
+
'uri#integrity': z.string().optional(),
|
|
54
62
|
/** REQUIRED if more than one SVG template is present. */
|
|
55
|
-
properties
|
|
56
|
-
};
|
|
63
|
+
properties: SvgTemplatePropertiesSchema.optional(),
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
export type SvgTemplateRendering = z.infer<typeof SvgTemplateRenderingSchema>;
|
|
57
67
|
|
|
58
68
|
/**
|
|
59
69
|
* Rendering metadata, either simple or SVG-based, for a credential.
|
|
60
70
|
*/
|
|
61
|
-
export
|
|
71
|
+
export const RenderingSchema = z.object({
|
|
62
72
|
/** OPTIONAL. Simple rendering metadata. */
|
|
63
|
-
simple
|
|
73
|
+
simple: SimpleRenderingSchema.optional(),
|
|
64
74
|
/** OPTIONAL. Array of SVG template rendering objects. */
|
|
65
|
-
svg_template
|
|
66
|
-
};
|
|
75
|
+
svg_template: z.array(SvgTemplateRenderingSchema).optional(),
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
export type Rendering = z.infer<typeof RenderingSchema>;
|
|
67
79
|
|
|
68
80
|
/**
|
|
69
81
|
* Display metadata associated with a credential type.
|
|
70
82
|
*/
|
|
71
|
-
export
|
|
83
|
+
export const DisplaySchema = z.object({
|
|
72
84
|
/** REQUIRED. Language tag according to RFC 5646 (e.g., "en", "de"). */
|
|
73
|
-
lang: string
|
|
85
|
+
lang: z.string(),
|
|
74
86
|
/** REQUIRED. Human-readable name for the credential type. */
|
|
75
|
-
name: string
|
|
87
|
+
name: z.string(),
|
|
76
88
|
/** OPTIONAL. Description of the credential type for end users. */
|
|
77
|
-
description
|
|
89
|
+
description: z.string().optional(),
|
|
78
90
|
/** OPTIONAL. Rendering information (simple or SVG) for the credential. */
|
|
79
|
-
rendering
|
|
80
|
-
};
|
|
91
|
+
rendering: RenderingSchema.optional(),
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
export type Display = z.infer<typeof DisplaySchema>;
|
|
81
95
|
|
|
82
96
|
/**
|
|
83
97
|
* Claim path within the credential's JSON structure.
|
|
84
98
|
* Example: ["address", "street_address"]
|
|
85
99
|
*/
|
|
86
|
-
export
|
|
100
|
+
export const ClaimPathSchema = z.array(z.string().nullable());
|
|
101
|
+
|
|
102
|
+
export type ClaimPath = z.infer<typeof ClaimPathSchema>;
|
|
87
103
|
|
|
88
104
|
/**
|
|
89
105
|
* Display metadata for a specific claim.
|
|
90
106
|
*/
|
|
91
|
-
export
|
|
107
|
+
export const ClaimDisplaySchema = z.object({
|
|
92
108
|
/** REQUIRED. Language tag according to RFC 5646. */
|
|
93
|
-
lang: string
|
|
109
|
+
lang: z.string(),
|
|
94
110
|
/** REQUIRED. Human-readable label for the claim. */
|
|
95
|
-
label: string
|
|
111
|
+
label: z.string(),
|
|
96
112
|
/** OPTIONAL. Description of the claim for end users. */
|
|
97
|
-
description
|
|
98
|
-
};
|
|
113
|
+
description: z.string().optional(),
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
export type ClaimDisplay = z.infer<typeof ClaimDisplaySchema>;
|
|
99
117
|
|
|
100
118
|
/**
|
|
101
119
|
* Indicates whether a claim is selectively disclosable.
|
|
102
120
|
*/
|
|
103
|
-
export
|
|
121
|
+
export const ClaimSelectiveDisclosureSchema = z.enum([
|
|
122
|
+
'always',
|
|
123
|
+
'allowed',
|
|
124
|
+
'never',
|
|
125
|
+
]);
|
|
126
|
+
|
|
127
|
+
export type ClaimSelectiveDisclosure = z.infer<
|
|
128
|
+
typeof ClaimSelectiveDisclosureSchema
|
|
129
|
+
>;
|
|
104
130
|
|
|
105
131
|
/**
|
|
106
132
|
* Metadata for individual claims in the credential type.
|
|
107
133
|
*/
|
|
108
|
-
export
|
|
134
|
+
export const ClaimSchema = z.object({
|
|
109
135
|
/**
|
|
110
136
|
* REQUIRED. Array of one or more paths to the claim in the credential subject.
|
|
111
137
|
* Each path is an array of strings (or null for array elements).
|
|
112
138
|
*/
|
|
113
|
-
path:
|
|
139
|
+
path: ClaimPathSchema,
|
|
114
140
|
/** OPTIONAL. Display metadata in multiple languages. */
|
|
115
|
-
display
|
|
141
|
+
display: z.array(ClaimDisplaySchema).optional(),
|
|
116
142
|
/** OPTIONAL. Controls whether the claim must, may, or must not be selectively disclosed. */
|
|
117
|
-
sd
|
|
143
|
+
sd: ClaimSelectiveDisclosureSchema.optional(),
|
|
118
144
|
/**
|
|
119
145
|
* OPTIONAL. Unique string identifier for referencing the claim in an SVG template.
|
|
120
146
|
* Must consist of alphanumeric characters or underscores and must not start with a digit.
|
|
121
147
|
*/
|
|
122
|
-
svg_id
|
|
123
|
-
};
|
|
148
|
+
svg_id: z.string().optional(),
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
export type Claim = z.infer<typeof ClaimSchema>;
|
|
124
152
|
|
|
125
153
|
/**
|
|
126
154
|
* Type metadata for a specific Verifiable Credential (VC) type.
|
|
127
155
|
* Reference: https://www.ietf.org/archive/id/draft-ietf-oauth-sd-jwt-vc-09.html#name-type-metadata-format
|
|
128
156
|
*/
|
|
129
|
-
export
|
|
157
|
+
export const TypeMetadataFormatSchema = z.object({
|
|
130
158
|
/** REQUIRED. A URI uniquely identifying the credential type. */
|
|
131
|
-
vct: string
|
|
159
|
+
vct: z.string(),
|
|
132
160
|
/** OPTIONAL. Human-readable name for developers. */
|
|
133
|
-
name
|
|
161
|
+
name: z.string().optional(),
|
|
134
162
|
/** OPTIONAL. Human-readable description for developers. */
|
|
135
|
-
description
|
|
163
|
+
description: z.string().optional(),
|
|
136
164
|
/** OPTIONAL. URI of another type that this one extends. */
|
|
137
|
-
extends
|
|
165
|
+
extends: z.string().optional(),
|
|
138
166
|
/** OPTIONAL. Integrity metadata for the 'extends' field. */
|
|
139
|
-
'extends#integrity'
|
|
167
|
+
'extends#integrity': z.string().optional(),
|
|
140
168
|
/** OPTIONAL. Array of localized display metadata for the type. */
|
|
141
|
-
display
|
|
169
|
+
display: z.array(DisplaySchema).optional(),
|
|
142
170
|
/** OPTIONAL. Array of claim metadata. */
|
|
143
|
-
claims
|
|
171
|
+
claims: z.array(ClaimSchema).optional(),
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* The resolved type metadata. If you just want to use the type metadata, you should use `typeMetadata`.
|
|
176
|
+
* In case additional processing is needed (e.g. for extensions in type metadata), you can use the `typeMetadataChain`
|
|
177
|
+
*/
|
|
178
|
+
export type ResolvedTypeMetadata = {
|
|
179
|
+
/**
|
|
180
|
+
* The merged type metadata based on the resolved `vct` document and all `extends` values.
|
|
181
|
+
*/
|
|
182
|
+
mergedTypeMetadata: TypeMetadataFormat;
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* The original type metadata documents, ordered from the extending type to the last extended type.
|
|
186
|
+
*/
|
|
187
|
+
typeMetadataChain: [TypeMetadataFormat, ...TypeMetadataFormat[]];
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* The vct values present in the type metadata chain. This can be used for matching against e.g.
|
|
191
|
+
* DCQL queries which can query an underlying type.
|
|
192
|
+
*/
|
|
193
|
+
vctValues: [string, ...string[]];
|
|
144
194
|
};
|
|
195
|
+
|
|
196
|
+
export type TypeMetadataFormat = z.infer<typeof TypeMetadataFormatSchema>;
|