gdc-common-utils-ts 2.3.20 → 2.3.22

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.
@@ -2,6 +2,7 @@ export type ParsedActor = {
2
2
  /**
3
3
  * The token subject / authenticated actor identifier (as provided in the token request).
4
4
  * Examples:
5
+ * - did:web:api.acme.org:member:z6MksExampleHashedMemberId:ISCO-08|2211
5
6
  * - did:web:api.acme.org:employee:z6MksExampleHashedEmployeeId:ISCO-08|2211
6
7
  * - did:web:api.acme.org:employee:z6MksExampleHashedEmployeeId:ISCO-08|2211:<device-uuid>
7
8
  * - did:web:api.acme.org:employee:doctor1@acme.org:ISCO-08|2211
@@ -10,10 +11,15 @@ export type ParsedActor = {
10
11
  * - did:web:api.acme.org:family:<id>:v3-RoleCode|CHILD:<device-uuid>
11
12
  */
12
13
  sub: string;
13
- /** The actor identifier (employee hashed-id/email token, familyId, or raw email). */
14
+ /** The terminal member identifier (hashed email/phone, family id, or raw email). */
14
15
  identifier?: string;
15
16
  /** The employee role code if present (e.g. "ISCO-08|2211"). */
16
17
  role?: string;
18
+ /**
19
+ * Membership domain inferred from the terminal role contract, not from a
20
+ * provider-specific `member`/`employee` path label.
21
+ */
22
+ memberKind?: 'organization' | 'individual';
17
23
  /** The base organization did:web if `sub` is did:web (e.g. "did:web:api.acme.org"). */
18
24
  organization?: string;
19
25
  };
