gdc-sdk-node-ts 2.6.6 → 2.7.0
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/authorized-subject-directory.js +8 -8
- package/dist/device-activation.d.ts +3 -0
- package/dist/device-activation.js +15 -0
- package/dist/digital-twin.js +7 -6
- package/dist/family-organization-search.js +0 -1
- package/dist/individual-onboarding.js +0 -1
- package/dist/individual-start.js +0 -1
- package/dist/organization-controller-recovery.js +4 -1
- package/dist/organization-employee-lifecycle.js +4 -2
- package/dist/organization-license-order.js +0 -1
- package/dist/organization-registration-authorization.d.ts +16 -0
- package/dist/organization-registration-authorization.js +34 -0
- package/dist/resource-operations.js +4 -11
- package/dist/runtime-host-submission.js +0 -7
- package/dist/runtime-paths.js +4 -3
- package/dist/server-profile-session.d.ts +15 -1
- package/dist/server-profile-session.js +79 -12
- package/dist/smart-token.js +18 -9
- package/package.json +3 -3
|
@@ -34,11 +34,11 @@ export async function listAuthorizedIndividualSubjectsWithDeps(routeContext, inp
|
|
|
34
34
|
data: [{
|
|
35
35
|
type: 'Family-search-v1.0',
|
|
36
36
|
request: { method: 'POST' },
|
|
37
|
-
meta: { claims: {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
37
|
+
resource: { meta: { claims: {
|
|
38
|
+
'@context': SCHEMA_CONTEXT,
|
|
39
|
+
...(email ? { [CLAIM_ORGANIZATION_OWNER_EMAIL]: email } : {}),
|
|
40
|
+
...(telephone ? { [CLAIM_ORGANIZATION_OWNER_TELEPHONE]: telephone } : {}),
|
|
41
|
+
} } },
|
|
42
42
|
}],
|
|
43
43
|
},
|
|
44
44
|
}, input.pollOptions);
|
|
@@ -58,7 +58,7 @@ export async function listAuthorizedIndividualSubjectsWithDeps(routeContext, inp
|
|
|
58
58
|
type: 'batch',
|
|
59
59
|
data: [{
|
|
60
60
|
request: { method: 'POST' },
|
|
61
|
-
meta: { status: 'active', claims: licenseClaims },
|
|
61
|
+
resource: { meta: { status: 'active', claims: licenseClaims } },
|
|
62
62
|
}],
|
|
63
63
|
},
|
|
64
64
|
}, input.pollOptions);
|
|
@@ -76,7 +76,7 @@ export async function listAuthorizedIndividualSubjectsWithDeps(routeContext, inp
|
|
|
76
76
|
data: [{
|
|
77
77
|
type: 'Family-search-v1.0',
|
|
78
78
|
request: { method: 'POST' },
|
|
79
|
-
meta: { claims: organizationClaims },
|
|
79
|
+
resource: { meta: { claims: organizationClaims } },
|
|
80
80
|
}],
|
|
81
81
|
},
|
|
82
82
|
}, input.pollOptions);
|
|
@@ -155,7 +155,7 @@ function readAcceptedLicenses(result) {
|
|
|
155
155
|
}
|
|
156
156
|
function readFirstClaims(result) {
|
|
157
157
|
const entry = readData(result)[0];
|
|
158
|
-
const claims = entry?.meta?.claims || entry?.
|
|
158
|
+
const claims = entry?.resource?.meta?.claims || entry?.meta?.claims;
|
|
159
159
|
return claims && typeof claims === 'object' ? claims : {};
|
|
160
160
|
}
|
|
161
161
|
function readData(result) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ClinicalCreatorBinding } from 'gdc-common-utils-ts/utils/fhir-ips-creator-identity';
|
|
1
2
|
import type { PollOptions, SubmitAndPollResult } from './orchestration/client-port.js';
|
|
2
3
|
import type { RouteContext } from './individual-onboarding.js';
|
|
3
4
|
export type EmployeeDeviceActivationInput = {
|
|
@@ -32,6 +33,7 @@ export type ProfileDeviceRegistrationInput = Readonly<{
|
|
|
32
33
|
deviceName?: string;
|
|
33
34
|
actorDid?: string;
|
|
34
35
|
profileDid?: string;
|
|
36
|
+
clinicalCreatorBinding?: ClinicalCreatorBinding;
|
|
35
37
|
}>;
|
|
36
38
|
/** Advanced typed editor used by SDK runtimes that already own profile keys. */
|
|
37
39
|
export interface ProfileDeviceActivationDraft {
|
|
@@ -43,6 +45,7 @@ export interface ProfileDeviceActivationDraft {
|
|
|
43
45
|
setDeviceName(value: string): ProfileDeviceActivationDraft;
|
|
44
46
|
setActorDid(value: string): ProfileDeviceActivationDraft;
|
|
45
47
|
setProfileDid(value: string): ProfileDeviceActivationDraft;
|
|
48
|
+
setClinicalCreatorBinding(value: ClinicalCreatorBinding): ProfileDeviceActivationDraft;
|
|
46
49
|
setTimeoutSeconds(value: number): ProfileDeviceActivationDraft;
|
|
47
50
|
setIntervalSeconds(value: number): ProfileDeviceActivationDraft;
|
|
48
51
|
build(): EmployeeDeviceActivationRequestInput & {
|
|
@@ -20,6 +20,7 @@ export function createProfileDeviceActivationRequest(input) {
|
|
|
20
20
|
let deviceName = '';
|
|
21
21
|
let actorDid = '';
|
|
22
22
|
let profileDid = '';
|
|
23
|
+
let clinicalCreatorBinding;
|
|
23
24
|
let timeoutSeconds;
|
|
24
25
|
let intervalSeconds;
|
|
25
26
|
const draft = {
|
|
@@ -31,6 +32,7 @@ export function createProfileDeviceActivationRequest(input) {
|
|
|
31
32
|
setDeviceName(value) { deviceName = normalizedText(value); return draft; },
|
|
32
33
|
setActorDid(value) { actorDid = normalizedText(value); return draft; },
|
|
33
34
|
setProfileDid(value) { profileDid = normalizedText(value); return draft; },
|
|
35
|
+
setClinicalCreatorBinding(value) { clinicalCreatorBinding = stableClinicalCreatorBinding(value); return draft; },
|
|
34
36
|
setTimeoutSeconds(value) { timeoutSeconds = positiveNumber(value, 'timeout seconds'); return draft; },
|
|
35
37
|
setIntervalSeconds(value) { intervalSeconds = positiveNumber(value, 'interval seconds'); return draft; },
|
|
36
38
|
build() {
|
|
@@ -43,6 +45,7 @@ export function createProfileDeviceActivationRequest(input) {
|
|
|
43
45
|
...(deviceName ? { deviceName } : {}),
|
|
44
46
|
...(actorDid ? { actorDid } : {}),
|
|
45
47
|
...(profileDid ? { profileDid } : {}),
|
|
48
|
+
...(clinicalCreatorBinding ? { clinicalCreatorBinding } : {}),
|
|
46
49
|
};
|
|
47
50
|
return {
|
|
48
51
|
...input,
|
|
@@ -165,12 +168,24 @@ function resolveDcrPayload(input) {
|
|
|
165
168
|
},
|
|
166
169
|
...(registration.actorDid ? { [IdentityDcrMetadataFields.ActorDid]: registration.actorDid } : {}),
|
|
167
170
|
...(registration.profileDid ? { [IdentityDcrMetadataFields.ProfileDid]: registration.profileDid } : {}),
|
|
171
|
+
...(registration.clinicalCreatorBinding ? {
|
|
172
|
+
[IdentityDcrMetadataFields.ClinicalCreatorBinding]: stableClinicalCreatorBinding(registration.clinicalCreatorBinding),
|
|
173
|
+
} : {}),
|
|
168
174
|
};
|
|
169
175
|
}
|
|
170
176
|
if (input.dcrPayload)
|
|
171
177
|
return { ...input.dcrPayload };
|
|
172
178
|
throw new Error('Device activation requires a device registration built by createProfileDeviceActivationRequest.');
|
|
173
179
|
}
|
|
180
|
+
function stableClinicalCreatorBinding(binding) {
|
|
181
|
+
return {
|
|
182
|
+
kind: binding.kind,
|
|
183
|
+
actorIdentifier: binding.actorIdentifier,
|
|
184
|
+
authorIdentifier: binding.authorIdentifier,
|
|
185
|
+
ownerIdentifier: binding.ownerIdentifier,
|
|
186
|
+
role: binding.role,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
174
189
|
function responseBody(value) {
|
|
175
190
|
if (typeof value === 'string') {
|
|
176
191
|
try {
|
package/dist/digital-twin.js
CHANGED
|
@@ -234,12 +234,13 @@ export async function searchDigitalTwinsWithDeps(ctx, input, deps) {
|
|
|
234
234
|
? {
|
|
235
235
|
data: [{
|
|
236
236
|
type: `${resourceType}-search-request-v1.0`,
|
|
237
|
-
resource: {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
237
|
+
resource: {
|
|
238
|
+
resourceType: 'Parameters',
|
|
239
|
+
parameter: parameters,
|
|
240
|
+
meta: { claims: Object.fromEntries([
|
|
241
|
+
['@context', format],
|
|
242
|
+
...parameters.map((parameter) => [parameter.name, parameter.valueString || parameter.valueDate || '']),
|
|
243
|
+
]) },
|
|
243
244
|
},
|
|
244
245
|
}],
|
|
245
246
|
}
|
package/dist/individual-start.js
CHANGED
|
@@ -68,7 +68,10 @@ export const recoverOrganizationControllerWithIssueWithDeps = recoverOrganizatio
|
|
|
68
68
|
export function readLegalOrganizationCredentialReissuanceActivationCode(result) {
|
|
69
69
|
const pollBody = (result?.poll?.body || {});
|
|
70
70
|
for (const candidate of nestedObjects(pollBody)) {
|
|
71
|
-
const
|
|
71
|
+
const resource = candidate.resource;
|
|
72
|
+
const resourceClaims = resource?.meta?.claims;
|
|
73
|
+
const legacyClaims = candidate.meta?.claims;
|
|
74
|
+
const claims = resourceClaims || legacyClaims;
|
|
72
75
|
const activationCode = String(claims?.['org.schema.IndividualProduct.serialNumber'] || '').trim();
|
|
73
76
|
if (activationCode)
|
|
74
77
|
return activationCode;
|
|
@@ -84,8 +84,10 @@ export function readEmployeeLicenseOfferId(value) {
|
|
|
84
84
|
for (const candidate of nestedRecords(value)) {
|
|
85
85
|
if (candidate.type !== 'Employee-license-offer-v1.0')
|
|
86
86
|
continue;
|
|
87
|
-
const
|
|
88
|
-
const
|
|
87
|
+
const resource = record(candidate.resource);
|
|
88
|
+
const resourceMeta = record(resource?.meta);
|
|
89
|
+
const legacyMeta = record(candidate.meta);
|
|
90
|
+
const claims = record(resourceMeta?.claims) || record(legacyMeta?.claims);
|
|
89
91
|
const offerId = String(claims?.[ClaimsOfferSchemaorg.identifier] || '').trim();
|
|
90
92
|
if (offerId)
|
|
91
93
|
return offerId;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { VerifiableCredentialV2 } from 'gdc-common-utils-ts/models/verifiable-credential';
|
|
2
|
+
import type { IWallet, WalletExecutionContext, WalletKeySelection } from 'gdc-sdk-core-ts';
|
|
3
|
+
/** Input required to add one human-authorizer proof to the out-of-band VC. */
|
|
4
|
+
export type SignOrganizationRegistrationAuthorizationInput = Readonly<{
|
|
5
|
+
credential: VerifiableCredentialV2;
|
|
6
|
+
wallet: IWallet;
|
|
7
|
+
context: WalletExecutionContext;
|
|
8
|
+
key: WalletKeySelection;
|
|
9
|
+
verificationMethod: string;
|
|
10
|
+
createdAt?: string;
|
|
11
|
+
}>;
|
|
12
|
+
/**
|
|
13
|
+
* Adds one detached JWS proof made by the already-unlocked professional
|
|
14
|
+
* profile. The browser never receives the private key or clear wallet seed.
|
|
15
|
+
*/
|
|
16
|
+
export declare function signOrganizationRegistrationAuthorizationCredential(input: SignOrganizationRegistrationAuthorizationInput): Promise<VerifiableCredentialV2>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { canonicalizeOrganizationRegistrationAuthorizationCredential, } from 'gdc-common-utils-ts/utils/organization-registration-authorization';
|
|
2
|
+
/**
|
|
3
|
+
* Adds one detached JWS proof made by the already-unlocked professional
|
|
4
|
+
* profile. The browser never receives the private key or clear wallet seed.
|
|
5
|
+
*/
|
|
6
|
+
export async function signOrganizationRegistrationAuthorizationCredential(input) {
|
|
7
|
+
if (!input.wallet.signDetachedJws) {
|
|
8
|
+
throw new Error('Wallet does not support detached organization authorization proofs.');
|
|
9
|
+
}
|
|
10
|
+
const verificationMethod = String(input.verificationMethod || '').trim();
|
|
11
|
+
if (!verificationMethod)
|
|
12
|
+
throw new Error('Organization authorization proof requires verificationMethod.');
|
|
13
|
+
const payload = canonicalizeOrganizationRegistrationAuthorizationCredential(input.credential);
|
|
14
|
+
const jws = await input.wallet.signDetachedJws(input.context, {
|
|
15
|
+
payload,
|
|
16
|
+
header: { typ: 'application/vc+ld+json' },
|
|
17
|
+
key: input.key,
|
|
18
|
+
});
|
|
19
|
+
const existingProofs = Array.isArray(input.credential.proof)
|
|
20
|
+
? input.credential.proof
|
|
21
|
+
: input.credential.proof
|
|
22
|
+
? [input.credential.proof]
|
|
23
|
+
: [];
|
|
24
|
+
return {
|
|
25
|
+
...input.credential,
|
|
26
|
+
proof: [...existingProofs, {
|
|
27
|
+
type: 'JsonWebSignature2020',
|
|
28
|
+
created: input.createdAt || new Date().toISOString(),
|
|
29
|
+
proofPurpose: 'contractAgreement',
|
|
30
|
+
verificationMethod,
|
|
31
|
+
jws,
|
|
32
|
+
}],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -340,11 +340,11 @@ export async function transitionIndividualMemberLicenseWithDeps(routeCtx, action
|
|
|
340
340
|
...(input.ownerOrganizationId ? { ownerOrganizationId: input.ownerOrganizationId } : {}),
|
|
341
341
|
...(input.subjectId ? { subjectId: input.subjectId } : {}),
|
|
342
342
|
...(input.verifiedActorIdentifier ? { verifiedActorIdentifier: input.verifiedActorIdentifier } : {}),
|
|
343
|
-
claims: {
|
|
344
|
-
'@context': 'org.schema',
|
|
345
|
-
'org.schema.IndividualProduct.serialNumber': input.activationCode,
|
|
346
|
-
},
|
|
347
343
|
},
|
|
344
|
+
resource: { meta: { claims: {
|
|
345
|
+
'@context': 'org.schema',
|
|
346
|
+
'org.schema.IndividualProduct.serialNumber': input.activationCode,
|
|
347
|
+
} } },
|
|
348
348
|
request: { method: 'POST' },
|
|
349
349
|
}],
|
|
350
350
|
},
|
|
@@ -414,7 +414,6 @@ export async function disableIndividualMemberWithDeps(routeCtx, input, options,
|
|
|
414
414
|
type: 'batch',
|
|
415
415
|
entry: [{
|
|
416
416
|
request: { method: GwCoreLifecycleRequestMethod.Post },
|
|
417
|
-
meta: { claims },
|
|
418
417
|
resource: {
|
|
419
418
|
...resource,
|
|
420
419
|
...(input.resourceId ? { id: input.resourceId } : {}),
|
|
@@ -444,7 +443,6 @@ export async function purgeIndividualMemberWithDeps(routeCtx, input, options, de
|
|
|
444
443
|
entry: [{
|
|
445
444
|
type: input.dataType || GwCoreLifecycleRequestType.IndividualMemberPurge,
|
|
446
445
|
request: { method: GwCoreLifecycleRequestMethod.Post },
|
|
447
|
-
meta: { claims },
|
|
448
446
|
resource: {
|
|
449
447
|
...resource,
|
|
450
448
|
...(input.resourceId ? { id: input.resourceId } : {}),
|
|
@@ -550,7 +548,6 @@ export async function requestProfessionalAccessWithDeps(routeCtx, input, deps) {
|
|
|
550
548
|
type: 'batch',
|
|
551
549
|
data: [{
|
|
552
550
|
type: 'CommMsgExtended',
|
|
553
|
-
meta: { claims: communicationClaims },
|
|
554
551
|
resource: communicationResource,
|
|
555
552
|
}],
|
|
556
553
|
},
|
|
@@ -701,7 +698,6 @@ export function buildBlockchainArtifactBundleFromSearchResponse(input) {
|
|
|
701
698
|
});
|
|
702
699
|
return {
|
|
703
700
|
type: 'DocumentReference',
|
|
704
|
-
meta: { claims: artifact.documentReference.meta?.claims || {} },
|
|
705
701
|
resource: artifact.documentReference,
|
|
706
702
|
request: {
|
|
707
703
|
method: 'POST',
|
|
@@ -762,7 +758,6 @@ export async function registerBlockchainArtifactAndUpdateIndexWithDeps(routeCtx,
|
|
|
762
758
|
type: 'batch',
|
|
763
759
|
data: [{
|
|
764
760
|
type: 'DocumentReference',
|
|
765
|
-
meta: { claims: artifact.documentReference.meta?.claims || {} },
|
|
766
761
|
resource: artifact.documentReference,
|
|
767
762
|
request: {
|
|
768
763
|
method: 'POST',
|
|
@@ -840,7 +835,6 @@ export async function grantProfessionalAccessWithDeps(routeCtx, input, deps) {
|
|
|
840
835
|
body: {
|
|
841
836
|
data: [{
|
|
842
837
|
type: input.dataType || 'Consent-grant-request-v1.0',
|
|
843
|
-
meta: { claims: built.consentClaims },
|
|
844
838
|
resource: { resourceType: 'Consent', meta: { claims: built.consentClaims } },
|
|
845
839
|
}],
|
|
846
840
|
},
|
|
@@ -983,7 +977,6 @@ export async function revokeProfessionalAccessWithDeps(routeCtx, input, deps) {
|
|
|
983
977
|
body: {
|
|
984
978
|
data: [{
|
|
985
979
|
type: input.dataType || 'Consent-grant-request-v1.0',
|
|
986
|
-
meta: { claims: revokedClaims },
|
|
987
980
|
resource: { resourceType: 'Consent', meta: { claims: revokedClaims } },
|
|
988
981
|
}],
|
|
989
982
|
},
|
|
@@ -65,13 +65,6 @@ export async function activateOrganizationInGatewayFromIcaProofWithDeps(input) {
|
|
|
65
65
|
...(input.activationInput.controller ? { controller: input.activationInput.controller } : {}),
|
|
66
66
|
data: [{
|
|
67
67
|
type: 'Organization-activation-request-v1.0',
|
|
68
|
-
meta: {
|
|
69
|
-
claims: {
|
|
70
|
-
'@context': 'org.schema',
|
|
71
|
-
...serviceClaims,
|
|
72
|
-
...(input.activationInput.additionalClaims || {}),
|
|
73
|
-
},
|
|
74
|
-
},
|
|
75
68
|
resource: {
|
|
76
69
|
meta: {
|
|
77
70
|
claims: {
|
package/dist/runtime-paths.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
|
|
2
2
|
import { buildGwCoreTenantResourceActionPath } from 'gdc-common-utils-ts/utils/gw-core-path';
|
|
3
|
-
import { IdentityAuthActions, IdentityAuthRouteSegments } from 'gdc-common-utils-ts/constants/identity-auth';
|
|
3
|
+
import { IdentityAuthActions, IdentityAuthResourceTypes, IdentityAuthRouteSegments, SmartPostDcrActions, } from 'gdc-common-utils-ts/constants/identity-auth';
|
|
4
|
+
import { GatewayRouteFormats, GatewayRouteSections, } from 'gdc-common-utils-ts/constants/gateway-response';
|
|
4
5
|
export function buildV1Path(routeCtx, section, format, resourceType, action) {
|
|
5
6
|
return buildGwCoreTenantResourceActionPath({
|
|
6
7
|
tenantId: routeCtx.tenantId,
|
|
@@ -60,8 +61,8 @@ function buildIdentityAuthPath(ctx, action) {
|
|
|
60
61
|
return `/${encodeURIComponent(IdentityAuthRouteSegments.Host)}/cds-${encodeURIComponent(ctx.jurisdiction)}/${IdentityAuthRouteSegments.Version}/${encodeURIComponent(ctx.sector)}/${encodeURIComponent(ctx.tenantId)}/${IdentityAuthRouteSegments.Section}/${IdentityAuthRouteSegments.Format}/${encodeURIComponent(action)}`;
|
|
61
62
|
}
|
|
62
63
|
export function buildIdentityOpenIdSmartTokenPath(ctx) {
|
|
63
|
-
return
|
|
64
|
+
return buildV1Path(ctx, GatewayRouteSections.Identity, GatewayRouteFormats.OpenId, IdentityAuthResourceTypes.Smart, SmartPostDcrActions.Token);
|
|
64
65
|
}
|
|
65
66
|
export function buildIdentityOpenIdSmartTokenPollPath(ctx) {
|
|
66
|
-
return
|
|
67
|
+
return buildV1Path(ctx, GatewayRouteSections.Identity, GatewayRouteFormats.OpenId, IdentityAuthResourceTypes.Smart, SmartPostDcrActions.TokenResponse);
|
|
67
68
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { JWK } from 'gdc-common-utils-ts/models/jwk';
|
|
2
2
|
import type { ActorKind } from 'gdc-common-utils-ts/models/actor-session';
|
|
3
3
|
import type { LegalOrganizationVerificationTransactionInput } from 'gdc-common-utils-ts/utils/legal-organization-verification-transaction';
|
|
4
|
-
import { type AppInfo, type ConfidentialStorageProfile, type PollOptions, type SubmitAndPollResult } from 'gdc-sdk-core-ts';
|
|
4
|
+
import { type AppInfo, type ConfidentialStorageProfile, type PollOptions, type SubmitAndPollResult, type ClinicalCreatorBinding, type ClinicalCreatorIpsExport } from 'gdc-sdk-core-ts';
|
|
5
5
|
import { OrganizationControllerSdk } from './orchestration/organization-controller-sdk.js';
|
|
6
6
|
import { ProfessionalSdk } from './orchestration/professional-sdk.js';
|
|
7
7
|
import { DigitalTwinSdk } from './orchestration/digital-twin-sdk.js';
|
|
@@ -29,6 +29,8 @@ export type ServerProfileRecord = Readonly<{
|
|
|
29
29
|
clientId: string;
|
|
30
30
|
/** Stable non-secret id of the browser/app installation registered by DCR. */
|
|
31
31
|
clientInstanceId?: string;
|
|
32
|
+
/** Stable FHIR creator/permission identity; channel and device values are aliases only. */
|
|
33
|
+
clinicalCreatorBinding?: ClinicalCreatorBinding;
|
|
32
34
|
deviceDid: string;
|
|
33
35
|
publicJwks: Record<string, unknown>[];
|
|
34
36
|
/** Public recipient key for local confidential storage; not a DCR communication key. */
|
|
@@ -122,6 +124,12 @@ export type ServerProfileEnrollmentInput = Readonly<{
|
|
|
122
124
|
* derives the identity from the wallet key it creates for this profile.
|
|
123
125
|
*/
|
|
124
126
|
clientInstanceId?: string;
|
|
127
|
+
/**
|
|
128
|
+
* Server-authorized stable creator identity imported from FHIR or generated
|
|
129
|
+
* locally. Enrollment adds the operational actor DID and registered DCR
|
|
130
|
+
* client/key aliases; it never derives UUIDs from those aliases.
|
|
131
|
+
*/
|
|
132
|
+
clinicalCreatorBinding?: ClinicalCreatorBinding;
|
|
125
133
|
/**
|
|
126
134
|
* Signed actor/controller VP protected for later SMART operations. Required
|
|
127
135
|
* for organization/professional/member actors. Individual-controller
|
|
@@ -379,6 +387,7 @@ export declare class ServerProfileSessionManager {
|
|
|
379
387
|
openProfessional(input: ServerProfessionalOpenInput): Promise<OpenedServerProfessional>;
|
|
380
388
|
lock(ownerId: string, sessionId: string): Promise<void>;
|
|
381
389
|
private createClient;
|
|
390
|
+
private createRegisteredProfileClient;
|
|
382
391
|
private createWallet;
|
|
383
392
|
/**
|
|
384
393
|
* Upgrade legacy profiles deterministically after successful PIN unlock.
|
|
@@ -391,3 +400,8 @@ export declare class ServerProfileSessionManager {
|
|
|
391
400
|
private requireSubject;
|
|
392
401
|
private now;
|
|
393
402
|
}
|
|
403
|
+
/**
|
|
404
|
+
* Exports the stable FHIR IPS creator attached to a server profile. Direct
|
|
405
|
+
* writes continue to use `profile.actorDid`; this function is export-only.
|
|
406
|
+
*/
|
|
407
|
+
export declare function exportServerProfileClinicalCreatorIps(profile: ServerProfileRecord): ClinicalCreatorIpsExport;
|
|
@@ -3,7 +3,7 @@ import { randomBytes } from 'node:crypto';
|
|
|
3
3
|
import { ActorKinds } from 'gdc-common-utils-ts/constants/actor-session';
|
|
4
4
|
import { readLegalOrganizationVerificationCredentialPairFromResponseBody, readServiceControllerCredentialFromResponseBody, } from 'gdc-common-utils-ts/utils/legal-organization-verification-result';
|
|
5
5
|
import { addLegalRepresentativeCredential, addOrganizationCredential, addServiceControllerCredential, createVP, } from 'gdc-common-utils-ts/utils/vp-token';
|
|
6
|
-
import { TransportProfiles, } from 'gdc-sdk-core-ts';
|
|
6
|
+
import { TransportProfiles, resolveClinicalCreatorIpsExport, } from 'gdc-sdk-core-ts';
|
|
7
7
|
import { NodeManagedWallet } from './node-managed-wallet.js';
|
|
8
8
|
import { NodeHttpClient } from './node-runtime-client.js';
|
|
9
9
|
import { OrganizationControllerSdk } from './orchestration/organization-controller-sdk.js';
|
|
@@ -58,7 +58,7 @@ export class ServerProfileSessionManager {
|
|
|
58
58
|
});
|
|
59
59
|
const redirectUris = input.redirectUris || input.dcrRedirectUris || [];
|
|
60
60
|
const clientName = String(input.clientName || input.dcrClientName || '').trim();
|
|
61
|
-
const
|
|
61
|
+
const activationDraft = createProfileDeviceActivationRequest({
|
|
62
62
|
activationCode: input.activationCode,
|
|
63
63
|
idToken,
|
|
64
64
|
...input.routeContext,
|
|
@@ -69,8 +69,11 @@ export class ServerProfileSessionManager {
|
|
|
69
69
|
.setRedirectUris(redirectUris)
|
|
70
70
|
.setPublicJwks(publicKeys.filter((entry) => entry.purpose !== 'document-at-rest').map((entry) => entry.publicJwk))
|
|
71
71
|
.setActorDid(input.actorDid)
|
|
72
|
-
.setProfileDid(input.profileDid)
|
|
73
|
-
|
|
72
|
+
.setProfileDid(input.profileDid);
|
|
73
|
+
if (input.clinicalCreatorBinding) {
|
|
74
|
+
activationDraft.setClinicalCreatorBinding(input.clinicalCreatorBinding);
|
|
75
|
+
}
|
|
76
|
+
const activationRequest = activationDraft.build();
|
|
74
77
|
const activation = await client.activateProfileDeviceWithActivationRequest(activationRequest);
|
|
75
78
|
const dcrBody = terminalBody(activation.dcr.poll.body);
|
|
76
79
|
const clientId = findText(dcrBody, ['client_id', 'clientId']);
|
|
@@ -86,6 +89,16 @@ export class ServerProfileSessionManager {
|
|
|
86
89
|
...input.professionalProof,
|
|
87
90
|
})
|
|
88
91
|
: undefined);
|
|
92
|
+
const clinicalCreatorBinding = input.clinicalCreatorBinding
|
|
93
|
+
? bindClinicalCreatorChannels(input.clinicalCreatorBinding, {
|
|
94
|
+
actorDid: input.actorDid,
|
|
95
|
+
clientId,
|
|
96
|
+
clientInstanceId,
|
|
97
|
+
keyIds: publicKeys
|
|
98
|
+
.map((entry) => String(entry.publicJwk?.kid || entry.kid || '').trim())
|
|
99
|
+
.filter(Boolean),
|
|
100
|
+
})
|
|
101
|
+
: undefined;
|
|
89
102
|
const record = {
|
|
90
103
|
profileId: input.profileId,
|
|
91
104
|
walletKeyDerivationId,
|
|
@@ -99,6 +112,7 @@ export class ServerProfileSessionManager {
|
|
|
99
112
|
allowedSubjectDids: unique(input.allowedSubjectDids),
|
|
100
113
|
clientId,
|
|
101
114
|
clientInstanceId,
|
|
115
|
+
...(clinicalCreatorBinding ? { clinicalCreatorBinding } : {}),
|
|
102
116
|
deviceDid,
|
|
103
117
|
publicJwks: publicKeys.filter((entry) => entry.purpose !== 'document-at-rest').map((entry) => entry.publicJwk),
|
|
104
118
|
storagePublicJwk: storagePublicJwk,
|
|
@@ -157,6 +171,14 @@ export class ServerProfileSessionManager {
|
|
|
157
171
|
role: replacement.employeeRole,
|
|
158
172
|
sameAs: replacement.employeeActorIdentifier,
|
|
159
173
|
},
|
|
174
|
+
...(previous.clinicalCreatorBinding ? {
|
|
175
|
+
clinicalCreatorBinding: {
|
|
176
|
+
...previous.clinicalCreatorBinding,
|
|
177
|
+
actorDids: [previous.actorDid],
|
|
178
|
+
dcrClientIds: [],
|
|
179
|
+
keyIds: [],
|
|
180
|
+
},
|
|
181
|
+
} : {}),
|
|
160
182
|
});
|
|
161
183
|
await this.options.store.deleteSessionsForProfile(profile.profileId);
|
|
162
184
|
return profile;
|
|
@@ -328,12 +350,13 @@ export class ServerProfileSessionManager {
|
|
|
328
350
|
profile = await this.ensureRequiredStorageProfile(profile, seed);
|
|
329
351
|
const walletKeyDerivationId = profile.walletKeyDerivationId || profile.profileId;
|
|
330
352
|
const wallet = await this.createWallet(walletKeyDerivationId, seed);
|
|
353
|
+
const context = walletContext(walletKeyDerivationId);
|
|
331
354
|
const smartTokenEndpoint = [
|
|
332
355
|
this.options.gatewayBaseUrl.replace(/\/+$/, ''),
|
|
333
356
|
buildIdentityOpenIdSmartTokenPath(profile.routeContext),
|
|
334
357
|
].join('');
|
|
335
358
|
const assertion = await buildWalletClientAssertion(wallet, profile, smartTokenEndpoint, now);
|
|
336
|
-
const token = await this.
|
|
359
|
+
const token = await this.createRegisteredProfileClient(profile, wallet, context, input.idToken).requestSmartToken({
|
|
337
360
|
...profile.routeContext,
|
|
338
361
|
actorDid: profile.actorDid,
|
|
339
362
|
subjectDid: input.subjectDid,
|
|
@@ -394,13 +417,14 @@ export class ServerProfileSessionManager {
|
|
|
394
417
|
: undefined;
|
|
395
418
|
const walletKeyDerivationId = profile.walletKeyDerivationId || profile.profileId;
|
|
396
419
|
const wallet = await this.createWallet(walletKeyDerivationId, seed);
|
|
420
|
+
const context = walletContext(walletKeyDerivationId);
|
|
397
421
|
const now = this.now();
|
|
398
422
|
const smartTokenEndpoint = [
|
|
399
423
|
this.options.gatewayBaseUrl.replace(/\/+$/, ''),
|
|
400
424
|
buildIdentityOpenIdSmartTokenPath(profile.routeContext),
|
|
401
425
|
].join('');
|
|
402
426
|
const assertion = await buildWalletClientAssertion(wallet, profile, smartTokenEndpoint, now);
|
|
403
|
-
const token = await this.
|
|
427
|
+
const token = await this.createRegisteredProfileClient(profile, wallet, context, normalizedIdToken).requestSmartToken({
|
|
404
428
|
...profile.routeContext,
|
|
405
429
|
actorDid: profile.actorDid,
|
|
406
430
|
subjectDid: session.subjectDid,
|
|
@@ -546,13 +570,11 @@ export class ServerProfileSessionManager {
|
|
|
546
570
|
transportProfile: TransportProfiles.DidcommEncryptedForm,
|
|
547
571
|
secureTransportAdapter,
|
|
548
572
|
});
|
|
549
|
-
// SMART
|
|
550
|
-
// the
|
|
551
|
-
//
|
|
552
|
-
// wallet and therefore use the encrypted DIDComm client. SMART/OpenID below
|
|
553
|
-
// remains an independent JSON/form exchange through its dedicated method.
|
|
573
|
+
// Both SMART and later business operations are post-DCR profile actions.
|
|
574
|
+
// Strict GW therefore resolves the same registered employee wallet for the
|
|
575
|
+
// encrypted request and poll transport used by both facades.
|
|
554
576
|
const sdk = new ProfessionalSdk(operationalClient);
|
|
555
|
-
const smartSdk =
|
|
577
|
+
const smartSdk = sdk;
|
|
556
578
|
const digitalTwin = new DigitalTwinSdk(operationalClient, profile.actorDid);
|
|
557
579
|
let digitalTwinAccessToken;
|
|
558
580
|
const request = async (smart) => {
|
|
@@ -625,6 +647,20 @@ export class ServerProfileSessionManager {
|
|
|
625
647
|
appInfo: this.options.appInfo,
|
|
626
648
|
});
|
|
627
649
|
}
|
|
650
|
+
createRegisteredProfileClient(profile, wallet, context, bearerToken) {
|
|
651
|
+
return new NodeHttpClient({
|
|
652
|
+
baseUrl: this.options.gatewayBaseUrl,
|
|
653
|
+
ctx: profile.routeContext,
|
|
654
|
+
bearerToken,
|
|
655
|
+
fetchImpl: this.options.fetchImpl,
|
|
656
|
+
appInfo: this.options.appInfo,
|
|
657
|
+
transportProfile: TransportProfiles.DidcommEncryptedForm,
|
|
658
|
+
secureTransportAdapter: {
|
|
659
|
+
pack: (message) => wallet.packForRecipientWithContext(bindOrganizationControllerTransport(message, profile), profile.providerDid, { context }),
|
|
660
|
+
unpack: async (jwe) => (await wallet.unpackWithContext(jwe, { context })).content,
|
|
661
|
+
},
|
|
662
|
+
});
|
|
663
|
+
}
|
|
628
664
|
async createWallet(walletKeyDerivationId, seed) {
|
|
629
665
|
const wallet = new NodeManagedWallet({ resolveRecipientJwk: this.options.resolveRecipientJwk });
|
|
630
666
|
const context = walletContext(walletKeyDerivationId);
|
|
@@ -755,6 +791,33 @@ function requireBase64UrlSeed32(seed) {
|
|
|
755
791
|
throw new Error('Profile enrollment walletSeed must be a 32-byte base64url value.');
|
|
756
792
|
}
|
|
757
793
|
}
|
|
794
|
+
/**
|
|
795
|
+
* Exports the stable FHIR IPS creator attached to a server profile. Direct
|
|
796
|
+
* writes continue to use `profile.actorDid`; this function is export-only.
|
|
797
|
+
*/
|
|
798
|
+
export function exportServerProfileClinicalCreatorIps(profile) {
|
|
799
|
+
if (!profile.clinicalCreatorBinding) {
|
|
800
|
+
throw new Error('Server profile has no clinical creator binding.');
|
|
801
|
+
}
|
|
802
|
+
return resolveClinicalCreatorIpsExport({
|
|
803
|
+
bindings: [profile.clinicalCreatorBinding],
|
|
804
|
+
evidence: { actorDid: profile.actorDid },
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
function bindClinicalCreatorChannels(binding, channel) {
|
|
808
|
+
const stored = {
|
|
809
|
+
...binding,
|
|
810
|
+
actorDids: unique([...(binding.actorDids || []), channel.actorDid]),
|
|
811
|
+
dcrClientIds: unique([
|
|
812
|
+
...(binding.dcrClientIds || []),
|
|
813
|
+
channel.clientId,
|
|
814
|
+
channel.clientInstanceId,
|
|
815
|
+
]),
|
|
816
|
+
keyIds: unique([...(binding.keyIds || []), ...channel.keyIds]),
|
|
817
|
+
};
|
|
818
|
+
resolveClinicalCreatorIpsExport({ bindings: [stored], evidence: { actorDid: channel.actorDid } });
|
|
819
|
+
return stored;
|
|
820
|
+
}
|
|
758
821
|
function requireEnrollment(input) {
|
|
759
822
|
for (const [name, value] of Object.entries({
|
|
760
823
|
ownerId: input.ownerId,
|
|
@@ -775,6 +838,10 @@ function requireEnrollment(input) {
|
|
|
775
838
|
if (input.professionalProof && !String(input.professionalProof.role || '').trim()) {
|
|
776
839
|
throw new Error('Profile enrollment professionalProof requires role.');
|
|
777
840
|
}
|
|
841
|
+
if (input.professionalProof && input.clinicalCreatorBinding
|
|
842
|
+
&& String(input.professionalProof.role).trim() !== String(input.clinicalCreatorBinding.role).trim()) {
|
|
843
|
+
throw new Error('Clinical creator binding role must equal professionalProof.role.');
|
|
844
|
+
}
|
|
778
845
|
if (!input.allowedSubjectDids.length)
|
|
779
846
|
throw new Error('Profile enrollment requires an allowed subject.');
|
|
780
847
|
}
|
package/dist/smart-token.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
|
|
2
2
|
import { HealthcareConsentPurposes } from 'gdc-common-utils-ts/constants';
|
|
3
|
+
import { IdentityAuthResourceTypes, SmartPostDcrActions, } from 'gdc-common-utils-ts/constants/identity-auth';
|
|
4
|
+
import { GatewayRouteFormats, GatewayRouteSections, } from 'gdc-common-utils-ts/constants/gateway-response';
|
|
3
5
|
import { buildClientAssertionJwt } from 'gdc-common-utils-ts/utils/client-assertion';
|
|
4
6
|
import { resolvePollOptionsFromSeconds } from './poll-options.js';
|
|
5
7
|
/**
|
|
@@ -109,16 +111,19 @@ function normalizeResolvedSmartTokenEndpoint(value) {
|
|
|
109
111
|
if (!['http:', 'https:'].includes(endpoint.protocol)) {
|
|
110
112
|
throw new Error('Resolved SMART token endpoint must use HTTP(S).');
|
|
111
113
|
}
|
|
112
|
-
if (!endpoint.pathname.endsWith(
|
|
113
|
-
throw new Error(
|
|
114
|
+
if (!endpoint.pathname.endsWith(smartTokenRouteSuffix(SmartPostDcrActions.Token))) {
|
|
115
|
+
throw new Error(`Resolved SMART token endpoint must target ${smartTokenRouteSuffix(SmartPostDcrActions.Token)}.`);
|
|
114
116
|
}
|
|
115
117
|
return endpoint.toString();
|
|
116
118
|
}
|
|
117
119
|
function smartTokenPollEndpointFromTokenEndpoint(tokenEndpoint) {
|
|
118
120
|
const endpoint = new URL(tokenEndpoint);
|
|
119
|
-
endpoint.pathname = endpoint.pathname.
|
|
121
|
+
endpoint.pathname = `${endpoint.pathname.slice(0, -smartTokenRouteSuffix(SmartPostDcrActions.Token).length)}${smartTokenRouteSuffix(SmartPostDcrActions.TokenResponse)}`;
|
|
120
122
|
return endpoint.toString();
|
|
121
123
|
}
|
|
124
|
+
function smartTokenRouteSuffix(action) {
|
|
125
|
+
return `/${GatewayRouteSections.Identity}/${GatewayRouteFormats.OpenId}/${IdentityAuthResourceTypes.Smart}/${action}`;
|
|
126
|
+
}
|
|
122
127
|
async function resolveClientAssertion(input) {
|
|
123
128
|
const explicit = String(input.clientAssertion || '').trim();
|
|
124
129
|
if (explicit)
|
|
@@ -132,13 +137,17 @@ async function resolveClientAssertion(input) {
|
|
|
132
137
|
});
|
|
133
138
|
}
|
|
134
139
|
function resolveTokenExchangeResult(exchange, normalizedScopes, tokenCacheKey, setTokenCache) {
|
|
135
|
-
const
|
|
140
|
+
const pollResult = exchange.poll;
|
|
141
|
+
const pollPayload = pollResult.body ?? {};
|
|
142
|
+
// Encrypted DIDComm returns the token in the authenticated message body;
|
|
143
|
+
// plain compatibility transports may still expose the token at the root.
|
|
144
|
+
const exchangeBody = pollPayload.body || pollPayload;
|
|
136
145
|
const accessToken = String(exchangeBody.access_token || '').trim();
|
|
137
|
-
if (
|
|
146
|
+
if (pollResult.status >= 400 || !accessToken) {
|
|
138
147
|
return {
|
|
139
148
|
status: 'failed',
|
|
140
|
-
statusCode:
|
|
141
|
-
response:
|
|
149
|
+
statusCode: pollResult.status,
|
|
150
|
+
response: exchangeBody,
|
|
142
151
|
};
|
|
143
152
|
}
|
|
144
153
|
const tokenType = String(exchangeBody.token_type || 'Bearer');
|
|
@@ -156,8 +165,8 @@ function resolveTokenExchangeResult(exchange, normalizedScopes, tokenCacheKey, s
|
|
|
156
165
|
accessToken,
|
|
157
166
|
tokenType,
|
|
158
167
|
scopes: resolvedScopes,
|
|
159
|
-
statusCode:
|
|
160
|
-
response:
|
|
168
|
+
statusCode: pollResult.status,
|
|
169
|
+
response: exchangeBody,
|
|
161
170
|
};
|
|
162
171
|
}
|
|
163
172
|
function createRuntimeUuid() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdc-sdk-node-ts",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.0",
|
|
4
4
|
"description": "Next-generation Node runtime package for the GDC SDK family",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Antifraud Services Inc.",
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
"test:e2e:live-gw:clean": "bash ./scripts/run-live-gw-clean.sh"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"gdc-common-utils-ts": "2.
|
|
42
|
-
"gdc-sdk-core-ts": "2.
|
|
41
|
+
"gdc-common-utils-ts": "2.8.1",
|
|
42
|
+
"gdc-sdk-core-ts": "2.5.1"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@types/node": "^20.14.10",
|