gdc-sdk-node-ts 2.6.7 → 2.7.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/device-activation.d.ts +3 -0
- package/dist/device-activation.js +15 -0
- package/dist/organization-registration-authorization.d.ts +16 -0
- package/dist/organization-registration-authorization.js +34 -0
- package/dist/server-profile-session.d.ts +14 -1
- package/dist/server-profile-session.js +57 -4
- package/dist/smart-token.js +8 -7
- package/package.json +3 -3
|
@@ -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 {
|
|
@@ -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
|
+
}
|
|
@@ -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
|
|
@@ -392,3 +400,8 @@ export declare class ServerProfileSessionManager {
|
|
|
392
400
|
private requireSubject;
|
|
393
401
|
private now;
|
|
394
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;
|
|
@@ -769,6 +791,33 @@ function requireBase64UrlSeed32(seed) {
|
|
|
769
791
|
throw new Error('Profile enrollment walletSeed must be a 32-byte base64url value.');
|
|
770
792
|
}
|
|
771
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
|
+
}
|
|
772
821
|
function requireEnrollment(input) {
|
|
773
822
|
for (const [name, value] of Object.entries({
|
|
774
823
|
ownerId: input.ownerId,
|
|
@@ -789,6 +838,10 @@ function requireEnrollment(input) {
|
|
|
789
838
|
if (input.professionalProof && !String(input.professionalProof.role || '').trim()) {
|
|
790
839
|
throw new Error('Profile enrollment professionalProof requires role.');
|
|
791
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
|
+
}
|
|
792
845
|
if (!input.allowedSubjectDids.length)
|
|
793
846
|
throw new Error('Profile enrollment requires an allowed subject.');
|
|
794
847
|
}
|
package/dist/smart-token.js
CHANGED
|
@@ -137,16 +137,17 @@ async function resolveClientAssertion(input) {
|
|
|
137
137
|
});
|
|
138
138
|
}
|
|
139
139
|
function resolveTokenExchangeResult(exchange, normalizedScopes, tokenCacheKey, setTokenCache) {
|
|
140
|
-
const
|
|
140
|
+
const pollResult = exchange.poll;
|
|
141
|
+
const pollPayload = pollResult.body ?? {};
|
|
141
142
|
// Encrypted DIDComm returns the token in the authenticated message body;
|
|
142
143
|
// plain compatibility transports may still expose the token at the root.
|
|
143
|
-
const exchangeBody =
|
|
144
|
+
const exchangeBody = pollPayload.body || pollPayload;
|
|
144
145
|
const accessToken = String(exchangeBody.access_token || '').trim();
|
|
145
|
-
if (
|
|
146
|
+
if (pollResult.status >= 400 || !accessToken) {
|
|
146
147
|
return {
|
|
147
148
|
status: 'failed',
|
|
148
|
-
statusCode:
|
|
149
|
-
response:
|
|
149
|
+
statusCode: pollResult.status,
|
|
150
|
+
response: exchangeBody,
|
|
150
151
|
};
|
|
151
152
|
}
|
|
152
153
|
const tokenType = String(exchangeBody.token_type || 'Bearer');
|
|
@@ -164,8 +165,8 @@ function resolveTokenExchangeResult(exchange, normalizedScopes, tokenCacheKey, s
|
|
|
164
165
|
accessToken,
|
|
165
166
|
tokenType,
|
|
166
167
|
scopes: resolvedScopes,
|
|
167
|
-
statusCode:
|
|
168
|
-
response:
|
|
168
|
+
statusCode: pollResult.status,
|
|
169
|
+
response: exchangeBody,
|
|
169
170
|
};
|
|
170
171
|
}
|
|
171
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.1",
|
|
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.2"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@types/node": "^20.14.10",
|