gdc-common-utils-ts 2.7.8 → 2.8.1
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/identity-auth.d.ts +1 -0
- package/dist/constants/identity-auth.js +1 -0
- package/dist/utils/fhir-ips-creator-identity.d.ts +89 -0
- package/dist/utils/fhir-ips-creator-identity.js +133 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/package.json +1 -1
|
@@ -65,6 +65,7 @@ export declare const IdentityDcrMetadataFields: Readonly<{
|
|
|
65
65
|
readonly ClientName: "client_name";
|
|
66
66
|
readonly ActorDid: "actor_did";
|
|
67
67
|
readonly ProfileDid: "profile_did";
|
|
68
|
+
readonly ClinicalCreatorBinding: "clinical_creator_binding";
|
|
68
69
|
}>;
|
|
69
70
|
/** Device metadata keys nested below `ext_device_info`. */
|
|
70
71
|
export declare const IdentityDeviceInfoFields: Readonly<{
|
|
@@ -65,6 +65,7 @@ export const IdentityDcrMetadataFields = Object.freeze({
|
|
|
65
65
|
ClientName: 'client_name',
|
|
66
66
|
ActorDid: 'actor_did',
|
|
67
67
|
ProfileDid: 'profile_did',
|
|
68
|
+
ClinicalCreatorBinding: 'clinical_creator_binding',
|
|
68
69
|
});
|
|
69
70
|
/** Device metadata keys nested below `ext_device_info`. */
|
|
70
71
|
export const IdentityDeviceInfoFields = Object.freeze({
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
export declare const FhirIpsCreatorKinds: Readonly<{
|
|
2
|
+
readonly IndividualSubject: "individual-subject";
|
|
3
|
+
readonly IndividualMember: "individual-member";
|
|
4
|
+
readonly Professional: "professional";
|
|
5
|
+
}>;
|
|
6
|
+
export type FhirIpsCreatorKind = typeof FhirIpsCreatorKinds[keyof typeof FhirIpsCreatorKinds];
|
|
7
|
+
export type FhirIpsCreatorBundleEntry = Readonly<{
|
|
8
|
+
fullUrl: string;
|
|
9
|
+
resource: Readonly<Record<string, unknown>>;
|
|
10
|
+
}>;
|
|
11
|
+
type FhirIpsCreatorBase = Readonly<{
|
|
12
|
+
/** Stable imported or locally generated actor identifier. */
|
|
13
|
+
actorIdentifier: string;
|
|
14
|
+
/** Stable identifier referenced by Composition.author for this role/relationship. */
|
|
15
|
+
authorIdentifier: string;
|
|
16
|
+
}>;
|
|
17
|
+
export type FhirIpsCreatorAuthorInput = (FhirIpsCreatorBase & Readonly<{
|
|
18
|
+
kind: typeof FhirIpsCreatorKinds.IndividualSubject;
|
|
19
|
+
subjectReference: string;
|
|
20
|
+
}>) | (FhirIpsCreatorBase & Readonly<{
|
|
21
|
+
kind: typeof FhirIpsCreatorKinds.IndividualMember;
|
|
22
|
+
subjectReference: string;
|
|
23
|
+
role: string;
|
|
24
|
+
}>) | (FhirIpsCreatorBase & Readonly<{
|
|
25
|
+
kind: typeof FhirIpsCreatorKinds.Professional;
|
|
26
|
+
organizationReference: string;
|
|
27
|
+
role: string;
|
|
28
|
+
}>);
|
|
29
|
+
export type FhirIpsCreatorAuthor = Readonly<{
|
|
30
|
+
authorReference: string;
|
|
31
|
+
entries: readonly FhirIpsCreatorBundleEntry[];
|
|
32
|
+
}>;
|
|
33
|
+
/**
|
|
34
|
+
* Builds the ordinary FHIR R4 resources referenced by an IPS
|
|
35
|
+
* `Composition.author`.
|
|
36
|
+
*
|
|
37
|
+
* The helper preserves imported `urn:uuid` identifiers. It never derives an
|
|
38
|
+
* author from email, telephone, an OIDC subject, DCR client id or key id.
|
|
39
|
+
* ONESELF references the existing Patient/subject entry. A professional
|
|
40
|
+
* references PractitionerRole while retaining the underlying Practitioner.
|
|
41
|
+
* An individual member references RelatedPerson.
|
|
42
|
+
*/
|
|
43
|
+
export declare function buildFhirIpsCreatorAuthor(input: FhirIpsCreatorAuthorInput): FhirIpsCreatorAuthor;
|
|
44
|
+
type ClinicalCreatorChannelAliases = Readonly<{
|
|
45
|
+
/** Operational DIDs returned by authenticated profiles; never the stable FHIR identifier. */
|
|
46
|
+
actorDids?: readonly string[];
|
|
47
|
+
/** Verified contact hashes used only to find this binding. */
|
|
48
|
+
verifiedContactIdentifiers?: readonly string[];
|
|
49
|
+
/** Registered OAuth/DCR clients used only to find this binding. */
|
|
50
|
+
dcrClientIds?: readonly string[];
|
|
51
|
+
/** Registered signing keys used only to find this binding. */
|
|
52
|
+
keyIds?: readonly string[];
|
|
53
|
+
}>;
|
|
54
|
+
/**
|
|
55
|
+
* Durable clinical-creator identity stored behind the authenticated profile.
|
|
56
|
+
* `actorIdentifier` identifies the person/member. `authorIdentifier` identifies
|
|
57
|
+
* the concrete role or relationship assignment used as the FHIR author.
|
|
58
|
+
* Contacts, DCR clients, keys and operational DIDs are aliases only.
|
|
59
|
+
*/
|
|
60
|
+
export type ClinicalCreatorBinding = ClinicalCreatorChannelAliases & Readonly<{
|
|
61
|
+
kind: FhirIpsCreatorKind;
|
|
62
|
+
actorIdentifier: string;
|
|
63
|
+
authorIdentifier: string;
|
|
64
|
+
/** Legal organization DID/URN or individual subject urn:uuid owning the assignment. */
|
|
65
|
+
ownerIdentifier: string;
|
|
66
|
+
/** Canonical governed ISCO/HL7 role claim; ONESELF is the subject relationship. */
|
|
67
|
+
role: string;
|
|
68
|
+
}>;
|
|
69
|
+
export type AuthenticatedClinicalCreatorEvidence = Readonly<{
|
|
70
|
+
actorDid?: string;
|
|
71
|
+
verifiedContactIdentifiers?: readonly string[];
|
|
72
|
+
dcrClientId?: string;
|
|
73
|
+
keyId?: string;
|
|
74
|
+
}>;
|
|
75
|
+
export type ClinicalCreatorPermissionActor = Readonly<{
|
|
76
|
+
/** UUID of the exact role/relationship assignment, never a contact address. */
|
|
77
|
+
actorIdentifier: string;
|
|
78
|
+
/** Governed role retained separately from the assignment UUID. */
|
|
79
|
+
actorRole: string;
|
|
80
|
+
}>;
|
|
81
|
+
/** Builds the stable actor-and-role pair consumed by Consent permission rules. */
|
|
82
|
+
export declare function buildClinicalCreatorPermissionActor(binding: ClinicalCreatorBinding): ClinicalCreatorPermissionActor;
|
|
83
|
+
/**
|
|
84
|
+
* Resolves already-authenticated channel evidence to one stable clinical
|
|
85
|
+
* creator binding. Callers must verify the OIDC contact, DCR client or signing
|
|
86
|
+
* key before invoking this deterministic matcher.
|
|
87
|
+
*/
|
|
88
|
+
export declare function resolveClinicalCreatorBinding(bindings: readonly ClinicalCreatorBinding[], evidence: AuthenticatedClinicalCreatorEvidence): ClinicalCreatorBinding | undefined;
|
|
89
|
+
export {};
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
|
|
2
|
+
import { getHealthcareRoleByClaim } from '../constants/healthcare.js';
|
|
3
|
+
import { isStableActorIdentifier } from './actor-identifier.js';
|
|
4
|
+
import { normalizeUuid } from './normalize-uuid.js';
|
|
5
|
+
export const FhirIpsCreatorKinds = Object.freeze({
|
|
6
|
+
IndividualSubject: 'individual-subject',
|
|
7
|
+
IndividualMember: 'individual-member',
|
|
8
|
+
Professional: 'professional',
|
|
9
|
+
});
|
|
10
|
+
/**
|
|
11
|
+
* Builds the ordinary FHIR R4 resources referenced by an IPS
|
|
12
|
+
* `Composition.author`.
|
|
13
|
+
*
|
|
14
|
+
* The helper preserves imported `urn:uuid` identifiers. It never derives an
|
|
15
|
+
* author from email, telephone, an OIDC subject, DCR client id or key id.
|
|
16
|
+
* ONESELF references the existing Patient/subject entry. A professional
|
|
17
|
+
* references PractitionerRole while retaining the underlying Practitioner.
|
|
18
|
+
* An individual member references RelatedPerson.
|
|
19
|
+
*/
|
|
20
|
+
export function buildFhirIpsCreatorAuthor(input) {
|
|
21
|
+
const actorIdentifier = requireUuidUrn(input.actorIdentifier, 'actorIdentifier');
|
|
22
|
+
const authorIdentifier = requireUuidUrn(input.authorIdentifier, 'authorIdentifier');
|
|
23
|
+
if (input.kind === FhirIpsCreatorKinds.IndividualSubject) {
|
|
24
|
+
const subjectReference = requireUuidUrn(input.subjectReference, 'subjectReference');
|
|
25
|
+
if (actorIdentifier !== subjectReference || authorIdentifier !== subjectReference) {
|
|
26
|
+
throw new TypeError('ONESELF author must reference the individual subject identifier.');
|
|
27
|
+
}
|
|
28
|
+
return { authorReference: subjectReference, entries: [] };
|
|
29
|
+
}
|
|
30
|
+
const role = requireRoleCoding(input.role);
|
|
31
|
+
if (input.kind === FhirIpsCreatorKinds.IndividualMember) {
|
|
32
|
+
const subjectReference = requireUuidUrn(input.subjectReference, 'subjectReference');
|
|
33
|
+
return {
|
|
34
|
+
authorReference: authorIdentifier,
|
|
35
|
+
entries: [{
|
|
36
|
+
fullUrl: authorIdentifier,
|
|
37
|
+
resource: {
|
|
38
|
+
resourceType: 'RelatedPerson',
|
|
39
|
+
id: uuidValue(authorIdentifier),
|
|
40
|
+
identifier: [{ value: actorIdentifier }],
|
|
41
|
+
patient: { reference: subjectReference },
|
|
42
|
+
relationship: [{ coding: [role] }],
|
|
43
|
+
},
|
|
44
|
+
}],
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const organizationReference = requireReference(input.organizationReference, 'organizationReference');
|
|
48
|
+
return {
|
|
49
|
+
authorReference: authorIdentifier,
|
|
50
|
+
entries: [
|
|
51
|
+
{
|
|
52
|
+
fullUrl: actorIdentifier,
|
|
53
|
+
resource: {
|
|
54
|
+
resourceType: 'Practitioner',
|
|
55
|
+
id: uuidValue(actorIdentifier),
|
|
56
|
+
identifier: [{ value: actorIdentifier }],
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
fullUrl: authorIdentifier,
|
|
61
|
+
resource: {
|
|
62
|
+
resourceType: 'PractitionerRole',
|
|
63
|
+
id: uuidValue(authorIdentifier),
|
|
64
|
+
identifier: [{ value: authorIdentifier }],
|
|
65
|
+
practitioner: { reference: actorIdentifier },
|
|
66
|
+
organization: { reference: organizationReference },
|
|
67
|
+
code: [{ coding: [role] }],
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/** Builds the stable actor-and-role pair consumed by Consent permission rules. */
|
|
74
|
+
export function buildClinicalCreatorPermissionActor(binding) {
|
|
75
|
+
requireUuidUrn(binding.actorIdentifier, 'binding.actorIdentifier');
|
|
76
|
+
const actorIdentifier = requireUuidUrn(binding.authorIdentifier, 'binding.authorIdentifier');
|
|
77
|
+
requireReference(binding.ownerIdentifier, 'binding.ownerIdentifier');
|
|
78
|
+
requireRoleCoding(binding.role);
|
|
79
|
+
return { actorIdentifier, actorRole: binding.role };
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Resolves already-authenticated channel evidence to one stable clinical
|
|
83
|
+
* creator binding. Callers must verify the OIDC contact, DCR client or signing
|
|
84
|
+
* key before invoking this deterministic matcher.
|
|
85
|
+
*/
|
|
86
|
+
export function resolveClinicalCreatorBinding(bindings, evidence) {
|
|
87
|
+
const evidenceContacts = new Set((evidence.verifiedContactIdentifiers || [])
|
|
88
|
+
.map((value) => String(value || '').trim())
|
|
89
|
+
.filter(isStableActorIdentifier));
|
|
90
|
+
const actorDid = String(evidence.actorDid || '').trim();
|
|
91
|
+
const dcrClientId = String(evidence.dcrClientId || '').trim();
|
|
92
|
+
const keyId = String(evidence.keyId || '').trim();
|
|
93
|
+
const matches = bindings.filter((binding) => {
|
|
94
|
+
requireUuidUrn(binding.actorIdentifier, 'binding.actorIdentifier');
|
|
95
|
+
requireUuidUrn(binding.authorIdentifier, 'binding.authorIdentifier');
|
|
96
|
+
requireReference(binding.ownerIdentifier, 'binding.ownerIdentifier');
|
|
97
|
+
requireRoleCoding(binding.role);
|
|
98
|
+
return Boolean((actorDid && binding.actorDids?.includes(actorDid))
|
|
99
|
+
|| (dcrClientId && binding.dcrClientIds?.includes(dcrClientId))
|
|
100
|
+
|| (keyId && binding.keyIds?.includes(keyId))
|
|
101
|
+
|| binding.verifiedContactIdentifiers?.some((identifier) => evidenceContacts.has(identifier)));
|
|
102
|
+
});
|
|
103
|
+
if (matches.length > 1) {
|
|
104
|
+
throw new Error('Authenticated channel resolves more than one clinical creator binding.');
|
|
105
|
+
}
|
|
106
|
+
return matches[0];
|
|
107
|
+
}
|
|
108
|
+
function requireUuidUrn(value, label) {
|
|
109
|
+
const normalized = String(value || '').trim().toLowerCase();
|
|
110
|
+
if (!normalized.startsWith('urn:uuid:') || !normalizeUuid(normalized)) {
|
|
111
|
+
throw new TypeError(`${label} must be a canonical urn:uuid identifier.`);
|
|
112
|
+
}
|
|
113
|
+
return normalized;
|
|
114
|
+
}
|
|
115
|
+
function uuidValue(identifier) {
|
|
116
|
+
return identifier.slice('urn:uuid:'.length);
|
|
117
|
+
}
|
|
118
|
+
function requireReference(value, label) {
|
|
119
|
+
const normalized = String(value || '').trim();
|
|
120
|
+
if (!normalized)
|
|
121
|
+
throw new TypeError(`${label} is required.`);
|
|
122
|
+
return normalized;
|
|
123
|
+
}
|
|
124
|
+
function requireRoleCoding(roleClaim) {
|
|
125
|
+
const descriptor = getHealthcareRoleByClaim(String(roleClaim || '').trim());
|
|
126
|
+
if (!descriptor)
|
|
127
|
+
throw new TypeError('role must be a canonical governed healthcare role.');
|
|
128
|
+
return {
|
|
129
|
+
system: descriptor.codingSystem,
|
|
130
|
+
code: descriptor.code,
|
|
131
|
+
...(descriptor.titleEn ? { display: descriptor.titleEn } : {}),
|
|
132
|
+
};
|
|
133
|
+
}
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -32,6 +32,7 @@ export * from './discovery-normalization';
|
|
|
32
32
|
export * from './format-converter';
|
|
33
33
|
export * from './fhir-cid';
|
|
34
34
|
export * from './fhir-search';
|
|
35
|
+
export * from './fhir-ips-creator-identity';
|
|
35
36
|
export * from './family-organization-summary';
|
|
36
37
|
export * from './gw-core-path';
|
|
37
38
|
export * from './gw-core-commercial-contract';
|
package/dist/utils/index.js
CHANGED
|
@@ -32,6 +32,7 @@ export * from './discovery-normalization.js';
|
|
|
32
32
|
export * from './format-converter.js';
|
|
33
33
|
export * from './fhir-cid.js';
|
|
34
34
|
export * from './fhir-search.js';
|
|
35
|
+
export * from './fhir-ips-creator-identity.js';
|
|
35
36
|
export * from './family-organization-summary.js';
|
|
36
37
|
export * from './gw-core-path.js';
|
|
37
38
|
export * from './gw-core-commercial-contract.js';
|