gdc-common-utils-ts 2.0.17 → 2.0.18
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 +7 -0
- package/dist/constants/verifiable-credentials.js +7 -0
- package/dist/examples/contract-examples.d.ts +1 -0
- package/dist/examples/contract-examples.js +1 -0
- package/dist/examples/index.d.ts +1 -0
- package/dist/examples/index.js +1 -0
- package/dist/examples/inter-tenant-access-contract.d.ts +124 -0
- package/dist/examples/inter-tenant-access-contract.js +134 -0
- package/dist/examples/shared.d.ts +5 -0
- package/dist/examples/shared.js +5 -0
- package/dist/models/index.d.ts +1 -0
- package/dist/models/index.js +1 -0
- package/dist/models/inter-tenant-access-contract.d.ts +105 -0
- package/dist/models/inter-tenant-access-contract.js +78 -0
- package/dist/utils/evidence-blockchain-references.d.ts +2 -0
- package/dist/utils/evidence-blockchain-references.js +4 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.js +2 -0
- package/dist/utils/inter-tenant-access-contract.d.ts +44 -0
- package/dist/utils/inter-tenant-access-contract.js +358 -0
- package/dist/utils/organization-authorization-urn.d.ts +43 -0
- package/dist/utils/organization-authorization-urn.js +87 -0
- package/package.json +1 -1
|
@@ -36,5 +36,12 @@ export declare const ActivationCredentialTypes: Readonly<{
|
|
|
36
36
|
export declare const ProfessionalCredentialTypes: Readonly<{
|
|
37
37
|
EmployeeCredential: "EmployeeCredential";
|
|
38
38
|
}>;
|
|
39
|
+
/**
|
|
40
|
+
* Canonical credential subtype names used by inter-tenant authorization
|
|
41
|
+
* contracts for cross-organization access.
|
|
42
|
+
*/
|
|
43
|
+
export declare const ContractCredentialTypes: Readonly<{
|
|
44
|
+
InterTenantAccessContractCredential: "InterTenantAccessContractCredential";
|
|
45
|
+
}>;
|
|
39
46
|
export declare const ORGANIZATION_ACTIVATION_VC_TYPES: readonly ("OrganizationCredential" | "LegalOrganizationCredential")[];
|
|
40
47
|
export declare const REPRESENTATIVE_ACTIVATION_VC_TYPES: readonly ("LegalRepresentativeCredential" | "PersonCredential")[];
|
|
@@ -38,6 +38,13 @@ export const ActivationCredentialTypes = Object.freeze({
|
|
|
38
38
|
export const ProfessionalCredentialTypes = Object.freeze({
|
|
39
39
|
EmployeeCredential: 'EmployeeCredential',
|
|
40
40
|
});
|
|
41
|
+
/**
|
|
42
|
+
* Canonical credential subtype names used by inter-tenant authorization
|
|
43
|
+
* contracts for cross-organization access.
|
|
44
|
+
*/
|
|
45
|
+
export const ContractCredentialTypes = Object.freeze({
|
|
46
|
+
InterTenantAccessContractCredential: 'InterTenantAccessContractCredential',
|
|
47
|
+
});
|
|
41
48
|
export const ORGANIZATION_ACTIVATION_VC_TYPES = Object.freeze([
|
|
42
49
|
ActivationCredentialTypes.OrganizationCredential,
|
|
43
50
|
ActivationCredentialTypes.LegalOrganizationCredential,
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
export * from './shared';
|
|
9
9
|
export * from './ica-activation-proof';
|
|
10
10
|
export * from './organization-controller';
|
|
11
|
+
export * from './inter-tenant-access-contract';
|
|
11
12
|
export * from './individual-controller';
|
|
12
13
|
export * from './professional';
|
|
13
14
|
export * from './related-person';
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
export * from './shared.js';
|
|
10
10
|
export * from './ica-activation-proof.js';
|
|
11
11
|
export * from './organization-controller.js';
|
|
12
|
+
export * from './inter-tenant-access-contract.js';
|
|
12
13
|
export * from './individual-controller.js';
|
|
13
14
|
export * from './professional.js';
|
|
14
15
|
export * from './related-person.js';
|
package/dist/examples/index.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export * from './professional';
|
|
|
10
10
|
export * from './employee';
|
|
11
11
|
export * from './license';
|
|
12
12
|
export * from './invoice';
|
|
13
|
+
export * from './inter-tenant-access-contract';
|
|
13
14
|
export * from './related-person';
|
|
14
15
|
export * from './consent-access';
|
|
15
16
|
export * from './relationship-access';
|
package/dist/examples/index.js
CHANGED
|
@@ -10,6 +10,7 @@ export * from './professional.js';
|
|
|
10
10
|
export * from './employee.js';
|
|
11
11
|
export * from './license.js';
|
|
12
12
|
export * from './invoice.js';
|
|
13
|
+
export * from './inter-tenant-access-contract.js';
|
|
13
14
|
export * from './related-person.js';
|
|
14
15
|
export * from './consent-access.js';
|
|
15
16
|
export * from './relationship-access.js';
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared synthetic inter-tenant contract fixtures reused by GW and SDK tests.
|
|
3
|
+
*
|
|
4
|
+
* Scenario:
|
|
5
|
+
* - `acme-id` is the clinical provider tenant in `health-care`
|
|
6
|
+
* - `lab-id` is the research tenant in `health-research`
|
|
7
|
+
* - both are assumed to be hosted by the same operator for the current scope
|
|
8
|
+
*
|
|
9
|
+
* Programmer hints:
|
|
10
|
+
* - provider = the tenant that owns/exposes the data
|
|
11
|
+
* - consumer = the foreign tenant asking for access
|
|
12
|
+
* - capability = the allowed technical scope, for example
|
|
13
|
+
* `organization/Composition.rs`
|
|
14
|
+
* - purpose = the allowed business/legal reason, for example `RESEARCH`
|
|
15
|
+
* - instantiates-uri = primary agreement artifact URL/CID, typically the
|
|
16
|
+
* signed PDF of the contract itself
|
|
17
|
+
* - invoice/payment evidence is intentionally not modeled as a mandatory core
|
|
18
|
+
* field in this first inter-tenant fixture
|
|
19
|
+
*/
|
|
20
|
+
export declare const EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_ID: "urn:uuid:inter-tenant-access-contract-001";
|
|
21
|
+
export declare const EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_VALID_FROM: "2026-06-29T00:00:00.000Z";
|
|
22
|
+
export declare const EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_VALID_UNTIL: "2027-06-29T00:00:00.000Z";
|
|
23
|
+
export declare const EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_PURPOSE: "RESEARCH";
|
|
24
|
+
export declare const EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_SCOPE: "organization/Composition.rs";
|
|
25
|
+
export declare const EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_SUBJECT_DID: "did:web:api.acme.org:individual:123";
|
|
26
|
+
export declare const EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_SECTION: "LOINC|48765-2";
|
|
27
|
+
export declare const EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_AGREEMENT_PDF_URL: "https://portal.example.org/files/contracts/inter-tenant-contract-acme-lab-001.pdf";
|
|
28
|
+
export declare const EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_CONSUMER_PROFESSIONAL_DID: "did:web:api.lab.org:employee:researcher1@lab.org:ISCO-08|2211";
|
|
29
|
+
export declare const EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_SMART_SCOPE: "organization/Composition.rs?subject=did:web:api.acme.org:individual:123§ion=LOINC|48765-2";
|
|
30
|
+
export declare const EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_CLAIMS: Readonly<{
|
|
31
|
+
readonly "Contract.identifier": "urn:uuid:inter-tenant-access-contract-001";
|
|
32
|
+
readonly "Contract.status": "executed";
|
|
33
|
+
readonly "Contract.issued": "2026-06-29T00:00:00.000Z";
|
|
34
|
+
readonly "Contract.applies-start": "2026-06-29T00:00:00.000Z";
|
|
35
|
+
readonly "Contract.applies-end": "2027-06-29T00:00:00.000Z";
|
|
36
|
+
readonly "Contract.provider-organization": "did:web:api.acme.org";
|
|
37
|
+
readonly "Contract.consumer-organization": "did:web:api.lab.org";
|
|
38
|
+
readonly "Contract.provider-controller": "did:web:people.acme.org:controllers:primary";
|
|
39
|
+
readonly "Contract.consumer-controller": "did:web:people.lab.org:controllers:primary";
|
|
40
|
+
readonly "Contract.security-label": "organization/Composition.rs";
|
|
41
|
+
readonly "Contract.term-type": "RESEARCH";
|
|
42
|
+
readonly "Contract.instantiates-uri": "https://portal.example.org/files/contracts/inter-tenant-contract-acme-lab-001.pdf";
|
|
43
|
+
}>;
|
|
44
|
+
export declare const EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_RESOURCE: Readonly<{
|
|
45
|
+
[x: string]: any;
|
|
46
|
+
}>;
|
|
47
|
+
export declare const EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_CREDENTIAL: Readonly<import("..").VerifiableCredentialV2>;
|
|
48
|
+
export declare const EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_CONTEXT: Readonly<{
|
|
49
|
+
readonly providerTenantId: "acme-id";
|
|
50
|
+
readonly providerSector: "health-care";
|
|
51
|
+
readonly providerOrganizationDid: "did:web:api.acme.org";
|
|
52
|
+
readonly consumerTenantId: "lab-id";
|
|
53
|
+
readonly consumerSector: "health-research";
|
|
54
|
+
readonly consumerOrganizationDid: "did:web:api.lab.org";
|
|
55
|
+
readonly jurisdiction: "ES";
|
|
56
|
+
readonly actorRole: "ISCO-08|2211";
|
|
57
|
+
readonly subjectDid: "did:web:api.acme.org:individual:123";
|
|
58
|
+
readonly consumerProfessionalDid: "did:web:api.lab.org:employee:researcher1@lab.org:ISCO-08|2211";
|
|
59
|
+
readonly requestedScope: "organization/Composition.rs";
|
|
60
|
+
readonly requestedSection: "LOINC|48765-2";
|
|
61
|
+
readonly smartScope: "organization/Composition.rs?subject=did:web:api.acme.org:individual:123§ion=LOINC|48765-2";
|
|
62
|
+
readonly purpose: "RESEARCH";
|
|
63
|
+
}>;
|
|
64
|
+
export declare const EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_PROVIDER_ORGANIZATION_URN: string;
|
|
65
|
+
export declare const EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_CONSUMER_ORGANIZATION_URN: string;
|
|
66
|
+
export declare const EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_CONSUMER_MEMBER_URN: string;
|
|
67
|
+
export declare const EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_CONTEXT_WITH_URNS: Readonly<{
|
|
68
|
+
readonly providerOrganizationUrn: string;
|
|
69
|
+
readonly consumerOrganizationUrn: string;
|
|
70
|
+
readonly consumerMemberUrn: string;
|
|
71
|
+
readonly providerTenantId: "acme-id";
|
|
72
|
+
readonly providerSector: "health-care";
|
|
73
|
+
readonly providerOrganizationDid: "did:web:api.acme.org";
|
|
74
|
+
readonly consumerTenantId: "lab-id";
|
|
75
|
+
readonly consumerSector: "health-research";
|
|
76
|
+
readonly consumerOrganizationDid: "did:web:api.lab.org";
|
|
77
|
+
readonly jurisdiction: "ES";
|
|
78
|
+
readonly actorRole: "ISCO-08|2211";
|
|
79
|
+
readonly subjectDid: "did:web:api.acme.org:individual:123";
|
|
80
|
+
readonly consumerProfessionalDid: "did:web:api.lab.org:employee:researcher1@lab.org:ISCO-08|2211";
|
|
81
|
+
readonly requestedScope: "organization/Composition.rs";
|
|
82
|
+
readonly requestedSection: "LOINC|48765-2";
|
|
83
|
+
readonly smartScope: "organization/Composition.rs?subject=did:web:api.acme.org:individual:123§ion=LOINC|48765-2";
|
|
84
|
+
readonly purpose: "RESEARCH";
|
|
85
|
+
}>;
|
|
86
|
+
/**
|
|
87
|
+
* Consent-style organization-to-employee delegation rule reused for the
|
|
88
|
+
* consumer tenant side.
|
|
89
|
+
*
|
|
90
|
+
* This intentionally reuses the existing `Consent.*` rule pattern:
|
|
91
|
+
* - `Consent.subject` is the consumer organization URN
|
|
92
|
+
* - `Consent.actor-identifier` is the delegated member URN
|
|
93
|
+
* - `Consent.actor-role` carries the role separately
|
|
94
|
+
* - `Consent.source-reference` points to the blockchain-safe contract VC
|
|
95
|
+
* reference that was or will be anchored on-chain
|
|
96
|
+
*/
|
|
97
|
+
export declare const EXAMPLE_INTER_TENANT_EMPLOYEE_CONTRACT_AUTHORIZATION_CONSENT: Readonly<{
|
|
98
|
+
readonly '@context': "org.hl7.fhir.api";
|
|
99
|
+
readonly "Consent.identifier": "urn:uuid:employee-contract-authorization-001";
|
|
100
|
+
readonly "Consent.subject": string;
|
|
101
|
+
readonly "Consent.actor-identifier": string;
|
|
102
|
+
readonly "Consent.actor-role": "ISCO-08|2211";
|
|
103
|
+
readonly "Consent.decision": "permit";
|
|
104
|
+
readonly "Consent.purpose": "RESEARCH";
|
|
105
|
+
readonly "Consent.action": "organization/Composition.rs";
|
|
106
|
+
readonly "Consent.source-reference": string | undefined;
|
|
107
|
+
readonly "Consent.date": "2026-06-29T00:00:00.000Z";
|
|
108
|
+
readonly "Consent.period-start": "2026-06-29T00:00:00.000Z";
|
|
109
|
+
readonly "Consent.period-end": "2027-06-29T00:00:00.000Z";
|
|
110
|
+
}>;
|
|
111
|
+
export declare const EXAMPLE_INTER_TENANT_PROVIDER_CONTRACT_AUTHORIZATION_CONSENT: Readonly<{
|
|
112
|
+
readonly '@context': "org.hl7.fhir.api";
|
|
113
|
+
readonly "Consent.identifier": "urn:uuid:provider-contract-authorization-001";
|
|
114
|
+
readonly "Consent.subject": string;
|
|
115
|
+
readonly "Consent.actor-identifier": string;
|
|
116
|
+
readonly "Consent.actor-role": "organization";
|
|
117
|
+
readonly "Consent.decision": "permit";
|
|
118
|
+
readonly "Consent.purpose": "RESEARCH";
|
|
119
|
+
readonly "Consent.action": "organization/Composition.rs";
|
|
120
|
+
readonly "Consent.source-reference": string | undefined;
|
|
121
|
+
readonly "Consent.date": "2026-06-29T00:00:00.000Z";
|
|
122
|
+
readonly "Consent.period-start": "2026-06-29T00:00:00.000Z";
|
|
123
|
+
readonly "Consent.period-end": "2027-06-29T00:00:00.000Z";
|
|
124
|
+
}>;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
|
|
2
|
+
import { ServiceCapability } from '../constants/service-capabilities.js';
|
|
3
|
+
import { DataspaceSectors } from '../constants/sectors.js';
|
|
4
|
+
import { ClaimConsent } from '../models/consent-rule.js';
|
|
5
|
+
import { ClaimInterTenantAccessContract } from '../models/inter-tenant-access-contract.js';
|
|
6
|
+
import { buildInterTenantAccessContractCredential, buildInterTenantAccessContractResource, getInterTenantAccessContractBlockchainReference, } from '../utils/inter-tenant-access-contract.js';
|
|
7
|
+
import { buildMemberAuthorizationUrn, buildOrganizationAuthorizationUrn, } from '../utils/organization-authorization-urn.js';
|
|
8
|
+
import { EXAMPLE_API_ORGANIZATION_DID, EXAMPLE_CONTROLLER_DID, EXAMPLE_HEALTHCARE_ACTOR_ROLE_PHYSICIAN, EXAMPLE_JURISDICTION, EXAMPLE_RESEARCH_API_ORGANIZATION_DID, EXAMPLE_RESEARCH_CONTROLLER_DID, EXAMPLE_RESEARCH_TENANT_IDENTIFIER, EXAMPLE_SECTOR, EXAMPLE_TENANT_IDENTIFIER, } from './shared.js';
|
|
9
|
+
/**
|
|
10
|
+
* Shared synthetic inter-tenant contract fixtures reused by GW and SDK tests.
|
|
11
|
+
*
|
|
12
|
+
* Scenario:
|
|
13
|
+
* - `acme-id` is the clinical provider tenant in `health-care`
|
|
14
|
+
* - `lab-id` is the research tenant in `health-research`
|
|
15
|
+
* - both are assumed to be hosted by the same operator for the current scope
|
|
16
|
+
*
|
|
17
|
+
* Programmer hints:
|
|
18
|
+
* - provider = the tenant that owns/exposes the data
|
|
19
|
+
* - consumer = the foreign tenant asking for access
|
|
20
|
+
* - capability = the allowed technical scope, for example
|
|
21
|
+
* `organization/Composition.rs`
|
|
22
|
+
* - purpose = the allowed business/legal reason, for example `RESEARCH`
|
|
23
|
+
* - instantiates-uri = primary agreement artifact URL/CID, typically the
|
|
24
|
+
* signed PDF of the contract itself
|
|
25
|
+
* - invoice/payment evidence is intentionally not modeled as a mandatory core
|
|
26
|
+
* field in this first inter-tenant fixture
|
|
27
|
+
*/
|
|
28
|
+
export const EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_ID = 'urn:uuid:inter-tenant-access-contract-001';
|
|
29
|
+
export const EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_VALID_FROM = '2026-06-29T00:00:00.000Z';
|
|
30
|
+
export const EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_VALID_UNTIL = '2027-06-29T00:00:00.000Z';
|
|
31
|
+
export const EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_PURPOSE = 'RESEARCH';
|
|
32
|
+
export const EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_SCOPE = ServiceCapability.IndexReader;
|
|
33
|
+
export const EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_SUBJECT_DID = 'did:web:api.acme.org:individual:123';
|
|
34
|
+
export const EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_SECTION = 'LOINC|48765-2';
|
|
35
|
+
export const EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_AGREEMENT_PDF_URL = 'https://portal.example.org/files/contracts/inter-tenant-contract-acme-lab-001.pdf';
|
|
36
|
+
export const EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_CONSUMER_PROFESSIONAL_DID = `${EXAMPLE_RESEARCH_API_ORGANIZATION_DID}:employee:researcher1@lab.org:${EXAMPLE_HEALTHCARE_ACTOR_ROLE_PHYSICIAN}`;
|
|
37
|
+
export const EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_SMART_SCOPE = `${EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_SCOPE}?subject=${EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_SUBJECT_DID}§ion=${EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_SECTION}`;
|
|
38
|
+
export const EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_CLAIMS = Object.freeze({
|
|
39
|
+
[ClaimInterTenantAccessContract.identifier]: EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_ID,
|
|
40
|
+
[ClaimInterTenantAccessContract.status]: 'executed',
|
|
41
|
+
[ClaimInterTenantAccessContract.issued]: EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_VALID_FROM,
|
|
42
|
+
[ClaimInterTenantAccessContract.appliesStart]: EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_VALID_FROM,
|
|
43
|
+
[ClaimInterTenantAccessContract.appliesEnd]: EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_VALID_UNTIL,
|
|
44
|
+
[ClaimInterTenantAccessContract.providerOrganization]: EXAMPLE_API_ORGANIZATION_DID,
|
|
45
|
+
[ClaimInterTenantAccessContract.consumerOrganization]: EXAMPLE_RESEARCH_API_ORGANIZATION_DID,
|
|
46
|
+
[ClaimInterTenantAccessContract.providerController]: EXAMPLE_CONTROLLER_DID,
|
|
47
|
+
[ClaimInterTenantAccessContract.consumerController]: EXAMPLE_RESEARCH_CONTROLLER_DID,
|
|
48
|
+
[ClaimInterTenantAccessContract.capability]: EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_SCOPE,
|
|
49
|
+
[ClaimInterTenantAccessContract.purpose]: EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_PURPOSE,
|
|
50
|
+
[ClaimInterTenantAccessContract.instantiatesUri]: EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_AGREEMENT_PDF_URL,
|
|
51
|
+
});
|
|
52
|
+
export const EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_RESOURCE = Object.freeze(buildInterTenantAccessContractResource(EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_CLAIMS));
|
|
53
|
+
export const EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_CREDENTIAL = Object.freeze(buildInterTenantAccessContractCredential({
|
|
54
|
+
claims: EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_CLAIMS,
|
|
55
|
+
issuer: EXAMPLE_CONTROLLER_DID,
|
|
56
|
+
validFrom: EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_VALID_FROM,
|
|
57
|
+
validUntil: EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_VALID_UNTIL,
|
|
58
|
+
additionalCredential: {
|
|
59
|
+
id: EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_ID,
|
|
60
|
+
},
|
|
61
|
+
}));
|
|
62
|
+
export const EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_CONTEXT = Object.freeze({
|
|
63
|
+
providerTenantId: EXAMPLE_TENANT_IDENTIFIER,
|
|
64
|
+
providerSector: EXAMPLE_SECTOR,
|
|
65
|
+
providerOrganizationDid: EXAMPLE_API_ORGANIZATION_DID,
|
|
66
|
+
consumerTenantId: EXAMPLE_RESEARCH_TENANT_IDENTIFIER,
|
|
67
|
+
consumerSector: DataspaceSectors.HealthResearch,
|
|
68
|
+
consumerOrganizationDid: EXAMPLE_RESEARCH_API_ORGANIZATION_DID,
|
|
69
|
+
jurisdiction: EXAMPLE_JURISDICTION,
|
|
70
|
+
actorRole: EXAMPLE_HEALTHCARE_ACTOR_ROLE_PHYSICIAN,
|
|
71
|
+
subjectDid: EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_SUBJECT_DID,
|
|
72
|
+
consumerProfessionalDid: EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_CONSUMER_PROFESSIONAL_DID,
|
|
73
|
+
requestedScope: EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_SCOPE,
|
|
74
|
+
requestedSection: EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_SECTION,
|
|
75
|
+
smartScope: EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_SMART_SCOPE,
|
|
76
|
+
purpose: EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_PURPOSE,
|
|
77
|
+
});
|
|
78
|
+
export const EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_PROVIDER_ORGANIZATION_URN = buildOrganizationAuthorizationUrn({
|
|
79
|
+
identifierType: 'TAX',
|
|
80
|
+
identifierValue: EXAMPLE_TENANT_IDENTIFIER,
|
|
81
|
+
});
|
|
82
|
+
export const EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_CONSUMER_ORGANIZATION_URN = buildOrganizationAuthorizationUrn({
|
|
83
|
+
identifierType: 'TAX',
|
|
84
|
+
identifierValue: EXAMPLE_RESEARCH_TENANT_IDENTIFIER,
|
|
85
|
+
});
|
|
86
|
+
export const EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_CONSUMER_MEMBER_URN = buildMemberAuthorizationUrn({
|
|
87
|
+
organizationUrn: EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_CONSUMER_ORGANIZATION_URN,
|
|
88
|
+
memberId: 'researcher-001',
|
|
89
|
+
});
|
|
90
|
+
export const EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_CONTEXT_WITH_URNS = Object.freeze({
|
|
91
|
+
...EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_CONTEXT,
|
|
92
|
+
providerOrganizationUrn: EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_PROVIDER_ORGANIZATION_URN,
|
|
93
|
+
consumerOrganizationUrn: EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_CONSUMER_ORGANIZATION_URN,
|
|
94
|
+
consumerMemberUrn: EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_CONSUMER_MEMBER_URN,
|
|
95
|
+
});
|
|
96
|
+
/**
|
|
97
|
+
* Consent-style organization-to-employee delegation rule reused for the
|
|
98
|
+
* consumer tenant side.
|
|
99
|
+
*
|
|
100
|
+
* This intentionally reuses the existing `Consent.*` rule pattern:
|
|
101
|
+
* - `Consent.subject` is the consumer organization URN
|
|
102
|
+
* - `Consent.actor-identifier` is the delegated member URN
|
|
103
|
+
* - `Consent.actor-role` carries the role separately
|
|
104
|
+
* - `Consent.source-reference` points to the blockchain-safe contract VC
|
|
105
|
+
* reference that was or will be anchored on-chain
|
|
106
|
+
*/
|
|
107
|
+
export const EXAMPLE_INTER_TENANT_EMPLOYEE_CONTRACT_AUTHORIZATION_CONSENT = Object.freeze({
|
|
108
|
+
'@context': 'org.hl7.fhir.api',
|
|
109
|
+
[ClaimConsent.identifier]: 'urn:uuid:employee-contract-authorization-001',
|
|
110
|
+
[ClaimConsent.subject]: EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_CONSUMER_ORGANIZATION_URN,
|
|
111
|
+
[ClaimConsent.actorIdentifier]: EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_CONSUMER_MEMBER_URN,
|
|
112
|
+
[ClaimConsent.actorRole]: EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_CONTEXT.actorRole,
|
|
113
|
+
[ClaimConsent.decision]: 'permit',
|
|
114
|
+
[ClaimConsent.purpose]: EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_CONTEXT.purpose,
|
|
115
|
+
[ClaimConsent.action]: EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_CONTEXT.requestedScope,
|
|
116
|
+
[ClaimConsent.sourceReference]: getInterTenantAccessContractBlockchainReference(EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_CREDENTIAL),
|
|
117
|
+
[ClaimConsent.date]: EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_VALID_FROM,
|
|
118
|
+
[ClaimConsent.periodStart]: EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_VALID_FROM,
|
|
119
|
+
[ClaimConsent.periodEnd]: EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_VALID_UNTIL,
|
|
120
|
+
});
|
|
121
|
+
export const EXAMPLE_INTER_TENANT_PROVIDER_CONTRACT_AUTHORIZATION_CONSENT = Object.freeze({
|
|
122
|
+
'@context': 'org.hl7.fhir.api',
|
|
123
|
+
[ClaimConsent.identifier]: 'urn:uuid:provider-contract-authorization-001',
|
|
124
|
+
[ClaimConsent.subject]: EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_PROVIDER_ORGANIZATION_URN,
|
|
125
|
+
[ClaimConsent.actorIdentifier]: EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_CONSUMER_ORGANIZATION_URN,
|
|
126
|
+
[ClaimConsent.actorRole]: 'organization',
|
|
127
|
+
[ClaimConsent.decision]: 'permit',
|
|
128
|
+
[ClaimConsent.purpose]: EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_CONTEXT.purpose,
|
|
129
|
+
[ClaimConsent.action]: EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_CONTEXT.requestedScope,
|
|
130
|
+
[ClaimConsent.sourceReference]: getInterTenantAccessContractBlockchainReference(EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_CREDENTIAL),
|
|
131
|
+
[ClaimConsent.date]: EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_VALID_FROM,
|
|
132
|
+
[ClaimConsent.periodStart]: EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_VALID_FROM,
|
|
133
|
+
[ClaimConsent.periodEnd]: EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_VALID_UNTIL,
|
|
134
|
+
});
|
|
@@ -11,12 +11,15 @@ import { IdKind } from '../constants/identity-identifiers';
|
|
|
11
11
|
* - all values below are synthetic documentation/test fixtures, never real data
|
|
12
12
|
*/
|
|
13
13
|
export declare const EXAMPLE_TENANT_IDENTIFIER: "acme-id";
|
|
14
|
+
export declare const EXAMPLE_RESEARCH_TENANT_IDENTIFIER: "lab-id";
|
|
14
15
|
export declare const EXAMPLE_JURISDICTION: "ES";
|
|
15
16
|
export declare const EXAMPLE_HOST_COVERAGE_SCOPE: "EU";
|
|
16
17
|
export declare const EXAMPLE_NETWORK_TYPE: "test";
|
|
17
18
|
export declare const EXAMPLE_ROUTE_VERSION: "v1";
|
|
18
19
|
export declare const EXAMPLE_SECTOR: "health-care";
|
|
20
|
+
export declare const EXAMPLE_RESEARCH_SECTOR: "health-research";
|
|
19
21
|
export declare const EXAMPLE_EMAIL_CONTROLLER_ORG: "controller@acme.org";
|
|
22
|
+
export declare const EXAMPLE_EMAIL_CONTROLLER_RESEARCH_ORG: "controller@lab.org";
|
|
20
23
|
export declare const EXAMPLE_ORGANIZATION_LEGAL_NAME: "ACME HEALTH SL";
|
|
21
24
|
export declare const EXAMPLE_LEGAL_ORGANIZATION_TAX_ID: "VATES-B00112233";
|
|
22
25
|
export declare const EXAMPLE_EMAIL_CONTROLLER_INDIVIDUAL: "ana.parent@example.org";
|
|
@@ -101,9 +104,11 @@ export declare const EXAMPLE_HOST_ROUTE_CONTEXT: {
|
|
|
101
104
|
readonly sector: "test";
|
|
102
105
|
};
|
|
103
106
|
export declare const EXAMPLE_CONTROLLER_DID: "did:web:people.acme.org:controllers:primary";
|
|
107
|
+
export declare const EXAMPLE_RESEARCH_CONTROLLER_DID: "did:web:people.lab.org:controllers:primary";
|
|
104
108
|
export declare const EXAMPLE_CONTROLLER_EMAIL: "controller@acme.org";
|
|
105
109
|
export declare const EXAMPLE_CONTROLLER_SAME_AS: "mailto:controller@acme.org";
|
|
106
110
|
export declare const EXAMPLE_API_ORGANIZATION_DID: "did:web:api.acme.org";
|
|
111
|
+
export declare const EXAMPLE_RESEARCH_API_ORGANIZATION_DID: "did:web:api.lab.org";
|
|
107
112
|
export declare const EXAMPLE_SERVICE_PUBLIC_DID: "did:web:public.acme.org";
|
|
108
113
|
export declare const EXAMPLE_PROFESSIONAL_DID: string;
|
|
109
114
|
export declare const EXAMPLE_PROVIDER_ORGANIZATION_DID: "did:web:hospital.acme.org";
|
package/dist/examples/shared.js
CHANGED
|
@@ -26,12 +26,15 @@ import { medicationStatementFlatToFhirR4 } from '../utils/clinical-resource-conv
|
|
|
26
26
|
* - all values below are synthetic documentation/test fixtures, never real data
|
|
27
27
|
*/
|
|
28
28
|
export const EXAMPLE_TENANT_IDENTIFIER = 'acme-id';
|
|
29
|
+
export const EXAMPLE_RESEARCH_TENANT_IDENTIFIER = 'lab-id';
|
|
29
30
|
export const EXAMPLE_JURISDICTION = 'ES';
|
|
30
31
|
export const EXAMPLE_HOST_COVERAGE_SCOPE = 'EU';
|
|
31
32
|
export const EXAMPLE_NETWORK_TYPE = HostNetworkTypes.Test;
|
|
32
33
|
export const EXAMPLE_ROUTE_VERSION = 'v1';
|
|
33
34
|
export const EXAMPLE_SECTOR = DataspaceSectors.HealthCare;
|
|
35
|
+
export const EXAMPLE_RESEARCH_SECTOR = DataspaceSectors.HealthResearch;
|
|
34
36
|
export const EXAMPLE_EMAIL_CONTROLLER_ORG = 'controller@acme.org';
|
|
37
|
+
export const EXAMPLE_EMAIL_CONTROLLER_RESEARCH_ORG = 'controller@lab.org';
|
|
35
38
|
export const EXAMPLE_ORGANIZATION_LEGAL_NAME = 'ACME HEALTH SL';
|
|
36
39
|
export const EXAMPLE_LEGAL_ORGANIZATION_TAX_ID = 'VATES-B00112233';
|
|
37
40
|
export const EXAMPLE_EMAIL_CONTROLLER_INDIVIDUAL = 'ana.parent@example.org';
|
|
@@ -116,9 +119,11 @@ export const EXAMPLE_HOST_ROUTE_CONTEXT = {
|
|
|
116
119
|
sector: EXAMPLE_NETWORK_TYPE,
|
|
117
120
|
};
|
|
118
121
|
export const EXAMPLE_CONTROLLER_DID = 'did:web:people.acme.org:controllers:primary';
|
|
122
|
+
export const EXAMPLE_RESEARCH_CONTROLLER_DID = 'did:web:people.lab.org:controllers:primary';
|
|
119
123
|
export const EXAMPLE_CONTROLLER_EMAIL = EXAMPLE_EMAIL_CONTROLLER_ORG;
|
|
120
124
|
export const EXAMPLE_CONTROLLER_SAME_AS = `mailto:${EXAMPLE_CONTROLLER_EMAIL}`;
|
|
121
125
|
export const EXAMPLE_API_ORGANIZATION_DID = 'did:web:api.acme.org';
|
|
126
|
+
export const EXAMPLE_RESEARCH_API_ORGANIZATION_DID = 'did:web:api.lab.org';
|
|
122
127
|
export const EXAMPLE_SERVICE_PUBLIC_DID = 'did:web:public.acme.org';
|
|
123
128
|
export const EXAMPLE_PROFESSIONAL_DID = buildProfessionalDidWeb({
|
|
124
129
|
organizationDidWeb: 'did:web:api.acme.org',
|
package/dist/models/index.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ export * from './interoperable-claims';
|
|
|
22
22
|
export * from './indexing';
|
|
23
23
|
export * from './identity-bootstrap';
|
|
24
24
|
export * from './individual-onboarding';
|
|
25
|
+
export * from './inter-tenant-access-contract';
|
|
25
26
|
export * from './issue';
|
|
26
27
|
export * from './jsonapi';
|
|
27
28
|
export * from './jwe';
|
package/dist/models/index.js
CHANGED
|
@@ -22,6 +22,7 @@ export * from './interoperable-claims.js';
|
|
|
22
22
|
export * from './indexing.js';
|
|
23
23
|
export * from './identity-bootstrap.js';
|
|
24
24
|
export * from './individual-onboarding.js';
|
|
25
|
+
export * from './inter-tenant-access-contract.js';
|
|
25
26
|
export * from './issue.js';
|
|
26
27
|
export * from './jsonapi.js';
|
|
27
28
|
export * from './jwe.js';
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical flat claim keys used when a frontend captures an inter-tenant
|
|
3
|
+
* access contract form and sends it to a backend before the final FHIR
|
|
4
|
+
* `Contract` resource is assembled.
|
|
5
|
+
*
|
|
6
|
+
* Notes:
|
|
7
|
+
* - these keys are claims-first transport helpers, not the legal source of
|
|
8
|
+
* truth
|
|
9
|
+
* - the canonical legal payload is the FHIR `Contract` placed in
|
|
10
|
+
* `credentialSubject` of the emitted VC
|
|
11
|
+
* - custom flat keys are used only where base FHIR does not define a simple
|
|
12
|
+
* one-claim scalar path suitable for UI transport
|
|
13
|
+
*/
|
|
14
|
+
export declare enum ClaimInterTenantAccessContract {
|
|
15
|
+
/** Stable business identifier of the agreement/contract. */
|
|
16
|
+
identifier = "Contract.identifier",
|
|
17
|
+
/** FHIR Contract lifecycle status, for example `executed` or `amended`. */
|
|
18
|
+
status = "Contract.status",
|
|
19
|
+
/** When the agreement was formally issued. */
|
|
20
|
+
issued = "Contract.issued",
|
|
21
|
+
/** Agreement validity start date/time. */
|
|
22
|
+
appliesStart = "Contract.applies-start",
|
|
23
|
+
/** Agreement validity end date/time. */
|
|
24
|
+
appliesEnd = "Contract.applies-end",
|
|
25
|
+
/**
|
|
26
|
+
* Provider organization DID.
|
|
27
|
+
*
|
|
28
|
+
* Meaning for programmers:
|
|
29
|
+
* - this is the tenant that owns/exposes the protected data
|
|
30
|
+
* - in the current example this is `did:web:api.acme.org`
|
|
31
|
+
*/
|
|
32
|
+
providerOrganization = "Contract.provider-organization",
|
|
33
|
+
/**
|
|
34
|
+
* Consumer organization DID.
|
|
35
|
+
*
|
|
36
|
+
* Meaning for programmers:
|
|
37
|
+
* - this is the foreign tenant requesting access under the agreement
|
|
38
|
+
* - in the current example this is `did:web:api.lab.org`
|
|
39
|
+
*/
|
|
40
|
+
consumerOrganization = "Contract.consumer-organization",
|
|
41
|
+
/**
|
|
42
|
+
* DID of the controller who signed on behalf of the provider organization.
|
|
43
|
+
*/
|
|
44
|
+
providerController = "Contract.provider-controller",
|
|
45
|
+
/**
|
|
46
|
+
* DID of the controller who signed on behalf of the consumer organization.
|
|
47
|
+
*/
|
|
48
|
+
consumerController = "Contract.consumer-controller",
|
|
49
|
+
/**
|
|
50
|
+
* Allowed operational capability, expressed with the same vocabulary used by
|
|
51
|
+
* GW service capabilities and SMART-like root scopes.
|
|
52
|
+
*
|
|
53
|
+
* Example:
|
|
54
|
+
* - `organization/Composition.rs`
|
|
55
|
+
*/
|
|
56
|
+
capability = "Contract.security-label",
|
|
57
|
+
/**
|
|
58
|
+
* Allowed business/legal purpose for the access.
|
|
59
|
+
*
|
|
60
|
+
* Example:
|
|
61
|
+
* - `RESEARCH`
|
|
62
|
+
*/
|
|
63
|
+
purpose = "Contract.term-type",
|
|
64
|
+
/**
|
|
65
|
+
* External instantiated artifact for the agreement, typically the signed PDF
|
|
66
|
+
* or CID/URL of the contract itself plus direct contractual annexes.
|
|
67
|
+
*
|
|
68
|
+
* Canonical FHIR field:
|
|
69
|
+
* - `Contract.instantiatesUri`
|
|
70
|
+
*
|
|
71
|
+
* Flat claim rule in this stack:
|
|
72
|
+
* - use FHIR-style `instantiates-uri` in claims, then map it to
|
|
73
|
+
* `instantiatesUri` in the actual FHIR resource object
|
|
74
|
+
*/
|
|
75
|
+
instantiatesUri = "Contract.instantiates-uri"
|
|
76
|
+
}
|
|
77
|
+
export type InterTenantAccessContractClaims = Partial<Record<ClaimInterTenantAccessContract, string>>;
|
|
78
|
+
export type InterTenantAccessContractSummary = Readonly<{
|
|
79
|
+
identifier?: string;
|
|
80
|
+
status?: string;
|
|
81
|
+
issued?: string;
|
|
82
|
+
appliesStart?: string;
|
|
83
|
+
appliesEnd?: string;
|
|
84
|
+
providerOrganizationDid?: string;
|
|
85
|
+
consumerOrganizationDid?: string;
|
|
86
|
+
providerControllerDid?: string;
|
|
87
|
+
consumerControllerDid?: string;
|
|
88
|
+
capabilities: readonly string[];
|
|
89
|
+
purposes: readonly string[];
|
|
90
|
+
}>;
|
|
91
|
+
export type InterTenantAccessContractMatchCriteria = Readonly<{
|
|
92
|
+
providerOrganizationDid: string;
|
|
93
|
+
consumerOrganizationDid: string;
|
|
94
|
+
requiredCapabilities?: readonly string[];
|
|
95
|
+
purpose?: string;
|
|
96
|
+
now?: string | Date;
|
|
97
|
+
}>;
|
|
98
|
+
export type InterTenantContractAuthorizationConsentCriteria = Readonly<{
|
|
99
|
+
consumerOrganizationDid: string;
|
|
100
|
+
actorIdentifier: string;
|
|
101
|
+
actorRole?: string;
|
|
102
|
+
requiredCapabilities?: readonly string[];
|
|
103
|
+
purpose?: string;
|
|
104
|
+
now?: string | Date;
|
|
105
|
+
}>;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
|
|
2
|
+
/**
|
|
3
|
+
* Canonical flat claim keys used when a frontend captures an inter-tenant
|
|
4
|
+
* access contract form and sends it to a backend before the final FHIR
|
|
5
|
+
* `Contract` resource is assembled.
|
|
6
|
+
*
|
|
7
|
+
* Notes:
|
|
8
|
+
* - these keys are claims-first transport helpers, not the legal source of
|
|
9
|
+
* truth
|
|
10
|
+
* - the canonical legal payload is the FHIR `Contract` placed in
|
|
11
|
+
* `credentialSubject` of the emitted VC
|
|
12
|
+
* - custom flat keys are used only where base FHIR does not define a simple
|
|
13
|
+
* one-claim scalar path suitable for UI transport
|
|
14
|
+
*/
|
|
15
|
+
export var ClaimInterTenantAccessContract;
|
|
16
|
+
(function (ClaimInterTenantAccessContract) {
|
|
17
|
+
/** Stable business identifier of the agreement/contract. */
|
|
18
|
+
ClaimInterTenantAccessContract["identifier"] = "Contract.identifier";
|
|
19
|
+
/** FHIR Contract lifecycle status, for example `executed` or `amended`. */
|
|
20
|
+
ClaimInterTenantAccessContract["status"] = "Contract.status";
|
|
21
|
+
/** When the agreement was formally issued. */
|
|
22
|
+
ClaimInterTenantAccessContract["issued"] = "Contract.issued";
|
|
23
|
+
/** Agreement validity start date/time. */
|
|
24
|
+
ClaimInterTenantAccessContract["appliesStart"] = "Contract.applies-start";
|
|
25
|
+
/** Agreement validity end date/time. */
|
|
26
|
+
ClaimInterTenantAccessContract["appliesEnd"] = "Contract.applies-end";
|
|
27
|
+
/**
|
|
28
|
+
* Provider organization DID.
|
|
29
|
+
*
|
|
30
|
+
* Meaning for programmers:
|
|
31
|
+
* - this is the tenant that owns/exposes the protected data
|
|
32
|
+
* - in the current example this is `did:web:api.acme.org`
|
|
33
|
+
*/
|
|
34
|
+
ClaimInterTenantAccessContract["providerOrganization"] = "Contract.provider-organization";
|
|
35
|
+
/**
|
|
36
|
+
* Consumer organization DID.
|
|
37
|
+
*
|
|
38
|
+
* Meaning for programmers:
|
|
39
|
+
* - this is the foreign tenant requesting access under the agreement
|
|
40
|
+
* - in the current example this is `did:web:api.lab.org`
|
|
41
|
+
*/
|
|
42
|
+
ClaimInterTenantAccessContract["consumerOrganization"] = "Contract.consumer-organization";
|
|
43
|
+
/**
|
|
44
|
+
* DID of the controller who signed on behalf of the provider organization.
|
|
45
|
+
*/
|
|
46
|
+
ClaimInterTenantAccessContract["providerController"] = "Contract.provider-controller";
|
|
47
|
+
/**
|
|
48
|
+
* DID of the controller who signed on behalf of the consumer organization.
|
|
49
|
+
*/
|
|
50
|
+
ClaimInterTenantAccessContract["consumerController"] = "Contract.consumer-controller";
|
|
51
|
+
/**
|
|
52
|
+
* Allowed operational capability, expressed with the same vocabulary used by
|
|
53
|
+
* GW service capabilities and SMART-like root scopes.
|
|
54
|
+
*
|
|
55
|
+
* Example:
|
|
56
|
+
* - `organization/Composition.rs`
|
|
57
|
+
*/
|
|
58
|
+
ClaimInterTenantAccessContract["capability"] = "Contract.security-label";
|
|
59
|
+
/**
|
|
60
|
+
* Allowed business/legal purpose for the access.
|
|
61
|
+
*
|
|
62
|
+
* Example:
|
|
63
|
+
* - `RESEARCH`
|
|
64
|
+
*/
|
|
65
|
+
ClaimInterTenantAccessContract["purpose"] = "Contract.term-type";
|
|
66
|
+
/**
|
|
67
|
+
* External instantiated artifact for the agreement, typically the signed PDF
|
|
68
|
+
* or CID/URL of the contract itself plus direct contractual annexes.
|
|
69
|
+
*
|
|
70
|
+
* Canonical FHIR field:
|
|
71
|
+
* - `Contract.instantiatesUri`
|
|
72
|
+
*
|
|
73
|
+
* Flat claim rule in this stack:
|
|
74
|
+
* - use FHIR-style `instantiates-uri` in claims, then map it to
|
|
75
|
+
* `instantiatesUri` in the actual FHIR resource object
|
|
76
|
+
*/
|
|
77
|
+
ClaimInterTenantAccessContract["instantiatesUri"] = "Contract.instantiates-uri";
|
|
78
|
+
})(ClaimInterTenantAccessContract || (ClaimInterTenantAccessContract = {}));
|
|
@@ -4,6 +4,8 @@ import type { EvidenceObjectDLT } from '../models/oidc4ida.evidence.model.js';
|
|
|
4
4
|
*
|
|
5
5
|
* Current shared rule:
|
|
6
6
|
* - `z...` multibase base58 references are already public and stable
|
|
7
|
+
* - `sha3-384:...` references are already blockchain-safe and must pass
|
|
8
|
+
* through unchanged
|
|
7
9
|
* - any other identifier-like value is SHA3-384 hashed before persistence
|
|
8
10
|
*/
|
|
9
11
|
export declare function sanitizeBlockchainReference(value: unknown): string | undefined;
|
|
@@ -5,6 +5,8 @@ const SHA3_384_PREFIX = 'sha3-384:';
|
|
|
5
5
|
*
|
|
6
6
|
* Current shared rule:
|
|
7
7
|
* - `z...` multibase base58 references are already public and stable
|
|
8
|
+
* - `sha3-384:...` references are already blockchain-safe and must pass
|
|
9
|
+
* through unchanged
|
|
8
10
|
* - any other identifier-like value is SHA3-384 hashed before persistence
|
|
9
11
|
*/
|
|
10
12
|
export function sanitizeBlockchainReference(value) {
|
|
@@ -13,6 +15,8 @@ export function sanitizeBlockchainReference(value) {
|
|
|
13
15
|
return undefined;
|
|
14
16
|
if (/^z[1-9A-HJ-NP-Za-km-z]+$/.test(normalized))
|
|
15
17
|
return normalized;
|
|
18
|
+
if (/^sha3-384:[a-f0-9]{96}$/i.test(normalized))
|
|
19
|
+
return normalized;
|
|
16
20
|
return `${SHA3_384_PREFIX}${createHash('sha3-384').update(normalized, 'utf8').digest('hex')}`;
|
|
17
21
|
}
|
|
18
22
|
/**
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -54,9 +54,11 @@ export * from './fhir-validator';
|
|
|
54
54
|
export * from './family-registration-test-data';
|
|
55
55
|
export * from './individual-form-pdf';
|
|
56
56
|
export * from './individual-organization-claims';
|
|
57
|
+
export * from './inter-tenant-access-contract';
|
|
57
58
|
export * from './legal-organization-onboarding-editor';
|
|
58
59
|
export * from './organization-lifecycle';
|
|
59
60
|
export * from './organization-did-binding';
|
|
61
|
+
export * from './organization-authorization-urn';
|
|
60
62
|
export * from './individual-organization-lifecycle';
|
|
61
63
|
export * from './individual-onboarding-editor';
|
|
62
64
|
export * from './individual-onboarding-document-reference';
|
package/dist/utils/index.js
CHANGED
|
@@ -54,9 +54,11 @@ export * from './fhir-validator.js';
|
|
|
54
54
|
export * from './family-registration-test-data.js';
|
|
55
55
|
export * from './individual-form-pdf.js';
|
|
56
56
|
export * from './individual-organization-claims.js';
|
|
57
|
+
export * from './inter-tenant-access-contract.js';
|
|
57
58
|
export * from './legal-organization-onboarding-editor.js';
|
|
58
59
|
export * from './organization-lifecycle.js';
|
|
59
60
|
export * from './organization-did-binding.js';
|
|
61
|
+
export * from './organization-authorization-urn.js';
|
|
60
62
|
export * from './individual-organization-lifecycle.js';
|
|
61
63
|
export * from './individual-onboarding-editor.js';
|
|
62
64
|
export * from './individual-onboarding-document-reference.js';
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { InterTenantAccessContractClaims, InterTenantContractAuthorizationConsentCriteria, InterTenantAccessContractMatchCriteria, InterTenantAccessContractSummary } from '../models/inter-tenant-access-contract';
|
|
2
|
+
import type { VerifiableCredentialV2 } from '../models/verifiable-credential';
|
|
3
|
+
type ContractLike = Record<string, any>;
|
|
4
|
+
export declare function buildInterTenantAccessContractResource(claims: InterTenantAccessContractClaims): ContractLike;
|
|
5
|
+
export declare function buildInterTenantAccessContractCredential(input: Readonly<{
|
|
6
|
+
claims: InterTenantAccessContractClaims;
|
|
7
|
+
issuer: string;
|
|
8
|
+
validFrom: string;
|
|
9
|
+
validUntil?: string;
|
|
10
|
+
additionalCredential?: Record<string, unknown>;
|
|
11
|
+
}>): VerifiableCredentialV2;
|
|
12
|
+
export declare function summarizeInterTenantAccessContract(credential: unknown): InterTenantAccessContractSummary | undefined;
|
|
13
|
+
export declare function isInterTenantAccessContractActive(summary: InterTenantAccessContractSummary | undefined, options?: Readonly<{
|
|
14
|
+
now?: string | Date;
|
|
15
|
+
}>): boolean;
|
|
16
|
+
export declare function matchesInterTenantAccessContract(summary: InterTenantAccessContractSummary | undefined, criteria: InterTenantAccessContractMatchCriteria): boolean;
|
|
17
|
+
export declare function getMatchingInterTenantAccessContractFromVpToken(vpToken: string, criteria: InterTenantAccessContractMatchCriteria): Record<string, unknown> | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* Returns the public blockchain-safe reference that a reused consent-style
|
|
20
|
+
* authorization rule should store under `Consent.source-reference` when it
|
|
21
|
+
* delegates use of one inter-tenant contract VC to an employee/researcher.
|
|
22
|
+
*
|
|
23
|
+
* Source-of-truth order:
|
|
24
|
+
* - VC `id`
|
|
25
|
+
* - FHIR `Contract.identifier`
|
|
26
|
+
*
|
|
27
|
+
* In both cases the reference is normalized through the same blockchain-safe
|
|
28
|
+
* sanitization rule already used by consent-access assets.
|
|
29
|
+
*/
|
|
30
|
+
export declare function getInterTenantAccessContractBlockchainReference(credential: unknown): string | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Evaluates one consent-style rule reused as an organization-to-employee
|
|
33
|
+
* delegation for inter-tenant contract usage.
|
|
34
|
+
*
|
|
35
|
+
* Shared semantic contract:
|
|
36
|
+
* - `Consent.subject` = consumer organization DID
|
|
37
|
+
* - `Consent.actor-identifier` = delegated employee/researcher identifier
|
|
38
|
+
* - `Consent.action` = allowed capabilities/scopes
|
|
39
|
+
* - `Consent.purpose` = allowed purpose
|
|
40
|
+
* - `Consent.source-reference` = blockchain-safe hash/reference of the
|
|
41
|
+
* underlying contract VC
|
|
42
|
+
*/
|
|
43
|
+
export declare function matchesInterTenantContractAuthorizationConsentRule(rule: Record<string, unknown>, contractCredential: unknown, criteria: InterTenantContractAuthorizationConsentCriteria): boolean;
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
// Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
|
|
2
|
+
import { ContractCredentialTypes, W3cCredentialContexts, W3cCredentialTypes } from '../constants/verifiable-credentials.js';
|
|
3
|
+
import { ClaimConsent } from '../models/consent-rule.js';
|
|
4
|
+
import { ClaimInterTenantAccessContract } from '../models/inter-tenant-access-contract.js';
|
|
5
|
+
import { sanitizeBlockchainReference } from './evidence-blockchain-references.js';
|
|
6
|
+
import { getVpCredentials } from './vp-token.js';
|
|
7
|
+
const ACTIVE_CONTRACT_STATUSES = new Set(['executed', 'amended', 'appended']);
|
|
8
|
+
const PROVIDER_ROLE = 'provider';
|
|
9
|
+
const CONSUMER_ROLE = 'consumer';
|
|
10
|
+
const PROVIDER_CONTROLLER_ROLE = 'provider-controller';
|
|
11
|
+
const CONSUMER_CONTROLLER_ROLE = 'consumer-controller';
|
|
12
|
+
function splitCsv(value) {
|
|
13
|
+
return Array.from(new Set(String(value || '')
|
|
14
|
+
.split(',')
|
|
15
|
+
.map((item) => item.trim())
|
|
16
|
+
.filter(Boolean)));
|
|
17
|
+
}
|
|
18
|
+
function readClaimWithAliases(claims, canonicalKey, aliases = []) {
|
|
19
|
+
const direct = String(claims[canonicalKey] || '').trim();
|
|
20
|
+
if (direct)
|
|
21
|
+
return direct;
|
|
22
|
+
for (const alias of aliases) {
|
|
23
|
+
const value = String(claims[alias] || '').trim();
|
|
24
|
+
if (value)
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
function firstIdentifierValue(input) {
|
|
30
|
+
if (Array.isArray(input)) {
|
|
31
|
+
for (const candidate of input) {
|
|
32
|
+
const found = firstIdentifierValue(candidate);
|
|
33
|
+
if (found)
|
|
34
|
+
return found;
|
|
35
|
+
}
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
if (!input || typeof input !== 'object')
|
|
39
|
+
return undefined;
|
|
40
|
+
const direct = String(input.value || input.id || '').trim();
|
|
41
|
+
return direct || undefined;
|
|
42
|
+
}
|
|
43
|
+
function extractRoleTexts(input) {
|
|
44
|
+
const items = Array.isArray(input) ? input : input ? [input] : [];
|
|
45
|
+
return items
|
|
46
|
+
.flatMap((item) => {
|
|
47
|
+
if (!item || typeof item !== 'object')
|
|
48
|
+
return [];
|
|
49
|
+
const text = String(item.text || '').trim();
|
|
50
|
+
const codingItems = Array.isArray(item.coding) ? item.coding : item.coding ? [item.coding] : [];
|
|
51
|
+
const codingValues = codingItems
|
|
52
|
+
.map((coding) => String(coding?.code || coding?.display || '').trim())
|
|
53
|
+
.filter(Boolean);
|
|
54
|
+
return [text, ...codingValues].filter(Boolean);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
function extractCapabilityValues(input) {
|
|
58
|
+
const items = Array.isArray(input) ? input : input ? [input] : [];
|
|
59
|
+
return Array.from(new Set(items.flatMap((item) => {
|
|
60
|
+
if (!item || typeof item !== 'object')
|
|
61
|
+
return [];
|
|
62
|
+
const text = String(item.text || '').trim();
|
|
63
|
+
const codingItems = Array.isArray(item.coding) ? item.coding : item.coding ? [item.coding] : [];
|
|
64
|
+
const codingValues = codingItems
|
|
65
|
+
.map((coding) => String(coding?.code || coding?.display || '').trim())
|
|
66
|
+
.filter(Boolean);
|
|
67
|
+
return [text, ...codingValues].flatMap((value) => splitCsv(value));
|
|
68
|
+
})));
|
|
69
|
+
}
|
|
70
|
+
function extractOfferPartyReference(contract, expectedRole) {
|
|
71
|
+
const terms = Array.isArray(contract.term) ? contract.term : contract.term ? [contract.term] : [];
|
|
72
|
+
for (const term of terms) {
|
|
73
|
+
const parties = Array.isArray(term?.offer?.party) ? term.offer.party : term?.offer?.party ? [term.offer.party] : [];
|
|
74
|
+
for (const party of parties) {
|
|
75
|
+
const roleTexts = extractRoleTexts(party?.role).map((value) => value.toLowerCase());
|
|
76
|
+
if (!roleTexts.includes(expectedRole))
|
|
77
|
+
continue;
|
|
78
|
+
const reference = String(party?.reference?.reference || party?.reference || '').trim();
|
|
79
|
+
if (reference)
|
|
80
|
+
return reference;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
function extractSignerReference(contract, expectedRole) {
|
|
86
|
+
const signers = Array.isArray(contract.signer) ? contract.signer : contract.signer ? [contract.signer] : [];
|
|
87
|
+
for (const signer of signers) {
|
|
88
|
+
const roleTexts = extractRoleTexts(signer?.type).map((value) => value.toLowerCase());
|
|
89
|
+
if (!roleTexts.includes(expectedRole))
|
|
90
|
+
continue;
|
|
91
|
+
const reference = String(signer?.party?.reference || signer?.party?.identifier?.value || '').trim();
|
|
92
|
+
if (reference)
|
|
93
|
+
return reference;
|
|
94
|
+
}
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
function extractPurposes(contract) {
|
|
98
|
+
const terms = Array.isArray(contract.term) ? contract.term : contract.term ? [contract.term] : [];
|
|
99
|
+
return Array.from(new Set(terms.flatMap((term) => {
|
|
100
|
+
const typeItems = Array.isArray(term?.type) ? term.type : term?.type ? [term.type] : [];
|
|
101
|
+
return typeItems.flatMap((item) => {
|
|
102
|
+
if (!item || typeof item !== 'object')
|
|
103
|
+
return [];
|
|
104
|
+
const text = String(item.text || '').trim();
|
|
105
|
+
const codingItems = Array.isArray(item.coding) ? item.coding : item.coding ? [item.coding] : [];
|
|
106
|
+
const codingValues = codingItems
|
|
107
|
+
.map((coding) => String(coding?.code || coding?.display || '').trim())
|
|
108
|
+
.filter(Boolean);
|
|
109
|
+
return [text, ...codingValues].flatMap((value) => splitCsv(value));
|
|
110
|
+
});
|
|
111
|
+
})));
|
|
112
|
+
}
|
|
113
|
+
function extractCapabilities(contract) {
|
|
114
|
+
const terms = Array.isArray(contract.term) ? contract.term : contract.term ? [contract.term] : [];
|
|
115
|
+
return Array.from(new Set(terms.flatMap((term) => extractCapabilityValues(term?.offer?.securityLabel))));
|
|
116
|
+
}
|
|
117
|
+
function normalizeNow(input) {
|
|
118
|
+
if (input instanceof Date)
|
|
119
|
+
return input.getTime();
|
|
120
|
+
if (input)
|
|
121
|
+
return new Date(input).getTime();
|
|
122
|
+
return Date.now();
|
|
123
|
+
}
|
|
124
|
+
export function buildInterTenantAccessContractResource(claims) {
|
|
125
|
+
const identifier = readClaimWithAliases(claims, ClaimInterTenantAccessContract.identifier) || '';
|
|
126
|
+
const status = readClaimWithAliases(claims, ClaimInterTenantAccessContract.status) || 'executed';
|
|
127
|
+
const issued = readClaimWithAliases(claims, ClaimInterTenantAccessContract.issued) || '';
|
|
128
|
+
const appliesStart = readClaimWithAliases(claims, ClaimInterTenantAccessContract.appliesStart, ['Contract.applies.start']) || '';
|
|
129
|
+
const appliesEnd = readClaimWithAliases(claims, ClaimInterTenantAccessContract.appliesEnd, ['Contract.applies.end']) || '';
|
|
130
|
+
const providerOrganization = readClaimWithAliases(claims, ClaimInterTenantAccessContract.providerOrganization, ['Contract.term.offer.party.provider']) || '';
|
|
131
|
+
const consumerOrganization = readClaimWithAliases(claims, ClaimInterTenantAccessContract.consumerOrganization, ['Contract.term.offer.party.consumer']) || '';
|
|
132
|
+
const providerController = readClaimWithAliases(claims, ClaimInterTenantAccessContract.providerController, ['Contract.signer.provider']) || '';
|
|
133
|
+
const consumerController = readClaimWithAliases(claims, ClaimInterTenantAccessContract.consumerController, ['Contract.signer.consumer']) || '';
|
|
134
|
+
const instantiatesUri = readClaimWithAliases(claims, ClaimInterTenantAccessContract.instantiatesUri, ['Contract.instantiatesUri']) || '';
|
|
135
|
+
const capabilities = splitCsv(readClaimWithAliases(claims, ClaimInterTenantAccessContract.capability, ['Contract.term.offer.securityLabel']));
|
|
136
|
+
const purposes = splitCsv(readClaimWithAliases(claims, ClaimInterTenantAccessContract.purpose, ['Contract.term.type']));
|
|
137
|
+
return {
|
|
138
|
+
resourceType: 'Contract',
|
|
139
|
+
id: identifier || undefined,
|
|
140
|
+
identifier: identifier ? [{ value: identifier }] : undefined,
|
|
141
|
+
status,
|
|
142
|
+
issued: issued || undefined,
|
|
143
|
+
instantiatesUri: instantiatesUri || undefined,
|
|
144
|
+
applies: {
|
|
145
|
+
...(appliesStart ? { start: appliesStart } : {}),
|
|
146
|
+
...(appliesEnd ? { end: appliesEnd } : {}),
|
|
147
|
+
},
|
|
148
|
+
signer: [
|
|
149
|
+
providerController
|
|
150
|
+
? {
|
|
151
|
+
type: [{ text: PROVIDER_CONTROLLER_ROLE }],
|
|
152
|
+
party: { reference: providerController },
|
|
153
|
+
}
|
|
154
|
+
: undefined,
|
|
155
|
+
consumerController
|
|
156
|
+
? {
|
|
157
|
+
type: [{ text: CONSUMER_CONTROLLER_ROLE }],
|
|
158
|
+
party: { reference: consumerController },
|
|
159
|
+
}
|
|
160
|
+
: undefined,
|
|
161
|
+
].filter(Boolean),
|
|
162
|
+
term: [{
|
|
163
|
+
type: purposes.map((purpose) => ({ text: purpose })),
|
|
164
|
+
offer: {
|
|
165
|
+
party: [
|
|
166
|
+
providerOrganization
|
|
167
|
+
? {
|
|
168
|
+
reference: { reference: providerOrganization },
|
|
169
|
+
role: [{ text: PROVIDER_ROLE }],
|
|
170
|
+
}
|
|
171
|
+
: undefined,
|
|
172
|
+
consumerOrganization
|
|
173
|
+
? {
|
|
174
|
+
reference: { reference: consumerOrganization },
|
|
175
|
+
role: [{ text: CONSUMER_ROLE }],
|
|
176
|
+
}
|
|
177
|
+
: undefined,
|
|
178
|
+
].filter(Boolean),
|
|
179
|
+
securityLabel: capabilities.map((capability) => ({ text: capability })),
|
|
180
|
+
},
|
|
181
|
+
}],
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
export function buildInterTenantAccessContractCredential(input) {
|
|
185
|
+
return {
|
|
186
|
+
'@context': [W3cCredentialContexts.V2],
|
|
187
|
+
type: [
|
|
188
|
+
W3cCredentialTypes.VerifiableCredential,
|
|
189
|
+
ContractCredentialTypes.InterTenantAccessContractCredential,
|
|
190
|
+
],
|
|
191
|
+
issuer: String(input.issuer || '').trim(),
|
|
192
|
+
validFrom: String(input.validFrom || '').trim(),
|
|
193
|
+
...(input.validUntil ? { validUntil: String(input.validUntil).trim() } : {}),
|
|
194
|
+
credentialSubject: buildInterTenantAccessContractResource(input.claims),
|
|
195
|
+
...(input.additionalCredential || {}),
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
export function summarizeInterTenantAccessContract(credential) {
|
|
199
|
+
if (!credential || typeof credential !== 'object')
|
|
200
|
+
return undefined;
|
|
201
|
+
const typeRaw = Array.isArray(credential.type) ? credential.type : [credential.type].filter(Boolean);
|
|
202
|
+
if (!typeRaw.includes(ContractCredentialTypes.InterTenantAccessContractCredential))
|
|
203
|
+
return undefined;
|
|
204
|
+
const subject = (credential.credentialSubject || {});
|
|
205
|
+
if (String(subject.resourceType || '').trim() !== 'Contract')
|
|
206
|
+
return undefined;
|
|
207
|
+
return {
|
|
208
|
+
identifier: firstIdentifierValue(subject.identifier) || String(subject.id || '').trim() || undefined,
|
|
209
|
+
status: String(subject.status || '').trim() || undefined,
|
|
210
|
+
issued: String(subject.issued || '').trim() || undefined,
|
|
211
|
+
appliesStart: String(subject.applies?.start || '').trim() || undefined,
|
|
212
|
+
appliesEnd: String(subject.applies?.end || '').trim() || undefined,
|
|
213
|
+
providerOrganizationDid: extractOfferPartyReference(subject, PROVIDER_ROLE),
|
|
214
|
+
consumerOrganizationDid: extractOfferPartyReference(subject, CONSUMER_ROLE),
|
|
215
|
+
providerControllerDid: extractSignerReference(subject, PROVIDER_CONTROLLER_ROLE),
|
|
216
|
+
consumerControllerDid: extractSignerReference(subject, CONSUMER_CONTROLLER_ROLE),
|
|
217
|
+
capabilities: extractCapabilities(subject),
|
|
218
|
+
purposes: extractPurposes(subject),
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
export function isInterTenantAccessContractActive(summary, options = {}) {
|
|
222
|
+
if (!summary)
|
|
223
|
+
return false;
|
|
224
|
+
const status = String(summary.status || '').trim().toLowerCase();
|
|
225
|
+
if (!ACTIVE_CONTRACT_STATUSES.has(status))
|
|
226
|
+
return false;
|
|
227
|
+
const nowMs = normalizeNow(options.now);
|
|
228
|
+
const startMs = summary.appliesStart ? Date.parse(summary.appliesStart) : NaN;
|
|
229
|
+
const endMs = summary.appliesEnd ? Date.parse(summary.appliesEnd) : NaN;
|
|
230
|
+
if (!Number.isNaN(startMs) && startMs > nowMs)
|
|
231
|
+
return false;
|
|
232
|
+
if (!Number.isNaN(endMs) && endMs < nowMs)
|
|
233
|
+
return false;
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
236
|
+
export function matchesInterTenantAccessContract(summary, criteria) {
|
|
237
|
+
if (!isInterTenantAccessContractActive(summary, { now: criteria.now }))
|
|
238
|
+
return false;
|
|
239
|
+
if (String(summary?.providerOrganizationDid || '').trim() !== String(criteria.providerOrganizationDid || '').trim())
|
|
240
|
+
return false;
|
|
241
|
+
if (String(summary?.consumerOrganizationDid || '').trim() !== String(criteria.consumerOrganizationDid || '').trim())
|
|
242
|
+
return false;
|
|
243
|
+
const requiredCapabilities = Array.from(new Set((criteria.requiredCapabilities || []).map((value) => String(value || '').trim()).filter(Boolean)));
|
|
244
|
+
if (requiredCapabilities.length > 0) {
|
|
245
|
+
const contractCapabilities = new Set((summary?.capabilities || []).map((value) => String(value || '').trim()));
|
|
246
|
+
if (!requiredCapabilities.every((value) => contractCapabilities.has(value)))
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
const purpose = String(criteria.purpose || '').trim();
|
|
250
|
+
if (purpose) {
|
|
251
|
+
const contractPurposes = new Set((summary?.purposes || []).map((value) => String(value || '').trim()));
|
|
252
|
+
if (contractPurposes.size > 0 && !contractPurposes.has(purpose))
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
return true;
|
|
256
|
+
}
|
|
257
|
+
export function getMatchingInterTenantAccessContractFromVpToken(vpToken, criteria) {
|
|
258
|
+
const credentials = getVpCredentials(vpToken);
|
|
259
|
+
return credentials.find((credential) => matchesInterTenantAccessContract(summarizeInterTenantAccessContract(credential), criteria));
|
|
260
|
+
}
|
|
261
|
+
function splitClaimCsv(value) {
|
|
262
|
+
return Array.from(new Set(String(value || '')
|
|
263
|
+
.split(',')
|
|
264
|
+
.map((item) => item.trim())
|
|
265
|
+
.filter(Boolean)));
|
|
266
|
+
}
|
|
267
|
+
function isConsentLikeRuleActive(rule, now) {
|
|
268
|
+
const nowMs = normalizeNow(now);
|
|
269
|
+
const startMs = Date.parse(String(rule[ClaimConsent.periodStart] || '').trim());
|
|
270
|
+
const endMs = Date.parse(String(rule[ClaimConsent.periodEnd] || '').trim());
|
|
271
|
+
if (!Number.isNaN(startMs) && startMs > nowMs)
|
|
272
|
+
return false;
|
|
273
|
+
if (!Number.isNaN(endMs) && endMs < nowMs)
|
|
274
|
+
return false;
|
|
275
|
+
return true;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Returns the public blockchain-safe reference that a reused consent-style
|
|
279
|
+
* authorization rule should store under `Consent.source-reference` when it
|
|
280
|
+
* delegates use of one inter-tenant contract VC to an employee/researcher.
|
|
281
|
+
*
|
|
282
|
+
* Source-of-truth order:
|
|
283
|
+
* - VC `id`
|
|
284
|
+
* - FHIR `Contract.identifier`
|
|
285
|
+
*
|
|
286
|
+
* In both cases the reference is normalized through the same blockchain-safe
|
|
287
|
+
* sanitization rule already used by consent-access assets.
|
|
288
|
+
*/
|
|
289
|
+
export function getInterTenantAccessContractBlockchainReference(credential) {
|
|
290
|
+
if (!credential || typeof credential !== 'object')
|
|
291
|
+
return undefined;
|
|
292
|
+
const vcId = sanitizeBlockchainReference(credential.id);
|
|
293
|
+
if (vcId)
|
|
294
|
+
return vcId;
|
|
295
|
+
const summary = summarizeInterTenantAccessContract(credential);
|
|
296
|
+
return sanitizeBlockchainReference(summary?.identifier);
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Evaluates one consent-style rule reused as an organization-to-employee
|
|
300
|
+
* delegation for inter-tenant contract usage.
|
|
301
|
+
*
|
|
302
|
+
* Shared semantic contract:
|
|
303
|
+
* - `Consent.subject` = consumer organization DID
|
|
304
|
+
* - `Consent.actor-identifier` = delegated employee/researcher identifier
|
|
305
|
+
* - `Consent.action` = allowed capabilities/scopes
|
|
306
|
+
* - `Consent.purpose` = allowed purpose
|
|
307
|
+
* - `Consent.source-reference` = blockchain-safe hash/reference of the
|
|
308
|
+
* underlying contract VC
|
|
309
|
+
*/
|
|
310
|
+
export function matchesInterTenantContractAuthorizationConsentRule(rule, contractCredential, criteria) {
|
|
311
|
+
const summary = summarizeInterTenantAccessContract(contractCredential);
|
|
312
|
+
if (!isInterTenantAccessContractActive(summary, { now: criteria.now })) {
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
315
|
+
if (String(rule[ClaimConsent.decision] || '').trim() !== 'permit')
|
|
316
|
+
return false;
|
|
317
|
+
if (!isConsentLikeRuleActive(rule, criteria.now))
|
|
318
|
+
return false;
|
|
319
|
+
if (String(rule[ClaimConsent.subject] || '').trim() !== String(criteria.consumerOrganizationDid || '').trim())
|
|
320
|
+
return false;
|
|
321
|
+
if (String(rule[ClaimConsent.actorIdentifier] || '').trim() !== String(criteria.actorIdentifier || '').trim())
|
|
322
|
+
return false;
|
|
323
|
+
const expectedReference = getInterTenantAccessContractBlockchainReference(contractCredential);
|
|
324
|
+
const ruleReference = String(rule[ClaimConsent.sourceReference]
|
|
325
|
+
|| rule[ClaimConsent.eventBasedOn]
|
|
326
|
+
|| '').trim();
|
|
327
|
+
if (!expectedReference || ruleReference !== expectedReference)
|
|
328
|
+
return false;
|
|
329
|
+
const purpose = String(criteria.purpose || '').trim();
|
|
330
|
+
if (purpose) {
|
|
331
|
+
const rulePurpose = String(rule[ClaimConsent.purpose] || '').trim();
|
|
332
|
+
if (rulePurpose && rulePurpose !== purpose)
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
const actorRole = String(criteria.actorRole || '').trim();
|
|
336
|
+
if (actorRole) {
|
|
337
|
+
const allowedRoles = splitClaimCsv(rule[ClaimConsent.actorRole]);
|
|
338
|
+
if (allowedRoles.length > 0 && !allowedRoles.includes(actorRole))
|
|
339
|
+
return false;
|
|
340
|
+
}
|
|
341
|
+
const requiredCapabilities = Array.from(new Set((criteria.requiredCapabilities || []).map((value) => String(value || '').trim()).filter(Boolean)));
|
|
342
|
+
if (requiredCapabilities.length > 0) {
|
|
343
|
+
const allowedCapabilities = new Set(splitClaimCsv(rule[ClaimConsent.action]));
|
|
344
|
+
if (!requiredCapabilities.every((value) => allowedCapabilities.has(value) || allowedCapabilities.has('*')))
|
|
345
|
+
return false;
|
|
346
|
+
}
|
|
347
|
+
if (requiredCapabilities.length > 0) {
|
|
348
|
+
const contractCapabilities = new Set((summary?.capabilities || []).map((value) => String(value || '').trim()));
|
|
349
|
+
if (!requiredCapabilities.every((value) => contractCapabilities.has(value)))
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
if (purpose) {
|
|
353
|
+
const contractPurposes = new Set((summary?.purposes || []).map((value) => String(value || '').trim()));
|
|
354
|
+
if (contractPurposes.size > 0 && !contractPurposes.has(purpose))
|
|
355
|
+
return false;
|
|
356
|
+
}
|
|
357
|
+
return true;
|
|
358
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { ClaimsRecord } from '../models/resource-document';
|
|
2
|
+
export type OrganizationAuthorizationUrnInput = Readonly<{
|
|
3
|
+
identifierType: string;
|
|
4
|
+
identifierValue: string;
|
|
5
|
+
}>;
|
|
6
|
+
export type MemberAuthorizationUrnInput = Readonly<{
|
|
7
|
+
organizationUrn?: string;
|
|
8
|
+
identifierType?: string;
|
|
9
|
+
identifierValue?: string;
|
|
10
|
+
memberId: string;
|
|
11
|
+
/** Optional compatibility input. Role must be persisted separately, not inside the canonical member URN. */
|
|
12
|
+
roleCode?: string;
|
|
13
|
+
}>;
|
|
14
|
+
/**
|
|
15
|
+
* Canonical organization authorization identifier used by ledger, consent-like
|
|
16
|
+
* rules, and inter-tenant authorization persistence.
|
|
17
|
+
*
|
|
18
|
+
* Format:
|
|
19
|
+
* - `urn:org:<identifierType-lowercase>:<identifierValue-as-canonical-input>`
|
|
20
|
+
*
|
|
21
|
+
* Examples:
|
|
22
|
+
* - `urn:org:tax:VATES-B12345678`
|
|
23
|
+
* - `urn:org:tax:acme-id`
|
|
24
|
+
*/
|
|
25
|
+
export declare function buildOrganizationAuthorizationUrn(input: OrganizationAuthorizationUrnInput): string;
|
|
26
|
+
/**
|
|
27
|
+
* Accepts either the legacy `TYPE|VALUE` input or an already-canonical
|
|
28
|
+
* `urn:org:...` identifier and always returns the canonical URN form.
|
|
29
|
+
*/
|
|
30
|
+
export declare function normalizeOrganizationAuthorizationUrn(input: string): string;
|
|
31
|
+
export declare function buildOrganizationAuthorizationUrnFromClaims(claims?: ClaimsRecord): string;
|
|
32
|
+
/**
|
|
33
|
+
* Canonical member authorization identifier rooted in one canonical
|
|
34
|
+
* `urn:org:...`.
|
|
35
|
+
*
|
|
36
|
+
* Format:
|
|
37
|
+
* - `urn:org:<type>:<value>:member:<memberId>`
|
|
38
|
+
*
|
|
39
|
+
* Important rule:
|
|
40
|
+
* - role must be stored as a separate claim, not inside the canonical member
|
|
41
|
+
* identifier
|
|
42
|
+
*/
|
|
43
|
+
export declare function buildMemberAuthorizationUrn(input: MemberAuthorizationUrnInput): string;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
|
|
2
|
+
import { ClaimsOrganizationSchemaorg } from '../constants/schemaorg.js';
|
|
3
|
+
const ORGANIZATION_URN_PREFIX = 'urn:org:';
|
|
4
|
+
function normalizeTypeSegment(identifierType) {
|
|
5
|
+
return String(identifierType || '').trim().toLowerCase();
|
|
6
|
+
}
|
|
7
|
+
function normalizeValueSegment(identifierValue) {
|
|
8
|
+
return String(identifierValue || '').trim();
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Canonical organization authorization identifier used by ledger, consent-like
|
|
12
|
+
* rules, and inter-tenant authorization persistence.
|
|
13
|
+
*
|
|
14
|
+
* Format:
|
|
15
|
+
* - `urn:org:<identifierType-lowercase>:<identifierValue-as-canonical-input>`
|
|
16
|
+
*
|
|
17
|
+
* Examples:
|
|
18
|
+
* - `urn:org:tax:VATES-B12345678`
|
|
19
|
+
* - `urn:org:tax:acme-id`
|
|
20
|
+
*/
|
|
21
|
+
export function buildOrganizationAuthorizationUrn(input) {
|
|
22
|
+
const identifierType = normalizeTypeSegment(input.identifierType);
|
|
23
|
+
const identifierValue = normalizeValueSegment(input.identifierValue);
|
|
24
|
+
if (!identifierType || !identifierValue) {
|
|
25
|
+
throw new Error('Organization authorization URN requires identifierType and identifierValue.');
|
|
26
|
+
}
|
|
27
|
+
return `${ORGANIZATION_URN_PREFIX}${identifierType}:${identifierValue}`;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Accepts either the legacy `TYPE|VALUE` input or an already-canonical
|
|
31
|
+
* `urn:org:...` identifier and always returns the canonical URN form.
|
|
32
|
+
*/
|
|
33
|
+
export function normalizeOrganizationAuthorizationUrn(input) {
|
|
34
|
+
const normalized = String(input || '').trim();
|
|
35
|
+
if (!normalized) {
|
|
36
|
+
throw new Error('Organization authorization identifier cannot be empty.');
|
|
37
|
+
}
|
|
38
|
+
if (normalized.toLowerCase().startsWith(ORGANIZATION_URN_PREFIX)) {
|
|
39
|
+
const parts = normalized.split(':');
|
|
40
|
+
const identifierType = String(parts[2] || '').trim();
|
|
41
|
+
const value = parts.slice(3).join(':').trim();
|
|
42
|
+
return buildOrganizationAuthorizationUrn({
|
|
43
|
+
identifierType,
|
|
44
|
+
identifierValue: value,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
const legacyMatch = normalized.match(/^([^|:]+)\|(.+)$/);
|
|
48
|
+
if (legacyMatch) {
|
|
49
|
+
return buildOrganizationAuthorizationUrn({
|
|
50
|
+
identifierType: legacyMatch[1],
|
|
51
|
+
identifierValue: legacyMatch[2],
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
throw new Error(`Unsupported organization authorization identifier format: ${normalized}`);
|
|
55
|
+
}
|
|
56
|
+
export function buildOrganizationAuthorizationUrnFromClaims(claims) {
|
|
57
|
+
const identifierType = String(claims?.[ClaimsOrganizationSchemaorg.identifierType] || '').trim();
|
|
58
|
+
const identifierValue = String(claims?.[ClaimsOrganizationSchemaorg.identifierValue] || '').trim();
|
|
59
|
+
return buildOrganizationAuthorizationUrn({
|
|
60
|
+
identifierType,
|
|
61
|
+
identifierValue,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Canonical member authorization identifier rooted in one canonical
|
|
66
|
+
* `urn:org:...`.
|
|
67
|
+
*
|
|
68
|
+
* Format:
|
|
69
|
+
* - `urn:org:<type>:<value>:member:<memberId>`
|
|
70
|
+
*
|
|
71
|
+
* Important rule:
|
|
72
|
+
* - role must be stored as a separate claim, not inside the canonical member
|
|
73
|
+
* identifier
|
|
74
|
+
*/
|
|
75
|
+
export function buildMemberAuthorizationUrn(input) {
|
|
76
|
+
const organizationUrn = input.organizationUrn
|
|
77
|
+
? normalizeOrganizationAuthorizationUrn(input.organizationUrn)
|
|
78
|
+
: buildOrganizationAuthorizationUrn({
|
|
79
|
+
identifierType: String(input.identifierType || '').trim(),
|
|
80
|
+
identifierValue: String(input.identifierValue || '').trim(),
|
|
81
|
+
});
|
|
82
|
+
const memberId = String(input.memberId || '').trim();
|
|
83
|
+
if (!memberId) {
|
|
84
|
+
throw new Error('Member authorization URN requires memberId.');
|
|
85
|
+
}
|
|
86
|
+
return `${organizationUrn}:member:${memberId}`;
|
|
87
|
+
}
|