gdc-common-utils-ts 2.3.3 → 2.3.4
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/constants/verifiable-credentials.d.ts +8 -0
- package/dist/constants/verifiable-credentials.js +8 -0
- package/dist/examples/index.d.ts +1 -0
- package/dist/examples/index.js +1 -0
- package/dist/examples/shared.d.ts +2 -0
- package/dist/examples/shared.js +2 -0
- package/dist/examples/subject-identity-binding.d.ts +9 -0
- package/dist/examples/subject-identity-binding.js +20 -0
- package/dist/models/index.d.ts +1 -0
- package/dist/models/index.js +1 -0
- package/dist/models/subject-identity-binding.d.ts +22 -0
- package/dist/models/subject-identity-binding.js +7 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/legal-organization-onboarding-editor.d.ts +13 -0
- package/dist/utils/legal-organization-onboarding-editor.js +14 -3
- package/dist/utils/subject-identity-binding.d.ts +28 -0
- package/dist/utils/subject-identity-binding.js +136 -0
- package/package.json +1 -1
|
@@ -44,6 +44,14 @@ export declare const IndividualCredentialTypes: Readonly<{
|
|
|
44
44
|
IndividualControllerCredential: "IndividualControllerCredential";
|
|
45
45
|
IndividualMemberCredential: "IndividualMemberCredential";
|
|
46
46
|
IndividualSubjectCredential: "IndividualSubjectCredential";
|
|
47
|
+
/**
|
|
48
|
+
* Credential issued by a trusted portal/identity authority to bind two or
|
|
49
|
+
* more DIDs that identify the same individual.
|
|
50
|
+
*
|
|
51
|
+
* A physical card/support DID is not an individual alias: clients resolve
|
|
52
|
+
* the support document's `subject` first and bind that subject DID instead.
|
|
53
|
+
*/
|
|
54
|
+
SubjectIdentityBindingCredential: "SubjectIdentityBindingCredential";
|
|
47
55
|
}>;
|
|
48
56
|
/**
|
|
49
57
|
* Canonical credential subtype names used by inter-tenant authorization
|
|
@@ -46,6 +46,14 @@ export const IndividualCredentialTypes = Object.freeze({
|
|
|
46
46
|
IndividualControllerCredential: 'IndividualControllerCredential',
|
|
47
47
|
IndividualMemberCredential: 'IndividualMemberCredential',
|
|
48
48
|
IndividualSubjectCredential: 'IndividualSubjectCredential',
|
|
49
|
+
/**
|
|
50
|
+
* Credential issued by a trusted portal/identity authority to bind two or
|
|
51
|
+
* more DIDs that identify the same individual.
|
|
52
|
+
*
|
|
53
|
+
* A physical card/support DID is not an individual alias: clients resolve
|
|
54
|
+
* the support document's `subject` first and bind that subject DID instead.
|
|
55
|
+
*/
|
|
56
|
+
SubjectIdentityBindingCredential: 'SubjectIdentityBindingCredential',
|
|
49
57
|
});
|
|
50
58
|
/**
|
|
51
59
|
* Canonical credential subtype names used by inter-tenant authorization
|
package/dist/examples/index.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export * from './employee';
|
|
|
11
11
|
export * from './license';
|
|
12
12
|
export * from './invoice';
|
|
13
13
|
export * from './inter-tenant-access-contract';
|
|
14
|
+
export * from './subject-identity-binding';
|
|
14
15
|
export * from './related-person';
|
|
15
16
|
export * from './consent-access';
|
|
16
17
|
export * from './relationship-access';
|
package/dist/examples/index.js
CHANGED
|
@@ -11,6 +11,7 @@ export * from './employee.js';
|
|
|
11
11
|
export * from './license.js';
|
|
12
12
|
export * from './invoice.js';
|
|
13
13
|
export * from './inter-tenant-access-contract.js';
|
|
14
|
+
export * from './subject-identity-binding.js';
|
|
14
15
|
export * from './related-person.js';
|
|
15
16
|
export * from './consent-access.js';
|
|
16
17
|
export * from './relationship-access.js';
|
|
@@ -117,6 +117,8 @@ export declare const EXAMPLE_GATEWAY_PUBLIC_ORIGIN: "https://gateway.example.com
|
|
|
117
117
|
export declare const EXAMPLE_HOST_PUBLIC_HOSTNAME: "host.example.com";
|
|
118
118
|
export declare const EXAMPLE_PROVIDER_TAX_ID: "VATES-B00112233";
|
|
119
119
|
export declare const EXAMPLE_PROVIDER_DOMAIN: "health-care.provider.example.org";
|
|
120
|
+
/** Synthetic public portal domain used to derive organization path DIDs. */
|
|
121
|
+
export declare const EXAMPLE_PUBLIC_PORTAL_DOMAIN: "portal.example.org";
|
|
120
122
|
export declare const EXAMPLE_INDIVIDUAL_MULTIBASE_ID: string;
|
|
121
123
|
export declare const EXAMPLE_INDIVIDUAL_MULTIBASE_ID_SECONDARY: string;
|
|
122
124
|
export declare const EXAMPLE_INDIVIDUAL_MULTIBASE_ID_TERTIARY: string;
|
package/dist/examples/shared.js
CHANGED
|
@@ -136,6 +136,8 @@ export const EXAMPLE_GATEWAY_PUBLIC_ORIGIN = 'https://gateway.example.com';
|
|
|
136
136
|
export const EXAMPLE_HOST_PUBLIC_HOSTNAME = 'host.example.com';
|
|
137
137
|
export const EXAMPLE_PROVIDER_TAX_ID = 'VATES-B00112233';
|
|
138
138
|
export const EXAMPLE_PROVIDER_DOMAIN = 'health-care.provider.example.org';
|
|
139
|
+
/** Synthetic public portal domain used to derive organization path DIDs. */
|
|
140
|
+
export const EXAMPLE_PUBLIC_PORTAL_DOMAIN = 'portal.example.org';
|
|
139
141
|
export const EXAMPLE_INDIVIDUAL_MULTIBASE_ID = encodeHexToMultibase58btc('a87e5b15aea444759c7c40aa88354b6f');
|
|
140
142
|
export const EXAMPLE_INDIVIDUAL_MULTIBASE_ID_SECONDARY = encodeHexToMultibase58btc('b98f6c24bfb545849d8d51bb99465c7e');
|
|
141
143
|
export const EXAMPLE_INDIVIDUAL_MULTIBASE_ID_TERTIARY = encodeHexToMultibase58btc('c39f7d35c0c65695ae9e62cca0576d8f');
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Synthetic trusted portal DID used by binding examples and tests. */
|
|
2
|
+
export declare const EXAMPLE_TRUSTED_HEALTH_PORTAL_DID: "did:web:portal.example.org";
|
|
3
|
+
/** Synthetic individual DID exposed by one health portal. */
|
|
4
|
+
export declare const EXAMPLE_PORTAL_INDIVIDUAL_DID: "did:web:portal.example.org:health-care:individual:multibase:zSubjectExample";
|
|
5
|
+
/** Synthetic individual DID exposed by a second, independent portal. */
|
|
6
|
+
export declare const EXAMPLE_ALTERNATE_PORTAL_INDIVIDUAL_DID: "did:web:cards.example.org:individual:multibase:zSubjectExample";
|
|
7
|
+
/** Physical support/card DID used only to demonstrate that it is not an auth alias. */
|
|
8
|
+
export declare const EXAMPLE_PHYSICAL_SUPPORT_DID: "did:web:cards.example.org:card:personal:000-000-000-001";
|
|
9
|
+
export declare const EXAMPLE_SUBJECT_IDENTITY_BINDING_CREDENTIAL: Readonly<import("..").VerifiableCredentialV2>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
|
|
2
|
+
import { DataspaceSectors } from '../constants/sectors.js';
|
|
3
|
+
import { buildSubjectIdentityBindingCredential } from '../utils/subject-identity-binding.js';
|
|
4
|
+
/** Synthetic trusted portal DID used by binding examples and tests. */
|
|
5
|
+
export const EXAMPLE_TRUSTED_HEALTH_PORTAL_DID = 'did:web:portal.example.org';
|
|
6
|
+
/** Synthetic individual DID exposed by one health portal. */
|
|
7
|
+
export const EXAMPLE_PORTAL_INDIVIDUAL_DID = 'did:web:portal.example.org:health-care:individual:multibase:zSubjectExample';
|
|
8
|
+
/** Synthetic individual DID exposed by a second, independent portal. */
|
|
9
|
+
export const EXAMPLE_ALTERNATE_PORTAL_INDIVIDUAL_DID = 'did:web:cards.example.org:individual:multibase:zSubjectExample';
|
|
10
|
+
/** Physical support/card DID used only to demonstrate that it is not an auth alias. */
|
|
11
|
+
export const EXAMPLE_PHYSICAL_SUPPORT_DID = 'did:web:cards.example.org:card:personal:000-000-000-001';
|
|
12
|
+
export const EXAMPLE_SUBJECT_IDENTITY_BINDING_CREDENTIAL = Object.freeze(buildSubjectIdentityBindingCredential({
|
|
13
|
+
id: 'urn:uuid:subject-identity-binding-001',
|
|
14
|
+
issuerDid: EXAMPLE_TRUSTED_HEALTH_PORTAL_DID,
|
|
15
|
+
subjectDid: EXAMPLE_PORTAL_INDIVIDUAL_DID,
|
|
16
|
+
aliasDids: [EXAMPLE_ALTERNATE_PORTAL_INDIVIDUAL_DID],
|
|
17
|
+
sectors: [DataspaceSectors.HealthCare],
|
|
18
|
+
validFrom: '2026-01-01T00:00:00.000Z',
|
|
19
|
+
validUntil: '2027-01-01T00:00:00.000Z',
|
|
20
|
+
}));
|
package/dist/models/index.d.ts
CHANGED
|
@@ -48,6 +48,7 @@ export * from './resource-document';
|
|
|
48
48
|
export * from './relationship-access';
|
|
49
49
|
export * from './response';
|
|
50
50
|
export * from './subject-identifier-ledger';
|
|
51
|
+
export * from './subject-identity-binding';
|
|
51
52
|
export * from './urlPath';
|
|
52
53
|
export * from './verifiable-credential';
|
|
53
54
|
export * from './wallet';
|
package/dist/models/index.js
CHANGED
|
@@ -48,6 +48,7 @@ export * from './resource-document.js';
|
|
|
48
48
|
export * from './relationship-access.js';
|
|
49
49
|
export * from './response.js';
|
|
50
50
|
export * from './subject-identifier-ledger.js';
|
|
51
|
+
export * from './subject-identity-binding.js';
|
|
51
52
|
export * from './urlPath.js';
|
|
52
53
|
export * from './verifiable-credential.js';
|
|
53
54
|
export * from './wallet.js';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Canonical JSON members of a subject identity binding credential. */
|
|
2
|
+
export declare const SubjectIdentityBindingClaims: Readonly<{
|
|
3
|
+
SubjectId: "id";
|
|
4
|
+
SameAs: "sameAs";
|
|
5
|
+
Sector: "sector";
|
|
6
|
+
}>;
|
|
7
|
+
/** Normalized, security-relevant projection of a subject identity binding VC. */
|
|
8
|
+
export type SubjectIdentityBindingSummary = Readonly<{
|
|
9
|
+
issuerDid: string;
|
|
10
|
+
subjectDid: string;
|
|
11
|
+
aliasDids: readonly string[];
|
|
12
|
+
sectors: readonly string[];
|
|
13
|
+
validFrom?: string;
|
|
14
|
+
validUntil?: string;
|
|
15
|
+
}>;
|
|
16
|
+
/** Criteria used by a verifier after the enclosing VP has been verified. */
|
|
17
|
+
export type SubjectIdentityBindingMatchCriteria = Readonly<{
|
|
18
|
+
trustedIssuerDids: readonly string[];
|
|
19
|
+
requiredSubjectDids: readonly string[];
|
|
20
|
+
sector?: string;
|
|
21
|
+
now?: string | Date;
|
|
22
|
+
}>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
|
|
2
|
+
/** Canonical JSON members of a subject identity binding credential. */
|
|
3
|
+
export const SubjectIdentityBindingClaims = Object.freeze({
|
|
4
|
+
SubjectId: 'id',
|
|
5
|
+
SameAs: 'sameAs',
|
|
6
|
+
Sector: 'sector',
|
|
7
|
+
});
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -99,6 +99,7 @@ export * from './permission-templates';
|
|
|
99
99
|
export * from './smart-scope';
|
|
100
100
|
export * from './service-act-reasons';
|
|
101
101
|
export * from './same-as';
|
|
102
|
+
export * from './subject-identity-binding';
|
|
102
103
|
export * from './activation-request';
|
|
103
104
|
export * from './vp-token';
|
|
104
105
|
export * from './vital-sign-day-batch';
|
package/dist/utils/index.js
CHANGED
|
@@ -99,6 +99,7 @@ export * from './permission-templates.js';
|
|
|
99
99
|
export * from './smart-scope.js';
|
|
100
100
|
export * from './service-act-reasons.js';
|
|
101
101
|
export * from './same-as.js';
|
|
102
|
+
export * from './subject-identity-binding.js';
|
|
102
103
|
export * from './activation-request.js';
|
|
103
104
|
export * from './vp-token.js';
|
|
104
105
|
export * from './vital-sign-day-batch.js';
|
|
@@ -33,6 +33,19 @@ export type LegalOrganizationOnboardingDraftResult = Readonly<{
|
|
|
33
33
|
export type LegalOrganizationGatewayVerificationSignatureFlow = 'certificate' | 'otp';
|
|
34
34
|
export type LegalOrganizationGatewayVerificationRequestInput = Readonly<{
|
|
35
35
|
controller: LegalOrganizationVerificationTransactionController;
|
|
36
|
+
/**
|
|
37
|
+
* Explicit public organization DID controlled by the registering portal.
|
|
38
|
+
* This is independent from the GW/service DID and from its network address.
|
|
39
|
+
*/
|
|
40
|
+
publicOrganizationDid?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Public portal domain used to derive the canonical path DID when
|
|
43
|
+
* `publicOrganizationDid` is omitted.
|
|
44
|
+
*
|
|
45
|
+
* Result:
|
|
46
|
+
* `did:web:<domain>:<sector>:organization:taxid:<tax-id>`
|
|
47
|
+
*/
|
|
48
|
+
publicOrganizationDomain?: string;
|
|
36
49
|
signatureFlow?: LegalOrganizationGatewayVerificationSignatureFlow;
|
|
37
50
|
representativeSameAs?: string;
|
|
38
51
|
verificationResourceType?: string;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ClaimsOrganizationSchemaorg, ClaimsPersonSchemaorg, ClaimsServiceSchemaorg, } from '../constants/schemaorg.js';
|
|
2
2
|
import { validateLegalOrganizationOnboardingClaims, } from './legal-organization-onboarding.js';
|
|
3
|
+
import { buildHostedProviderDidWeb } from './did.js';
|
|
3
4
|
function normalizeText(value) {
|
|
4
5
|
return typeof value === 'string' ? value.trim() : '';
|
|
5
6
|
}
|
|
@@ -275,16 +276,26 @@ export function createLegalOrganizationOnboardingFacade() {
|
|
|
275
276
|
},
|
|
276
277
|
buildGatewayVerificationRequest(fields, input) {
|
|
277
278
|
const controllerEmail = facade.getControllerEmail(fields);
|
|
278
|
-
const serviceIdentifier = facade.getServiceIdentifier(fields);
|
|
279
279
|
const serviceUrl = facade.getServiceUrl(fields);
|
|
280
|
+
const publicOrganizationDid = normalizeOptionalText(input.publicOrganizationDid);
|
|
281
|
+
const publicOrganizationDomain = normalizeOptionalText(input.publicOrganizationDomain);
|
|
282
|
+
const organizationDid = publicOrganizationDid || (publicOrganizationDomain
|
|
283
|
+
&& facade.getServiceCategory(fields)
|
|
284
|
+
&& facade.getTaxId(fields)
|
|
285
|
+
? buildHostedProviderDidWeb({
|
|
286
|
+
hostDomain: publicOrganizationDomain,
|
|
287
|
+
sector: facade.getServiceCategory(fields),
|
|
288
|
+
providerTaxId: facade.getTaxId(fields),
|
|
289
|
+
})
|
|
290
|
+
: undefined);
|
|
280
291
|
const signatureFlow = normalizeText(input.signatureFlow || 'certificate').toLowerCase();
|
|
281
292
|
const representativeSameAs = normalizeOptionalText(input.representativeSameAs || controllerEmail);
|
|
282
293
|
const signedTermsPdfUrl = normalizeOptionalText(input.signedTermsPdfUrl);
|
|
283
294
|
return facade.buildVerificationTransactionInput(fields, {
|
|
284
295
|
controller: input.controller,
|
|
285
|
-
organization:
|
|
296
|
+
organization: organizationDid || serviceUrl
|
|
286
297
|
? {
|
|
287
|
-
...(
|
|
298
|
+
...(organizationDid ? { did: organizationDid } : {}),
|
|
288
299
|
...(serviceUrl ? { url: serviceUrl } : {}),
|
|
289
300
|
}
|
|
290
301
|
: undefined,
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type SubjectIdentityBindingMatchCriteria, type SubjectIdentityBindingSummary } from '../models/subject-identity-binding';
|
|
2
|
+
import type { VerifiableCredentialV2 } from '../models/verifiable-credential';
|
|
3
|
+
/**
|
|
4
|
+
* Builds the unsigned JSON form of a subject identity binding VC.
|
|
5
|
+
*
|
|
6
|
+
* This helper only builds the canonical payload. The issuer must sign it using
|
|
7
|
+
* the normal VC/VP signing flow before a verifier treats it as evidence.
|
|
8
|
+
*/
|
|
9
|
+
export declare function buildSubjectIdentityBindingCredential(input: Readonly<{
|
|
10
|
+
issuerDid: string;
|
|
11
|
+
subjectDid: string;
|
|
12
|
+
aliasDids: readonly string[];
|
|
13
|
+
sectors: readonly string[];
|
|
14
|
+
validFrom: string;
|
|
15
|
+
validUntil?: string;
|
|
16
|
+
id?: string;
|
|
17
|
+
}>): VerifiableCredentialV2;
|
|
18
|
+
/** Returns the normalized identity set asserted by one binding VC. */
|
|
19
|
+
export declare function summarizeSubjectIdentityBinding(credential: unknown): SubjectIdentityBindingSummary | undefined;
|
|
20
|
+
/** Checks issuer trust, validity, sector and exact DID membership. */
|
|
21
|
+
export declare function matchesSubjectIdentityBinding(summary: SubjectIdentityBindingSummary | undefined, criteria: SubjectIdentityBindingMatchCriteria): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Finds a matching binding credential in an already-verified VP token.
|
|
24
|
+
*
|
|
25
|
+
* This function decodes and matches claims; it does not verify signatures.
|
|
26
|
+
* Callers must first verify the enclosing VP/VC proof chain.
|
|
27
|
+
*/
|
|
28
|
+
export declare function getMatchingSubjectIdentityBindingFromVpToken(vpToken: string, criteria: SubjectIdentityBindingMatchCriteria): SubjectIdentityBindingSummary | undefined;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
// Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
|
|
2
|
+
import { IndividualCredentialTypes, W3cCredentialContexts, W3cCredentialTypes, } from '../constants/verifiable-credentials.js';
|
|
3
|
+
import { SubjectIdentityBindingClaims, } from '../models/subject-identity-binding.js';
|
|
4
|
+
import { getVpCredentials } from './vp-token.js';
|
|
5
|
+
function uniqueStrings(value) {
|
|
6
|
+
const values = Array.isArray(value) ? value : value ? [value] : [];
|
|
7
|
+
return Array.from(new Set(values.map((item) => String(item || '').trim()).filter(Boolean)));
|
|
8
|
+
}
|
|
9
|
+
function isDidWeb(value) {
|
|
10
|
+
return /^did:web:[^:\s]+(?::[^:\s]+)*$/.test(value);
|
|
11
|
+
}
|
|
12
|
+
function isPhysicalSupportDid(value) {
|
|
13
|
+
const segments = value.toLowerCase().split(':');
|
|
14
|
+
return segments.includes('card') || segments.includes('petd');
|
|
15
|
+
}
|
|
16
|
+
function isIndividualIdentityDid(value) {
|
|
17
|
+
return isDidWeb(value) && !isPhysicalSupportDid(value);
|
|
18
|
+
}
|
|
19
|
+
function includesCredentialType(credential) {
|
|
20
|
+
const types = uniqueStrings(credential?.type);
|
|
21
|
+
return types.includes(IndividualCredentialTypes.SubjectIdentityBindingCredential);
|
|
22
|
+
}
|
|
23
|
+
function normalizeNow(input) {
|
|
24
|
+
if (input instanceof Date)
|
|
25
|
+
return input.getTime();
|
|
26
|
+
if (input)
|
|
27
|
+
return new Date(input).getTime();
|
|
28
|
+
return Date.now();
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Builds the unsigned JSON form of a subject identity binding VC.
|
|
32
|
+
*
|
|
33
|
+
* This helper only builds the canonical payload. The issuer must sign it using
|
|
34
|
+
* the normal VC/VP signing flow before a verifier treats it as evidence.
|
|
35
|
+
*/
|
|
36
|
+
export function buildSubjectIdentityBindingCredential(input) {
|
|
37
|
+
const issuerDid = String(input.issuerDid || '').trim();
|
|
38
|
+
const subjectDid = String(input.subjectDid || '').trim();
|
|
39
|
+
const aliasDids = uniqueStrings(input.aliasDids).filter((did) => did !== subjectDid);
|
|
40
|
+
const sectors = uniqueStrings(input.sectors);
|
|
41
|
+
if (!isDidWeb(issuerDid))
|
|
42
|
+
throw new Error('issuerDid must be a did:web identifier.');
|
|
43
|
+
if (!isIndividualIdentityDid(subjectDid)) {
|
|
44
|
+
throw new Error('subjectDid must be a did:web individual identifier, not a physical support DID.');
|
|
45
|
+
}
|
|
46
|
+
if (aliasDids.length === 0 || aliasDids.some((did) => !isIndividualIdentityDid(did))) {
|
|
47
|
+
throw new Error('aliasDids must contain at least one distinct did:web individual identifier and no physical support DID.');
|
|
48
|
+
}
|
|
49
|
+
if (sectors.length === 0)
|
|
50
|
+
throw new Error('sectors must contain at least one sector.');
|
|
51
|
+
if (!String(input.validFrom || '').trim() || Number.isNaN(Date.parse(input.validFrom))) {
|
|
52
|
+
throw new Error('validFrom must be an ISO date-time.');
|
|
53
|
+
}
|
|
54
|
+
if (input.validUntil && Number.isNaN(Date.parse(input.validUntil))) {
|
|
55
|
+
throw new Error('validUntil must be an ISO date-time.');
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
'@context': [W3cCredentialContexts.V2],
|
|
59
|
+
...(input.id ? { id: String(input.id).trim() } : {}),
|
|
60
|
+
type: [
|
|
61
|
+
W3cCredentialTypes.VerifiableCredential,
|
|
62
|
+
IndividualCredentialTypes.SubjectIdentityBindingCredential,
|
|
63
|
+
],
|
|
64
|
+
issuer: issuerDid,
|
|
65
|
+
validFrom: String(input.validFrom).trim(),
|
|
66
|
+
...(input.validUntil ? { validUntil: String(input.validUntil).trim() } : {}),
|
|
67
|
+
credentialSubject: {
|
|
68
|
+
[SubjectIdentityBindingClaims.SubjectId]: subjectDid,
|
|
69
|
+
[SubjectIdentityBindingClaims.SameAs]: aliasDids,
|
|
70
|
+
[SubjectIdentityBindingClaims.Sector]: sectors,
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/** Returns the normalized identity set asserted by one binding VC. */
|
|
75
|
+
export function summarizeSubjectIdentityBinding(credential) {
|
|
76
|
+
if (!credential || typeof credential !== 'object' || !includesCredentialType(credential))
|
|
77
|
+
return undefined;
|
|
78
|
+
const source = credential;
|
|
79
|
+
const issuerDid = String(source.issuer?.id || source.issuer || '').trim();
|
|
80
|
+
const subject = source.credentialSubject || {};
|
|
81
|
+
const subjectDid = String(subject[SubjectIdentityBindingClaims.SubjectId] || '').trim();
|
|
82
|
+
const aliasDids = uniqueStrings(subject[SubjectIdentityBindingClaims.SameAs])
|
|
83
|
+
.filter((did) => did !== subjectDid);
|
|
84
|
+
const sectors = uniqueStrings(subject[SubjectIdentityBindingClaims.Sector]);
|
|
85
|
+
if (!isDidWeb(issuerDid) || !isIndividualIdentityDid(subjectDid) || aliasDids.length === 0)
|
|
86
|
+
return undefined;
|
|
87
|
+
if (aliasDids.some((did) => !isIndividualIdentityDid(did)) || sectors.length === 0)
|
|
88
|
+
return undefined;
|
|
89
|
+
return {
|
|
90
|
+
issuerDid,
|
|
91
|
+
subjectDid,
|
|
92
|
+
aliasDids,
|
|
93
|
+
sectors,
|
|
94
|
+
validFrom: String(source.validFrom || '').trim() || undefined,
|
|
95
|
+
validUntil: String(source.validUntil || '').trim() || undefined,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/** Checks issuer trust, validity, sector and exact DID membership. */
|
|
99
|
+
export function matchesSubjectIdentityBinding(summary, criteria) {
|
|
100
|
+
if (!summary)
|
|
101
|
+
return false;
|
|
102
|
+
if (!criteria.trustedIssuerDids.includes(summary.issuerDid))
|
|
103
|
+
return false;
|
|
104
|
+
const now = normalizeNow(criteria.now);
|
|
105
|
+
if (Number.isNaN(now))
|
|
106
|
+
return false;
|
|
107
|
+
if (summary.validFrom) {
|
|
108
|
+
const start = Date.parse(summary.validFrom);
|
|
109
|
+
if (Number.isNaN(start) || start > now)
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
if (summary.validUntil) {
|
|
113
|
+
const end = Date.parse(summary.validUntil);
|
|
114
|
+
if (Number.isNaN(end) || end < now)
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
if (criteria.sector && !summary.sectors.includes(criteria.sector))
|
|
118
|
+
return false;
|
|
119
|
+
const identities = new Set([summary.subjectDid, ...summary.aliasDids]);
|
|
120
|
+
const required = uniqueStrings(criteria.requiredSubjectDids);
|
|
121
|
+
return required.length > 0 && required.every((did) => identities.has(did));
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Finds a matching binding credential in an already-verified VP token.
|
|
125
|
+
*
|
|
126
|
+
* This function decodes and matches claims; it does not verify signatures.
|
|
127
|
+
* Callers must first verify the enclosing VP/VC proof chain.
|
|
128
|
+
*/
|
|
129
|
+
export function getMatchingSubjectIdentityBindingFromVpToken(vpToken, criteria) {
|
|
130
|
+
for (const credential of getVpCredentials(vpToken)) {
|
|
131
|
+
const summary = summarizeSubjectIdentityBinding(credential);
|
|
132
|
+
if (matchesSubjectIdentityBinding(summary, criteria))
|
|
133
|
+
return summary;
|
|
134
|
+
}
|
|
135
|
+
return undefined;
|
|
136
|
+
}
|