@@ -11,20 +11,40 @@ export function parseActorFromSub(sub) {
11
11
  const host = after.split(':')[0];
12
12
  if (host)
13
13
  parsed.organization = `did:web:${host}`;
14
- // Extract email and role from endpoint-style DID shapes:
15
- // did:web:<host>:(employee|family):<id>:<roleSystem>|<roleCode>[:<uuid>]
14
+ // Member vocabulary is provider-specific (`member`, `employee`, `family`,
15
+ // `individual-member`, ...). The interoperable identity tuple is the
16
+ // terminal `<identifier>:<role-system>|<role-code>` pair, optionally
17
+ // followed by a device id. Do not make consent matching depend on a path
18
+ // label chosen by a hosted or external DID provider.
16
19
  const parts = after.split(':');
17
- const employeeIdx = parts.indexOf('employee');
18
- const familyIdx = parts.indexOf('family');
19
- const idIdx = employeeIdx >= 0 ? employeeIdx + 1 : familyIdx >= 0 ? familyIdx + 1 : -1;
20
+ const roleIdx = parts.findIndex((part) => {
21
+ try {
22
+ return decodeURIComponent(part).includes('|');
23
+ }
24
+ catch {
25
+ return part.includes('|');
26
+ }
27
+ });
28
+ const hasRoleLabel = roleIdx > 0 && parts[roleIdx - 1].toLowerCase() === 'role';
29
+ const idIdx = roleIdx > 0 ? roleIdx - (hasRoleLabel ? 2 : 1) : -1;
20
30
  const identifier = idIdx >= 0 ? parts[idIdx] : undefined;
21
31
  if (identifier) {
22
32
  parsed.identifier = identifier.includes('@') ? identifier.toLowerCase() : identifier;
23
33
  }
24
- if (idIdx >= 0 && parts.length > idIdx + 1) {
25
- const roleCandidate = parts[idIdx + 1];
26
- if (roleCandidate && roleCandidate.includes('|'))
27
- parsed.role = roleCandidate;
34
+ if (roleIdx >= 0) {
35
+ const encodedRole = parts[roleIdx];
36
+ try {
37
+ parsed.role = decodeURIComponent(encodedRole);
38
+ }
39
+ catch {
40
+ parsed.role = encodedRole;
41
+ }
42
+ const normalizedRole = String(parsed.role || '').trim().toLowerCase();
43
+ const pathLabels = parts.slice(1, Math.max(1, idIdx)).map((part) => part.toLowerCase());
44
+ parsed.memberKind = normalizedRole.startsWith('v3-rolecode|')
45
+ || pathLabels.some((part) => part === 'family' || part === 'related-person' || part === 'individual-member')
46
+ ? 'individual'
47
+ : 'organization';
28
48
  }
29
49
  return parsed;
30
50
  }
@@ -157,8 +157,13 @@ export declare function buildBundleSearchReferenceUrl(input: Readonly<{
157
157
  */
158
158
  export declare function createSummaryOperationRequestParameters(subjectIdOrInput: string | CreateSummaryOperationParametersInput, filterSections?: string[]): ReadonlyArray<ParameterData>;
159
159
  /**
160
- * Flattens semantic summary-operation parameters to the relative `Bundle/_search`
161
- * path currently stored in `Communication.content-reference`.
160
+ * Compatibility escape hatch that flattens semantic summary parameters into a
161
+ * relative `Bundle/_search` operation reference.
162
+ *
163
+ * New application/BFF code uses the actor facade `requestClinicalSummary(...)`.
164
+ * The facade submits a `Communication`; GW interprets its content reference
165
+ * internally. Callers must not turn this returned value into a second direct
166
+ * HTTP request to the subject index.
162
167
  */
163
168
  export declare function createSummaryOperationRequestReferencePath(parameters: ReadonlyArray<ParameterData>): string;
164
169
  /**
@@ -246,8 +246,13 @@ export function createSummaryOperationRequestParameters(subjectIdOrInput, filter
246
246
  ];
247
247
  }
248
248
  /**
249
- * Flattens semantic summary-operation parameters to the relative `Bundle/_search`
250
- * path currently stored in `Communication.content-reference`.
249
+ * Compatibility escape hatch that flattens semantic summary parameters into a
250
+ * relative `Bundle/_search` operation reference.
251
+ *
252
+ * New application/BFF code uses the actor facade `requestClinicalSummary(...)`.
253
+ * The facade submits a `Communication`; GW interprets its content reference
254
+ * internally. Callers must not turn this returned value into a second direct
255
+ * HTTP request to the subject index.
251
256
  */
252
257
  export function createSummaryOperationRequestReferencePath(parameters) {
253
258
  const params = ['type=document'];
@@ -54,6 +54,10 @@ export type BuildConsentClaimsSimpleInput = SubjectIdentifierInput & {
54
54
  consentIdentifier?: string;
55
55
  consentDate?: string;
56
56
  decision?: 'permit' | 'deny';
57
+ /** Stable identifier/thread of the permission request that caused this decision. */
58
+ eventBasedOn?: string;
59
+ /** Canonical Communication reference for the permission request being answered. */
60
+ sourceReference?: string;
57
61
  attachmentContentType?: string;
58
62
  attachmentBase64?: string;
59
63
  };
@@ -169,6 +169,12 @@ export function buildConsentClaimsSimple(input, options = {}) {
169
169
  [ClaimConsent.action]: (input.actions || []).join(','),
170
170
  [ClaimConsent.actorIdentifier]: actorIdentifier,
171
171
  [ClaimConsent.actorRole]: input.actorRole,
172
+ ...(String(input.eventBasedOn || '').trim()
173
+ ? { [ClaimConsent.eventBasedOn]: String(input.eventBasedOn).trim() }
174
+ : {}),
175
+ ...(String(input.sourceReference || '').trim()
176
+ ? { [ClaimConsent.sourceReference]: String(input.sourceReference).trim() }
177
+ : {}),
172
178
  [ClaimConsent.attachmentContentType]: input.attachmentContentType || 'application/odrl+json',
173
179
  [ClaimConsent.attachmentData]: input.attachmentBase64 || 'e30=',
174
180
  },
@@ -20,6 +20,13 @@ export type ProfessionalSmartVpPayloadInput = Readonly<{
20
20
  additionalVp?: Record<string, unknown>;
21
21
  additionalPayload?: Record<string, unknown>;
22
22
  }>;
23
+ /** Identity fields read from an already-verified professional credential. */
24
+ export type ProfessionalCredentialSummary = Readonly<{
25
+ actorDid: string;
26
+ role: string;
27
+ issuerDid?: string;
28
+ sameAs: string[];
29
+ }>;
23
30
  /**
24
31
  * Returns the normalized public continuity aliases for one professional
25
32
  * identity VC.
@@ -62,6 +69,16 @@ export declare function getProfessionalIdentityTelephone(input: Readonly<Pick<Pr
62
69
  * @param input Professional identity source values.
63
70
  */
64
71
  export declare function getProfessionalIdentityVC(input: ProfessionalEmployeeCredentialInput): Record<string, unknown>;
72
+ /**
73
+ * Reads the actor and role asserted by one professional credential. Signature
74
+ * and employer/issuer trust verification belongs to the enclosing VP verifier.
75
+ */
76
+ export declare function summarizeProfessionalIdentityCredential(credential: unknown): ProfessionalCredentialSummary | undefined;
77
+ /** Finds the exact actor and role in an already-verified professional VP. */
78
+ export declare function getMatchingProfessionalCredentialFromVpToken(vpToken: string, criteria: Readonly<{
79
+ actorDid: string;
80
+ role: string;
81
+ }>): ProfessionalCredentialSummary | undefined;
65
82
  /**
66
83
  * Backwards-compatible alias kept for existing SMART demo callers.
67
84
  *
@@ -2,6 +2,7 @@ import { ClaimsPersonSchemaorg } from '../constants/schemaorg.js';
2
2
  import { ProfessionalCredentialTypes, W3cCredentialTypes, } from '../constants/verifiable-credentials.js';
3
3
  import { normalizeSameAsHashCsv, normalizeSameAsHashList, normalizeTelephoneHash } from './same-as.js';
4
4
  import { buildUnsignedVpJwt } from './jwt.js';
5
+ import { getVpCredentials } from './vp-token.js';
5
6
  /**
6
7
  * Returns the normalized public continuity aliases for one professional
7
8
  * identity VC.
@@ -84,6 +85,38 @@ export function getProfessionalIdentityVC(input) {
84
85
  ...(input.additionalCredential || {}),
85
86
  };
86
87
  }
88
+ /**
89
+ * Reads the actor and role asserted by one professional credential. Signature
90
+ * and employer/issuer trust verification belongs to the enclosing VP verifier.
91
+ */
92
+ export function summarizeProfessionalIdentityCredential(credential) {
93
+ const source = credential;
94
+ const types = Array.isArray(source?.type) ? source.type.map(String) : [String(source?.type || '')];
95
+ if (!types.includes(ProfessionalCredentialTypes.EmployeeCredential))
96
+ return undefined;
97
+ const subject = source?.credentialSubject || {};
98
+ const actorDid = String(subject.id || '').trim();
99
+ const role = String(subject.hasOccupation || '').trim();
100
+ if (!actorDid || !role)
101
+ return undefined;
102
+ const issuerDid = String(source?.issuer?.id || source?.issuer || '').trim();
103
+ return {
104
+ actorDid,
105
+ role,
106
+ ...(issuerDid ? { issuerDid } : {}),
107
+ sameAs: normalizeSameAsHashList(subject.sameAs),
108
+ };
109
+ }
110
+ /** Finds the exact actor and role in an already-verified professional VP. */
111
+ export function getMatchingProfessionalCredentialFromVpToken(vpToken, criteria) {
112
+ const actorDid = String(criteria.actorDid || '').trim();
113
+ const role = String(criteria.role || '').trim().toLowerCase();
114
+ return getVpCredentials(vpToken)
115
+ .map(summarizeProfessionalIdentityCredential)
116
+ .find((summary) => Boolean(summary
117
+ && summary.actorDid === actorDid
118
+ && summary.role.toLowerCase() === role));
119
+ }
87
120
  /**
88
121
  * Backwards-compatible alias kept for existing SMART demo callers.
89
122
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdc-common-utils-ts",
3
- "version": "2.3.20",
3
+ "version": "2.3.22",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },