gdc-sdk-node-ts 2.4.35 → 2.4.37
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 +22 -0
- package/dist/device-activation.js +58 -0
- package/dist/digital-twin.d.ts +12 -0
- package/dist/digital-twin.js +37 -2
- package/dist/individual-controller-backend-runtime.d.ts +2 -2
- package/dist/individual-onboarding.d.ts +14 -1
- package/dist/individual-onboarding.js +16 -1
- package/dist/node-runtime-client.d.ts +5 -3
- package/dist/node-runtime-client.js +12 -1
- package/dist/orchestration/client-port.d.ts +2 -2
- package/dist/orchestration/digital-twin-sdk.d.ts +8 -1
- package/dist/orchestration/digital-twin-sdk.js +8 -1
- package/dist/orchestration/individual-controller-sdk.d.ts +2 -2
- package/dist/runtime-client-paths.d.ts +2 -0
- package/dist/runtime-client-paths.js +3 -1
- package/dist/runtime-paths.d.ts +4 -0
- package/dist/runtime-paths.js +8 -0
- package/dist/server-profile-session.d.ts +25 -0
- package/dist/server-profile-session.js +105 -0
- package/package.json +2 -2
- package/dist/organization-registration-authorization.d.ts +0 -16
- package/dist/organization-registration-authorization.js +0 -34
|
@@ -68,6 +68,28 @@ export type EmployeeDeviceActivationResult = {
|
|
|
68
68
|
exchange: SubmitAndPollResult;
|
|
69
69
|
dcr: SubmitAndPollResult;
|
|
70
70
|
};
|
|
71
|
+
export type EmployeeDeviceOtpRecoveryInput = Readonly<{
|
|
72
|
+
idToken: string;
|
|
73
|
+
clientInstanceId: string;
|
|
74
|
+
pollOptions?: PollOptions;
|
|
75
|
+
}>;
|
|
76
|
+
export type EmployeeDeviceOtpRecoveryResult = Readonly<{
|
|
77
|
+
activationCode: string;
|
|
78
|
+
licenseId: string;
|
|
79
|
+
employeeRole: string;
|
|
80
|
+
employeeActorIdentifier: string;
|
|
81
|
+
recovery: SubmitAndPollResult;
|
|
82
|
+
}>;
|
|
83
|
+
/** Requests a fresh replacement credential for one OTP-authenticated installation. */
|
|
84
|
+
export declare function recoverEmployeeDeviceWithOtpWithDeps(deps: Readonly<{
|
|
85
|
+
routeCtx: RouteContext;
|
|
86
|
+
input: EmployeeDeviceOtpRecoveryInput;
|
|
87
|
+
identityEmployeeRecoveryPath: (ctx: RouteContext) => string;
|
|
88
|
+
identityEmployeeRecoveryPollPath: (ctx: RouteContext) => string;
|
|
89
|
+
submitAndPollWithBearerToken: (bearerToken: string | undefined, submitPath: string, pollPath: string, payload: {
|
|
90
|
+
thid?: string;
|
|
91
|
+
} & Record<string, unknown>, pollOptions?: PollOptions) => Promise<SubmitAndPollResult>;
|
|
92
|
+
}>): Promise<EmployeeDeviceOtpRecoveryResult>;
|
|
71
93
|
export type EmployeeDeviceRevocationInput = {
|
|
72
94
|
licenseId: string;
|
|
73
95
|
clientId: string;
|
|
@@ -56,6 +56,26 @@ export function createProfileDeviceActivationRequest(input) {
|
|
|
56
56
|
};
|
|
57
57
|
return draft;
|
|
58
58
|
}
|
|
59
|
+
/** Requests a fresh replacement credential for one OTP-authenticated installation. */
|
|
60
|
+
export async function recoverEmployeeDeviceWithOtpWithDeps(deps) {
|
|
61
|
+
const idToken = requiredText(deps.input.idToken, 'fresh OTP identity token');
|
|
62
|
+
const clientInstanceId = requiredText(deps.input.clientInstanceId, 'client instance id');
|
|
63
|
+
const recovery = await deps.submitAndPollWithBearerToken(idToken, deps.identityEmployeeRecoveryPath(deps.routeCtx), deps.identityEmployeeRecoveryPollPath(deps.routeCtx), { thid: `employee-recovery-${createRuntimeUuid()}`, client_instance_id: clientInstanceId }, deps.input.pollOptions);
|
|
64
|
+
const failedStatus = [recovery.submit.status, recovery.poll.status]
|
|
65
|
+
.find(status => status < 200 || status >= 300);
|
|
66
|
+
if (failedStatus !== undefined) {
|
|
67
|
+
throw new Error(`recoverEmployeeDeviceWithOtp: recovery failed (HTTP ${failedStatus}).`);
|
|
68
|
+
}
|
|
69
|
+
const terminal = responseBody(recovery.poll.body);
|
|
70
|
+
const activationCode = findResponseText(terminal, 'activation_code');
|
|
71
|
+
const licenseId = findResponseText(terminal, 'license_id');
|
|
72
|
+
const employeeRole = findResponseText(terminal, 'employee_role');
|
|
73
|
+
const employeeActorIdentifier = findResponseText(terminal, 'employee_same_as');
|
|
74
|
+
if (!activationCode || !licenseId || !employeeRole || !employeeActorIdentifier) {
|
|
75
|
+
throw new Error('recoverEmployeeDeviceWithOtp: incomplete recovery response.');
|
|
76
|
+
}
|
|
77
|
+
return { activationCode, licenseId, employeeRole, employeeActorIdentifier, recovery };
|
|
78
|
+
}
|
|
59
79
|
export async function revokeEmployeeDeviceWithDeps(deps) {
|
|
60
80
|
const licenseId = String(deps.input.licenseId || '').trim();
|
|
61
81
|
const clientId = String(deps.input.clientId || '').trim();
|
|
@@ -147,6 +167,44 @@ function resolveDcrPayload(input) {
|
|
|
147
167
|
return { ...input.dcrPayload };
|
|
148
168
|
throw new Error('Device activation requires a device registration built by createProfileDeviceActivationRequest.');
|
|
149
169
|
}
|
|
170
|
+
function responseBody(value) {
|
|
171
|
+
if (typeof value === 'string') {
|
|
172
|
+
try {
|
|
173
|
+
return responseBody(JSON.parse(value));
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
return {};
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
180
|
+
return {};
|
|
181
|
+
const record = value;
|
|
182
|
+
return record.body && typeof record.body === 'object' && !Array.isArray(record.body)
|
|
183
|
+
? record.body
|
|
184
|
+
: record;
|
|
185
|
+
}
|
|
186
|
+
function findResponseText(value, key) {
|
|
187
|
+
if (!value || typeof value !== 'object')
|
|
188
|
+
return '';
|
|
189
|
+
if (Array.isArray(value)) {
|
|
190
|
+
for (const child of value) {
|
|
191
|
+
const found = findResponseText(child, key);
|
|
192
|
+
if (found)
|
|
193
|
+
return found;
|
|
194
|
+
}
|
|
195
|
+
return '';
|
|
196
|
+
}
|
|
197
|
+
const record = value;
|
|
198
|
+
const direct = normalizedText(record[key]);
|
|
199
|
+
if (direct)
|
|
200
|
+
return direct;
|
|
201
|
+
for (const child of Object.values(record)) {
|
|
202
|
+
const found = findResponseText(child, key);
|
|
203
|
+
if (found)
|
|
204
|
+
return found;
|
|
205
|
+
}
|
|
206
|
+
return '';
|
|
207
|
+
}
|
|
150
208
|
function normalizedText(value) {
|
|
151
209
|
return typeof value === 'string' ? value.trim() : '';
|
|
152
210
|
}
|
package/dist/digital-twin.d.ts
CHANGED
|
@@ -10,6 +10,9 @@ export declare function assertDigitalTwinSubjectId(value: unknown): asserts valu
|
|
|
10
10
|
/** Search parameters added by the digital-twin Composition profile. */
|
|
11
11
|
export declare const DigitalTwinSearchParameter: Readonly<{
|
|
12
12
|
Section: "section";
|
|
13
|
+
DateFrom: "date-from";
|
|
14
|
+
DateTo: "date-to";
|
|
15
|
+
Text: "text";
|
|
13
16
|
MetaTag: "Composition.meta-tag";
|
|
14
17
|
}>;
|
|
15
18
|
export type DigitalTwinSearchInput = {
|
|
@@ -18,6 +21,15 @@ export type DigitalTwinSearchInput = {
|
|
|
18
21
|
thid?: string;
|
|
19
22
|
format?: DigitalTwinFhirFormat;
|
|
20
23
|
resourceType?: string;
|
|
24
|
+
/** One or more IPS section tokens. Basic search uses OR across sections. */
|
|
25
|
+
sections?: readonly string[];
|
|
26
|
+
/** Inclusive clinical-event lower bound as ISO date or dateTime. */
|
|
27
|
+
dateFrom?: string;
|
|
28
|
+
/** Inclusive upper bound. When omitted, GW resolves its current time. */
|
|
29
|
+
dateTo?: string;
|
|
30
|
+
/** Case- and accent-insensitive text matched against GW's private derived index. */
|
|
31
|
+
text?: string;
|
|
32
|
+
/** Advanced/compatibility filters. Do not combine with basic-search fields. */
|
|
21
33
|
filters?: Readonly<Record<string, string | readonly string[] | undefined>>;
|
|
22
34
|
pollOptions?: PollOptions;
|
|
23
35
|
};
|
package/dist/digital-twin.js
CHANGED
|
@@ -16,6 +16,9 @@ export function assertDigitalTwinSubjectId(value) {
|
|
|
16
16
|
/** Search parameters added by the digital-twin Composition profile. */
|
|
17
17
|
export const DigitalTwinSearchParameter = Object.freeze({
|
|
18
18
|
Section: 'section',
|
|
19
|
+
DateFrom: 'date-from',
|
|
20
|
+
DateTo: 'date-to',
|
|
21
|
+
Text: 'text',
|
|
19
22
|
MetaTag: 'Composition.meta-tag',
|
|
20
23
|
});
|
|
21
24
|
/**
|
|
@@ -132,10 +135,42 @@ export async function searchDigitalTwinsWithDeps(ctx, input, deps) {
|
|
|
132
135
|
const resourceType = String(input.resourceType || 'Composition').trim();
|
|
133
136
|
if (!resourceType)
|
|
134
137
|
throw new Error('Digital twin resourceType is required.');
|
|
135
|
-
const
|
|
138
|
+
const usesBasicSearch = Boolean(input.sections || input.dateFrom || input.dateTo || input.text);
|
|
139
|
+
if (usesBasicSearch && Object.keys(input.filters || {}).length > 0) {
|
|
140
|
+
throw new Error('Digital twin basic search cannot be combined with advanced filters.');
|
|
141
|
+
}
|
|
142
|
+
const sections = (input.sections || []).map((value) => String(value || '').trim()).filter(Boolean);
|
|
143
|
+
const dateFrom = String(input.dateFrom || '').trim();
|
|
144
|
+
const dateTo = String(input.dateTo || '').trim();
|
|
145
|
+
const searchText = String(input.text || '').trim();
|
|
146
|
+
if (usesBasicSearch) {
|
|
147
|
+
if (sections.length === 0)
|
|
148
|
+
throw new Error('Digital twin basic search requires at least one section.');
|
|
149
|
+
if (!dateFrom)
|
|
150
|
+
throw new Error('Digital twin basic search requires dateFrom.');
|
|
151
|
+
if (!searchText)
|
|
152
|
+
throw new Error('Digital twin basic search requires text.');
|
|
153
|
+
const fromMs = Date.parse(dateFrom);
|
|
154
|
+
const toMs = dateTo ? Date.parse(dateTo) : undefined;
|
|
155
|
+
if (Number.isNaN(fromMs))
|
|
156
|
+
throw new Error('Digital twin basic search dateFrom must be an ISO date or dateTime.');
|
|
157
|
+
if (dateTo && Number.isNaN(toMs))
|
|
158
|
+
throw new Error('Digital twin basic search dateTo must be an ISO date or dateTime.');
|
|
159
|
+
if (toMs !== undefined && toMs < fromMs)
|
|
160
|
+
throw new Error('Digital twin basic search dateTo must be on or after dateFrom.');
|
|
161
|
+
}
|
|
162
|
+
const advancedParameters = Object.entries(input.filters || {}).flatMap(([name, value]) => {
|
|
136
163
|
const values = Array.isArray(value) ? value : value === undefined ? [] : [value];
|
|
137
164
|
return values.map((item) => ({ name, valueString: String(item) }));
|
|
138
165
|
});
|
|
166
|
+
const parameters = usesBasicSearch
|
|
167
|
+
? [
|
|
168
|
+
...sections.map((section) => ({ name: DigitalTwinSearchParameter.Section, valueString: section })),
|
|
169
|
+
{ name: DigitalTwinSearchParameter.DateFrom, valueDate: dateFrom },
|
|
170
|
+
...(dateTo ? [{ name: DigitalTwinSearchParameter.DateTo, valueDate: dateTo }] : []),
|
|
171
|
+
{ name: DigitalTwinSearchParameter.Text, valueString: searchText },
|
|
172
|
+
]
|
|
173
|
+
: advancedParameters;
|
|
139
174
|
const thid = String(input.thid || randomUUID());
|
|
140
175
|
const body = format === 'org.hl7.fhir.api'
|
|
141
176
|
? {
|
|
@@ -145,7 +180,7 @@ export async function searchDigitalTwinsWithDeps(ctx, input, deps) {
|
|
|
145
180
|
meta: {
|
|
146
181
|
claims: Object.fromEntries([
|
|
147
182
|
['@context', format],
|
|
148
|
-
...parameters.map((parameter) => [parameter.name, parameter.valueString]),
|
|
183
|
+
...parameters.map((parameter) => [parameter.name, parameter.valueString || parameter.valueDate || '']),
|
|
149
184
|
]),
|
|
150
185
|
},
|
|
151
186
|
}],
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { SubmitAndPollResult } from 'gdc-sdk-core-ts';
|
|
2
2
|
import type { FamilyOrganizationSummary } from 'gdc-common-utils-ts/utils/family-organization-summary';
|
|
3
|
-
import type { IndividualOrganizationConfirmOrderInput, RouteContext } from './individual-onboarding.js';
|
|
3
|
+
import type { IndividualOrganizationConfirmOrderInput, IndividualOrganizationOrderResult, RouteContext } from './individual-onboarding.js';
|
|
4
4
|
import type { IndividualOrganizationBootstrapInput, IndividualOrganizationStartResult } from './individual-start.js';
|
|
5
5
|
import type { EnsureFamilyOrganizationRegistrationInput, EnsureFamilyOrganizationRegistrationResult } from './family-organization-registration.js';
|
|
6
6
|
import type { FamilyOrganizationSearchInput } from './family-organization-search.js';
|
|
@@ -45,7 +45,7 @@ export declare class IndividualControllerBackendRuntime {
|
|
|
45
45
|
/**
|
|
46
46
|
* Confirms the order returned by the individual bootstrap flow.
|
|
47
47
|
*/
|
|
48
|
-
confirmIndividualOrganizationOrder(profile: BackendIndividualControllerProfile, input: IndividualOrganizationConfirmOrderInput): Promise<
|
|
48
|
+
confirmIndividualOrganizationOrder(profile: BackendIndividualControllerProfile, input: IndividualOrganizationConfirmOrderInput): Promise<IndividualOrganizationOrderResult>;
|
|
49
49
|
/**
|
|
50
50
|
* Reads the authoritative subject document through the canonical
|
|
51
51
|
* `Communication -> Subject/$summary -> FHIR Parameters` lifecycle.
|
|
@@ -21,6 +21,13 @@ export type IndividualOrganizationConfirmOrderInput = {
|
|
|
21
21
|
timeoutSeconds?: number;
|
|
22
22
|
intervalSeconds?: number;
|
|
23
23
|
};
|
|
24
|
+
/**
|
|
25
|
+
* Terminal individual Order result with the opaque code required by the
|
|
26
|
+
* subsequent managed-wallet activation and DCR flow.
|
|
27
|
+
*/
|
|
28
|
+
export type IndividualOrganizationOrderResult = SubmitAndPollResult & Readonly<{
|
|
29
|
+
activationCode: string;
|
|
30
|
+
}>;
|
|
24
31
|
type ConfirmIndividualOrganizationOrderDeps = {
|
|
25
32
|
input: IndividualOrganizationConfirmOrderInput;
|
|
26
33
|
routeCtx: RouteContext;
|
|
@@ -32,5 +39,11 @@ type ConfirmIndividualOrganizationOrderDeps = {
|
|
|
32
39
|
thid?: string;
|
|
33
40
|
} & Record<string, unknown>, options?: PollOptions) => Promise<SubmitAndPollResult>;
|
|
34
41
|
};
|
|
35
|
-
export declare function confirmIndividualOrganizationOrderWithDeps(deps: ConfirmIndividualOrganizationOrderDeps): Promise<
|
|
42
|
+
export declare function confirmIndividualOrganizationOrderWithDeps(deps: ConfirmIndividualOrganizationOrderDeps): Promise<IndividualOrganizationOrderResult>;
|
|
43
|
+
/**
|
|
44
|
+
* Reads the opaque controller activation code from a completed individual
|
|
45
|
+
* Order. Integrators should consume `result.activationCode` instead of calling
|
|
46
|
+
* this reader directly; it remains public for response-adapter compatibility.
|
|
47
|
+
*/
|
|
48
|
+
export declare function readIndividualOrganizationActivationCode(responseBody: unknown): string | undefined;
|
|
36
49
|
export {};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
|
|
2
|
+
import { extractPrimaryClaims } from 'gdc-common-utils-ts';
|
|
2
3
|
import { resolvePollOptionsFromSeconds } from './poll-options.js';
|
|
3
4
|
export async function confirmIndividualOrganizationOrderWithDeps(deps) {
|
|
4
5
|
/**
|
|
@@ -35,7 +36,21 @@ export async function confirmIndividualOrganizationOrderWithDeps(deps) {
|
|
|
35
36
|
timeoutMs: deps.defaultTimeoutMs,
|
|
36
37
|
intervalMs: deps.defaultIntervalMs,
|
|
37
38
|
});
|
|
38
|
-
|
|
39
|
+
const order = await deps.submitAndPoll(deps.individualFamilyOrderBatchPath(deps.routeCtx), deps.individualFamilyOrderPollPath(deps.routeCtx), payload, pollOptions);
|
|
40
|
+
const activationCode = readIndividualOrganizationActivationCode(order.poll.body);
|
|
41
|
+
if (!activationCode) {
|
|
42
|
+
throw new Error('confirmIndividualOrganizationOrder failed: missing controller activation code in GW Order response.');
|
|
43
|
+
}
|
|
44
|
+
return { ...order, activationCode };
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Reads the opaque controller activation code from a completed individual
|
|
48
|
+
* Order. Integrators should consume `result.activationCode` instead of calling
|
|
49
|
+
* this reader directly; it remains public for response-adapter compatibility.
|
|
50
|
+
*/
|
|
51
|
+
export function readIndividualOrganizationActivationCode(responseBody) {
|
|
52
|
+
const claims = extractPrimaryClaims(responseBody);
|
|
53
|
+
return String(claims['org.schema.IndividualProduct.serialNumber'] || '').trim() || undefined;
|
|
39
54
|
}
|
|
40
55
|
function createRuntimeUuid() {
|
|
41
56
|
const fromCrypto = globalThis.crypto?.randomUUID?.();
|
|
@@ -5,11 +5,11 @@ import { type ResolvedAppInfo, type CommunicationClinicalFormatRenderers, type S
|
|
|
5
5
|
import { type HostRouteContext, type HostedTenantLifecycleInput } from './host-onboarding.js';
|
|
6
6
|
import type { HostedTenantDescendantKind } from './host-onboarding.js';
|
|
7
7
|
import type { NodeLegalOrganizationVerificationTransactionInput, NodeOrganizationDidBindingInput, NodeOrganizationActivationInput } from './orchestration/client-port.js';
|
|
8
|
-
import { type IndividualOrganizationConfirmOrderInput, type RouteContext } from './individual-onboarding.js';
|
|
8
|
+
import { type IndividualOrganizationConfirmOrderInput, type IndividualOrganizationOrderResult, type RouteContext } from './individual-onboarding.js';
|
|
9
9
|
import { type EnsureFamilyOrganizationRegistrationInput } from './family-organization-registration.js';
|
|
10
10
|
import { type FamilyOrganizationSearchInput } from './family-organization-search.js';
|
|
11
11
|
import { type SmartTokenRequestInput } from './smart-token.js';
|
|
12
|
-
import { type EmployeeDeviceActivationRequestInput, type EmployeeDeviceActivationResult, type EmployeeDeviceRevocationInput } from './device-activation.js';
|
|
12
|
+
import { type EmployeeDeviceActivationRequestInput, type EmployeeDeviceActivationResult, type EmployeeDeviceOtpRecoveryInput, type EmployeeDeviceOtpRecoveryResult, type EmployeeDeviceRevocationInput } from './device-activation.js';
|
|
13
13
|
import { type OrganizationLicenseOrderConfirmInput } from './organization-license-order.js';
|
|
14
14
|
import { type IndividualOrganizationBootstrapInput, type IndividualOrganizationStartResult } from './individual-start.js';
|
|
15
15
|
import { type CommunicationIngestionInput, type BlockchainArtifactRegistrationInput, type VitalSignBatchCommunicationFromSearchResponseInput, type CommunicationParticipantRuntimeSearchInput, type ClinicalBundleSearchInput, type ClinicalSectionUpdateInput, type ClinicalSummaryReadResult, type ClinicalSummaryRequestInput, type ClinicalSummaryUpdateInput, type GrantProfessionalAccessInput, type GrantProfessionalAccessResult, type DigitalTwinSecondaryUseConsentInput, type DigitalTwinSecondaryUseConsentResult, type DigitalTwinSubjectLinkPurgeInput, type DigitalTwinSubjectLinkPurgeResult, type IndividualMemberLifecycleInput, type IndividualMemberLicenseAddInput, type IndividualMemberLicenseInvitationInput, type IndividualMemberLicenseTransitionInput, type LicenseListRuntimeSearchInput, type LicenseOfferRuntimeSearchInput, type LicenseOrderRuntimeSearchInput, type OrganizationEmployeeCreationInput, type OrganizationEmployeeLicenseAddInput, type OrganizationEmployeeLicenseOfferInput, type OrganizationEmployeeLicenseInvitationInput, type OrganizationEmployeeLifecycleInput, type OrganizationEmployeeSearchInput, type ProfessionalAccessRequestInput, type ProfessionalAccessRequestResult, type RevokeProfessionalAccessInput, type RevokeProfessionalAccessResult, type RelatedPersonUpsertInput } from './resource-operations.js';
|
|
@@ -376,7 +376,7 @@ export declare class HttpRuntimeClient implements NodeRuntimeClient {
|
|
|
376
376
|
/**
|
|
377
377
|
* Confirms the order returned by `startIndividualOrganization(...)`.
|
|
378
378
|
*/
|
|
379
|
-
confirmIndividualOrganizationOrder(input: IndividualOrganizationConfirmOrderInput): Promise<
|
|
379
|
+
confirmIndividualOrganizationOrder(input: IndividualOrganizationConfirmOrderInput): Promise<IndividualOrganizationOrderResult>;
|
|
380
380
|
/**
|
|
381
381
|
* Disables a hosted individual/family organization without releasing licenses.
|
|
382
382
|
*
|
|
@@ -465,6 +465,8 @@ export declare class HttpRuntimeClient implements NodeRuntimeClient {
|
|
|
465
465
|
* The implementation is actor-neutral and shares the profile-device flow.
|
|
466
466
|
*/
|
|
467
467
|
activateEmployeeDeviceWithActivationRequest(input: EmployeeDeviceActivationRequestInput): Promise<EmployeeDeviceActivationResult>;
|
|
468
|
+
/** Obtains a replacement activation credential after fresh OTP verification. */
|
|
469
|
+
recoverEmployeeDeviceWithOtp(input: EmployeeDeviceOtpRecoveryInput & Partial<RouteContext>): Promise<EmployeeDeviceOtpRecoveryResult>;
|
|
468
470
|
/**
|
|
469
471
|
* Creates or updates a `RelatedPerson` for non-employee family/caregiver
|
|
470
472
|
* roles such as a grandfather, guardian, or external caregiver.
|
|
@@ -6,7 +6,7 @@ import { confirmIndividualOrganizationOrderWithDeps, } from './individual-onboar
|
|
|
6
6
|
import { ensureFamilyOrganizationRegistrationWithDeps, } from './family-organization-registration.js';
|
|
7
7
|
import { searchFamilyOrganizationWithDeps } from './family-organization-search.js';
|
|
8
8
|
import { requestSmartTokenWithDeps } from './smart-token.js';
|
|
9
|
-
import { activateEmployeeDeviceWithActivationCodeWithDeps, activateEmployeeDeviceWithActivationRequestWithDeps, revokeEmployeeDeviceWithDeps, } from './device-activation.js';
|
|
9
|
+
import { activateEmployeeDeviceWithActivationCodeWithDeps, activateEmployeeDeviceWithActivationRequestWithDeps, recoverEmployeeDeviceWithOtpWithDeps, revokeEmployeeDeviceWithDeps, } from './device-activation.js';
|
|
10
10
|
import { extractOfferIdFromResponseBody, extractOfferPreviewFromResponseBody, } from './order-offer-summary.js';
|
|
11
11
|
import { confirmOrganizationLicenseOrderWithDeps } from './organization-license-order.js';
|
|
12
12
|
import { startIndividualOrganizationWithDeps } from './individual-start.js';
|
|
@@ -909,6 +909,17 @@ export class HttpRuntimeClient {
|
|
|
909
909
|
async activateEmployeeDeviceWithActivationRequest(input) {
|
|
910
910
|
return this.activateProfileDeviceWithActivationRequest(input);
|
|
911
911
|
}
|
|
912
|
+
/** Obtains a replacement activation credential after fresh OTP verification. */
|
|
913
|
+
async recoverEmployeeDeviceWithOtp(input) {
|
|
914
|
+
const routeCtx = this.paths.routeCtxFromInput(input);
|
|
915
|
+
return recoverEmployeeDeviceWithOtpWithDeps({
|
|
916
|
+
routeCtx,
|
|
917
|
+
input,
|
|
918
|
+
identityEmployeeRecoveryPath: this.paths.identityEmployeeRecoveryPath.bind(this.paths),
|
|
919
|
+
identityEmployeeRecoveryPollPath: this.paths.identityEmployeeRecoveryPollPath.bind(this.paths),
|
|
920
|
+
submitAndPollWithBearerToken: this.submitAndPollWithBearerToken.bind(this),
|
|
921
|
+
});
|
|
922
|
+
}
|
|
912
923
|
/**
|
|
913
924
|
* Creates or updates a `RelatedPerson` for non-employee family/caregiver
|
|
914
925
|
* roles such as a grandfather, guardian, or external caregiver.
|
|
@@ -9,7 +9,7 @@ import type { EmployeeDeviceActivationResult, EmployeeDeviceActivationRequestInp
|
|
|
9
9
|
import type { OrganizationEmployeeProvisioningInput, OrganizationEmployeeProvisioningResult } from '../organization-employee-lifecycle.js';
|
|
10
10
|
import type { OrganizationEmployeeLifecycleRecord } from 'gdc-common-utils-ts/models/organization-employee-lifecycle';
|
|
11
11
|
import type { HostRouteContext, HostedTenantLifecycleInput, LegalOrganizationOrderInput } from '../host-onboarding.js';
|
|
12
|
-
import type { IndividualOrganizationConfirmOrderInput, RouteContext } from '../individual-onboarding.js';
|
|
12
|
+
import type { IndividualOrganizationConfirmOrderInput, IndividualOrganizationOrderResult, RouteContext } from '../individual-onboarding.js';
|
|
13
13
|
import type { IndividualOrganizationBootstrapInput, IndividualOrganizationStartResult } from '../individual-start.js';
|
|
14
14
|
import type { FamilyOrganizationSearchInput } from '../family-organization-search.js';
|
|
15
15
|
import type { FhirR5SubscriptionTopic } from 'gdc-common-utils-ts/models/fhir-r5-subscription';
|
|
@@ -106,7 +106,7 @@ export type RuntimeClient = {
|
|
|
106
106
|
startIndividualOrganization?: (input: IndividualOrganizationBootstrapInput) => Promise<IndividualOrganizationStartResult>;
|
|
107
107
|
searchFamilyOrganization?: (ctx: RouteContext, input: FamilyOrganizationSearchInput) => Promise<FamilyOrganizationSummary | null>;
|
|
108
108
|
ensureFamilyOrganizationRegistration?: (ctx: RouteContext, input: EnsureFamilyOrganizationRegistrationInput) => Promise<EnsureFamilyOrganizationRegistrationResult>;
|
|
109
|
-
confirmIndividualOrganizationOrder?: (input: IndividualOrganizationConfirmOrderInput) => Promise<
|
|
109
|
+
confirmIndividualOrganizationOrder?: (input: IndividualOrganizationConfirmOrderInput) => Promise<IndividualOrganizationOrderResult>;
|
|
110
110
|
disableIndividual?: (ctx: RouteContext, input: IndividualOrganizationLifecycleInput, pollOptions?: PollOptions) => Promise<SubmitAndPollResult>;
|
|
111
111
|
purgeIndividual?: (ctx: RouteContext, input: IndividualOrganizationLifecycleInput, pollOptions?: PollOptions) => Promise<SubmitAndPollResult>;
|
|
112
112
|
disableIndividualMember?: (ctx: RouteContext, input: IndividualMemberLifecycleInput, pollOptions?: PollOptions) => Promise<SubmitAndPollResult>;
|
|
@@ -10,7 +10,14 @@ export declare class DigitalTwinSdk {
|
|
|
10
10
|
constructor(client: NodeRuntimeClient, actorDid?: string);
|
|
11
11
|
/** Requests the SMART token used by subsequent digital-twin operations. */
|
|
12
12
|
requestSmartToken(input: SmartTokenRequestInput): Promise<SmartTokenExchangeResult>;
|
|
13
|
-
/**
|
|
13
|
+
/**
|
|
14
|
+
* Searches pseudonymous records and exposes matched Compositions directly.
|
|
15
|
+
*
|
|
16
|
+
* MVP basic search supplies `sections`, `dateFrom`, optional `dateTo`, and
|
|
17
|
+
* `text`. GW resolves an omitted `dateTo` to its current time, applies OR
|
|
18
|
+
* across sections and requires text/date to match the same clinical record.
|
|
19
|
+
* Organization/tenant scope comes from the authenticated SMART token.
|
|
20
|
+
*/
|
|
14
21
|
search(ctx: RouteContext, input: DigitalTwinSearchInput): Promise<DigitalTwinSearchResult>;
|
|
15
22
|
/**
|
|
16
23
|
* Saves a tagged researcher-owned working selection for one matching twin.
|
|
@@ -28,7 +28,14 @@ export class DigitalTwinSdk {
|
|
|
28
28
|
this.researcherDid = actorDid;
|
|
29
29
|
return result;
|
|
30
30
|
}
|
|
31
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* Searches pseudonymous records and exposes matched Compositions directly.
|
|
33
|
+
*
|
|
34
|
+
* MVP basic search supplies `sections`, `dateFrom`, optional `dateTo`, and
|
|
35
|
+
* `text`. GW resolves an omitted `dateTo` to its current time, applies OR
|
|
36
|
+
* across sections and requires text/date to match the same clinical record.
|
|
37
|
+
* Organization/tenant scope comes from the authenticated SMART token.
|
|
38
|
+
*/
|
|
32
39
|
async search(ctx, input) {
|
|
33
40
|
const filters = { ...(input.filters || {}) };
|
|
34
41
|
const isPrivateSelectionSearch = Object.keys(filters).some((name) => {
|
|
@@ -3,7 +3,7 @@ import { type NodeRuntimeClient, type PollOptions, type SubmitAndPollResult, typ
|
|
|
3
3
|
import type { FamilyOrganizationSummary } from 'gdc-common-utils-ts/utils/family-organization-summary';
|
|
4
4
|
import type { EnsureFamilyOrganizationRegistrationInput, EnsureFamilyOrganizationRegistrationResult } from '../family-organization-registration.js';
|
|
5
5
|
import type { FamilyOrganizationSearchInput } from '../family-organization-search.js';
|
|
6
|
-
import type { IndividualOrganizationConfirmOrderInput, RouteContext } from '../individual-onboarding.js';
|
|
6
|
+
import type { IndividualOrganizationConfirmOrderInput, IndividualOrganizationOrderResult, RouteContext } from '../individual-onboarding.js';
|
|
7
7
|
import type { IndividualOrganizationBootstrapInput, IndividualOrganizationStartResult } from '../individual-start.js';
|
|
8
8
|
import type { NodeCapability } from '../session.js';
|
|
9
9
|
import type { IndividualOrganizationLifecycleInput } from 'gdc-sdk-core-ts';
|
|
@@ -39,7 +39,7 @@ export declare class IndividualControllerSdk {
|
|
|
39
39
|
/**
|
|
40
40
|
* Confirms the order returned by `startIndividualOrganization(...)`.
|
|
41
41
|
*/
|
|
42
|
-
confirmIndividualOrganizationOrder(input: IndividualOrganizationConfirmOrderInput): Promise<
|
|
42
|
+
confirmIndividualOrganizationOrder(input: IndividualOrganizationConfirmOrderInput): Promise<IndividualOrganizationOrderResult>;
|
|
43
43
|
/**
|
|
44
44
|
* Disables the hosted individual/family organization without freeing licenses.
|
|
45
45
|
*/
|
|
@@ -109,6 +109,8 @@ export declare class RuntimeClientPaths {
|
|
|
109
109
|
digitalTwinCommunicationPollPath(ctx: RouteContext, format: string): string;
|
|
110
110
|
identityTokenExchangePath(ctx: RouteContext): string;
|
|
111
111
|
identityTokenExchangePollPath(ctx: RouteContext): string;
|
|
112
|
+
identityEmployeeRecoveryPath(ctx: RouteContext): string;
|
|
113
|
+
identityEmployeeRecoveryPollPath(ctx: RouteContext): string;
|
|
112
114
|
identityDeviceDcrPath(ctx: RouteContext): string;
|
|
113
115
|
identityDeviceDcrPollPath(ctx: RouteContext): string;
|
|
114
116
|
identityDeviceRevokePath(ctx: RouteContext): string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
|
|
2
2
|
import { GwCoreLifecycleAction } from './constants/lifecycle.js';
|
|
3
|
-
import { buildHostRegistryPath, buildIdentityDeviceDcrPath, buildIdentityDeviceDcrPollPath, buildIdentityDeviceRevokePath, buildIdentityDeviceRevokePollPath, buildIdentityLicenseIssuePath, buildIdentityLicenseIssuePollPath, buildIdentityOpenIdSmartTokenPath, buildIdentityOpenIdSmartTokenPollPath, buildIdentityTokenExchangePath, buildIdentityTokenExchangePollPath, buildOrganizationDidBindingPath, buildOrganizationDidBindingPollPath, buildV1Path, } from './runtime-paths.js';
|
|
3
|
+
import { buildHostRegistryPath, buildIdentityDeviceDcrPath, buildIdentityDeviceDcrPollPath, buildIdentityDeviceRevokePath, buildIdentityDeviceRevokePollPath, buildIdentityEmployeeRecoveryPath, buildIdentityEmployeeRecoveryPollPath, buildIdentityLicenseIssuePath, buildIdentityLicenseIssuePollPath, buildIdentityOpenIdSmartTokenPath, buildIdentityOpenIdSmartTokenPollPath, buildIdentityTokenExchangePath, buildIdentityTokenExchangePollPath, buildOrganizationDidBindingPath, buildOrganizationDidBindingPollPath, buildV1Path, } from './runtime-paths.js';
|
|
4
4
|
import { requireHostRouteContext, requireRouteContext, routeCtxFromInput, } from './runtime-route-context.js';
|
|
5
5
|
export class RuntimeClientPaths {
|
|
6
6
|
constructor(defaultCtx) {
|
|
@@ -123,6 +123,8 @@ export class RuntimeClientPaths {
|
|
|
123
123
|
digitalTwinCommunicationPollPath(ctx, format) { return this.v1Path(ctx, 'digitaltwin', format, 'Communication', '_batch-response'); }
|
|
124
124
|
identityTokenExchangePath(ctx) { return buildIdentityTokenExchangePath(ctx); }
|
|
125
125
|
identityTokenExchangePollPath(ctx) { return buildIdentityTokenExchangePollPath(ctx); }
|
|
126
|
+
identityEmployeeRecoveryPath(ctx) { return buildIdentityEmployeeRecoveryPath(ctx); }
|
|
127
|
+
identityEmployeeRecoveryPollPath(ctx) { return buildIdentityEmployeeRecoveryPollPath(ctx); }
|
|
126
128
|
identityDeviceDcrPath(ctx) { return buildIdentityDeviceDcrPath(ctx); }
|
|
127
129
|
identityDeviceDcrPollPath(ctx) { return buildIdentityDeviceDcrPollPath(ctx); }
|
|
128
130
|
identityDeviceRevokePath(ctx) { return buildIdentityDeviceRevokePath(ctx); }
|
package/dist/runtime-paths.d.ts
CHANGED
|
@@ -6,6 +6,10 @@ export declare function buildOrganizationDidBindingPath(routeCtx: RouteContext):
|
|
|
6
6
|
export declare function buildOrganizationDidBindingPollPath(routeCtx: RouteContext): string;
|
|
7
7
|
export declare function buildIdentityTokenExchangePath(ctx: RouteContext): string;
|
|
8
8
|
export declare function buildIdentityTokenExchangePollPath(ctx: RouteContext): string;
|
|
9
|
+
/** Fresh OTP-assisted replacement credential for one existing installation. */
|
|
10
|
+
export declare function buildIdentityEmployeeRecoveryPath(ctx: RouteContext): string;
|
|
11
|
+
/** Poll endpoint paired with employee wallet recovery. */
|
|
12
|
+
export declare function buildIdentityEmployeeRecoveryPollPath(ctx: RouteContext): string;
|
|
9
13
|
export declare function buildIdentityDeviceDcrPath(ctx: RouteContext): string;
|
|
10
14
|
export declare function buildIdentityDeviceDcrPollPath(ctx: RouteContext): string;
|
|
11
15
|
/** Canonical controller operation that reserves an employee seat activation credential. */
|
package/dist/runtime-paths.js
CHANGED
|
@@ -28,6 +28,14 @@ export function buildIdentityTokenExchangePath(ctx) {
|
|
|
28
28
|
export function buildIdentityTokenExchangePollPath(ctx) {
|
|
29
29
|
return buildIdentityAuthPath(ctx, IdentityAuthActions.ExchangeResponse);
|
|
30
30
|
}
|
|
31
|
+
/** Fresh OTP-assisted replacement credential for one existing installation. */
|
|
32
|
+
export function buildIdentityEmployeeRecoveryPath(ctx) {
|
|
33
|
+
return buildIdentityAuthPath(ctx, '_recover');
|
|
34
|
+
}
|
|
35
|
+
/** Poll endpoint paired with employee wallet recovery. */
|
|
36
|
+
export function buildIdentityEmployeeRecoveryPollPath(ctx) {
|
|
37
|
+
return buildIdentityAuthPath(ctx, '_recover-response');
|
|
38
|
+
}
|
|
31
39
|
export function buildIdentityDeviceDcrPath(ctx) {
|
|
32
40
|
return buildIdentityAuthPath(ctx, IdentityAuthActions.Dcr);
|
|
33
41
|
}
|
|
@@ -45,6 +45,7 @@ export type ServerProfileSessionRecord = Readonly<{
|
|
|
45
45
|
subjectDid: string;
|
|
46
46
|
scopes: string[];
|
|
47
47
|
sealedUnlockedWalletSeed: string;
|
|
48
|
+
sealedUnlockedVpToken?: string;
|
|
48
49
|
sealedAccessToken: string;
|
|
49
50
|
expiresAt: string;
|
|
50
51
|
}>;
|
|
@@ -56,7 +57,18 @@ export type ServerProfileStore = Readonly<{
|
|
|
56
57
|
getSession(sessionId: string): Promise<ServerProfileSessionRecord | undefined>;
|
|
57
58
|
putSession(session: ServerProfileSessionRecord): Promise<void>;
|
|
58
59
|
deleteSession(sessionId: string): Promise<void>;
|
|
60
|
+
/** Required by key rotation so no session can retain the superseded seed. */
|
|
61
|
+
deleteSessionsForProfile?(profileId: string): Promise<void>;
|
|
59
62
|
}>;
|
|
63
|
+
export type ServerEmployeeProfileOtpRotationInput = Readonly<{
|
|
64
|
+
ownerId: string;
|
|
65
|
+
profileId: string;
|
|
66
|
+
idToken: string;
|
|
67
|
+
newPin: string;
|
|
68
|
+
redirectUris: string[];
|
|
69
|
+
clientName: string;
|
|
70
|
+
}>;
|
|
71
|
+
export type ServerEmployeeProfileOtpRotationResult = ServerProfileRecord;
|
|
60
72
|
export type ServerProfileEnrollmentInput = Readonly<{
|
|
61
73
|
/** Stable confidential id of the authenticated portal account that owns and lists this profile. */
|
|
62
74
|
ownerId: string;
|
|
@@ -221,6 +233,13 @@ export declare class ServerProfileSessionManager {
|
|
|
221
233
|
private readonly options;
|
|
222
234
|
constructor(options: ServerProfileSessionManagerOptions);
|
|
223
235
|
enroll(input: ServerProfileEnrollmentInput): Promise<ServerProfileRecord>;
|
|
236
|
+
/**
|
|
237
|
+
* Rotates an employee wallet after a fresh OTP-authenticated GW recovery.
|
|
238
|
+
* The old PIN and seed are never opened: DCR replaces the installation with
|
|
239
|
+
* newly generated keys, all old BFF sessions are removed, and the new wallet
|
|
240
|
+
* is opened once with the new PIN.
|
|
241
|
+
*/
|
|
242
|
+
rotateEmployeeProfileWithOtp(input: ServerEmployeeProfileOtpRotationInput): Promise<ServerEmployeeProfileOtpRotationResult>;
|
|
224
243
|
/**
|
|
225
244
|
* Derives only the public enrollment descriptors for a server-governed
|
|
226
245
|
* recovery seed. This is intended for pre-DCR controller binding requests;
|
|
@@ -249,6 +268,12 @@ export declare class ServerProfileSessionManager {
|
|
|
249
268
|
submitLegalOrganizationIssueWithBootstrapWallet(input: ServerProfileOrganizationIssueInput): Promise<SubmitAndPollResult>;
|
|
250
269
|
listProfiles(ownerId: string): Promise<ServerProfileRecord[]>;
|
|
251
270
|
unlock(input: ServerProfileUnlockInput): Promise<ResolvedServerProfileSession>;
|
|
271
|
+
/**
|
|
272
|
+
* Renews the short SMART bearer while retaining the already-open server
|
|
273
|
+
* wallet session. The caller must supply a fresh authenticated account
|
|
274
|
+
* token; neither the PIN nor wallet keys return to the browser.
|
|
275
|
+
*/
|
|
276
|
+
refreshSession(ownerId: string, sessionId: string, idToken: string): Promise<ResolvedServerProfileSession>;
|
|
252
277
|
resolveSession(ownerId: string, sessionId: string): Promise<ResolvedServerProfileSession>;
|
|
253
278
|
lock(ownerId: string, sessionId: string): Promise<void>;
|
|
254
279
|
private createClient;
|
|
@@ -100,6 +100,53 @@ export class ServerProfileSessionManager {
|
|
|
100
100
|
await this.options.store.putProfile(record);
|
|
101
101
|
return record;
|
|
102
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* Rotates an employee wallet after a fresh OTP-authenticated GW recovery.
|
|
105
|
+
* The old PIN and seed are never opened: DCR replaces the installation with
|
|
106
|
+
* newly generated keys, all old BFF sessions are removed, and the new wallet
|
|
107
|
+
* is opened once with the new PIN.
|
|
108
|
+
*/
|
|
109
|
+
async rotateEmployeeProfileWithOtp(input) {
|
|
110
|
+
const previous = await this.requireOwnedProfile(input.ownerId, input.profileId);
|
|
111
|
+
if (previous.actorKind !== ActorKinds.OrganizationEmployee || previous.actorMode !== 'member') {
|
|
112
|
+
throw new Error('OTP wallet rotation is restricted to organization employee profiles.');
|
|
113
|
+
}
|
|
114
|
+
const clientInstanceId = String(previous.clientInstanceId || '').trim();
|
|
115
|
+
if (!clientInstanceId)
|
|
116
|
+
throw new Error('Employee profile has no registered installation id.');
|
|
117
|
+
if (!this.options.store.deleteSessionsForProfile) {
|
|
118
|
+
throw new Error('Employee wallet rotation requires profile-session invalidation support.');
|
|
119
|
+
}
|
|
120
|
+
const client = this.createClient(previous.routeContext, input.idToken);
|
|
121
|
+
const replacement = await client.recoverEmployeeDeviceWithOtp({
|
|
122
|
+
...previous.routeContext,
|
|
123
|
+
idToken: input.idToken,
|
|
124
|
+
clientInstanceId,
|
|
125
|
+
});
|
|
126
|
+
const profile = await this.enroll({
|
|
127
|
+
ownerId: previous.ownerId,
|
|
128
|
+
profileId: previous.profileId,
|
|
129
|
+
actorKind: previous.actorKind,
|
|
130
|
+
actorMode: previous.actorMode,
|
|
131
|
+
actorDid: previous.actorDid,
|
|
132
|
+
profileDid: previous.profileDid,
|
|
133
|
+
providerDid: previous.providerDid,
|
|
134
|
+
routeContext: previous.routeContext,
|
|
135
|
+
allowedSubjectDids: previous.allowedSubjectDids,
|
|
136
|
+
pin: input.newPin,
|
|
137
|
+
idToken: input.idToken,
|
|
138
|
+
activationCode: replacement.activationCode,
|
|
139
|
+
clientInstanceId,
|
|
140
|
+
redirectUris: input.redirectUris,
|
|
141
|
+
clientName: input.clientName,
|
|
142
|
+
professionalProof: {
|
|
143
|
+
role: replacement.employeeRole,
|
|
144
|
+
sameAs: replacement.employeeActorIdentifier,
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
await this.options.store.deleteSessionsForProfile(profile.profileId);
|
|
148
|
+
return profile;
|
|
149
|
+
}
|
|
103
150
|
/**
|
|
104
151
|
* Derives only the public enrollment descriptors for a server-governed
|
|
105
152
|
* recovery seed. This is intended for pre-DCR controller binding requests;
|
|
@@ -269,11 +316,69 @@ export class ServerProfileSessionManager {
|
|
|
269
316
|
subjectDid: input.subjectDid,
|
|
270
317
|
scopes: unique(input.scopes),
|
|
271
318
|
sealedUnlockedWalletSeed: await this.options.sealer.seal(seed, `${sessionId}:unlocked-wallet-seed`),
|
|
319
|
+
...(vpToken ? {
|
|
320
|
+
sealedUnlockedVpToken: await this.options.sealer.seal(vpToken, `${sessionId}:unlocked-vp-token`),
|
|
321
|
+
} : {}),
|
|
272
322
|
sealedAccessToken: await this.options.sealer.seal(token.accessToken, `${sessionId}:access-token`),
|
|
273
323
|
expiresAt: expiresAt.toISOString(),
|
|
274
324
|
});
|
|
275
325
|
return this.resolveSession(input.ownerId, sessionId);
|
|
276
326
|
}
|
|
327
|
+
/**
|
|
328
|
+
* Renews the short SMART bearer while retaining the already-open server
|
|
329
|
+
* wallet session. The caller must supply a fresh authenticated account
|
|
330
|
+
* token; neither the PIN nor wallet keys return to the browser.
|
|
331
|
+
*/
|
|
332
|
+
async refreshSession(ownerId, sessionId, idToken) {
|
|
333
|
+
const session = await this.options.store.getSession(sessionId);
|
|
334
|
+
if (!session || session.ownerId !== ownerId)
|
|
335
|
+
throw new Error('Profile session not found.');
|
|
336
|
+
if (new Date(session.expiresAt) <= this.now()) {
|
|
337
|
+
await this.options.store.deleteSession(sessionId);
|
|
338
|
+
throw new Error('Profile session expired.');
|
|
339
|
+
}
|
|
340
|
+
const normalizedIdToken = String(idToken || '').trim();
|
|
341
|
+
if (!normalizedIdToken)
|
|
342
|
+
throw new Error('Profile session refresh requires idToken.');
|
|
343
|
+
const profile = await this.requireOwnedProfile(ownerId, session.profileId);
|
|
344
|
+
this.requireSubject(profile, session.subjectDid);
|
|
345
|
+
const seed = await this.options.sealer.unseal(session.sealedUnlockedWalletSeed, `${sessionId}:unlocked-wallet-seed`);
|
|
346
|
+
const vpToken = session.sealedUnlockedVpToken
|
|
347
|
+
? await this.options.sealer.unseal(session.sealedUnlockedVpToken, `${sessionId}:unlocked-vp-token`)
|
|
348
|
+
: undefined;
|
|
349
|
+
const walletKeyDerivationId = profile.walletKeyDerivationId || profile.profileId;
|
|
350
|
+
const wallet = await this.createWallet(walletKeyDerivationId, seed);
|
|
351
|
+
const now = this.now();
|
|
352
|
+
const smartTokenEndpoint = [
|
|
353
|
+
this.options.gatewayBaseUrl.replace(/\/+$/, ''),
|
|
354
|
+
buildIdentityOpenIdSmartTokenPath(profile.routeContext),
|
|
355
|
+
].join('');
|
|
356
|
+
const assertion = await buildWalletClientAssertion(wallet, profile, smartTokenEndpoint, now);
|
|
357
|
+
const token = await this.createClient(profile.routeContext, normalizedIdToken).requestSmartToken({
|
|
358
|
+
...profile.routeContext,
|
|
359
|
+
actorDid: profile.actorDid,
|
|
360
|
+
subjectDid: session.subjectDid,
|
|
361
|
+
clientId: profile.clientId,
|
|
362
|
+
issuer: profile.clientId,
|
|
363
|
+
audience: smartTokenEndpoint,
|
|
364
|
+
idToken: normalizedIdToken,
|
|
365
|
+
vpToken,
|
|
366
|
+
vpTokenFallback: vpToken ? undefined : 'omit',
|
|
367
|
+
clientAssertion: assertion,
|
|
368
|
+
clientAssertionType: 'private_key_jwt',
|
|
369
|
+
smartTokenKind: 'openid-smart',
|
|
370
|
+
acrValues: profileSmartAcrValues(profile.actorKind),
|
|
371
|
+
scopes: unique(session.scopes),
|
|
372
|
+
tokenCacheKey: `profile-refresh:${sessionId}:${now.getTime()}`,
|
|
373
|
+
});
|
|
374
|
+
if (token.status !== 'fetched' || !token.accessToken)
|
|
375
|
+
throw new Error('SMART token refresh failed.');
|
|
376
|
+
await this.options.store.putSession({
|
|
377
|
+
...session,
|
|
378
|
+
sealedAccessToken: await this.options.sealer.seal(token.accessToken, `${sessionId}:access-token`),
|
|
379
|
+
});
|
|
380
|
+
return this.resolveSession(ownerId, sessionId);
|
|
381
|
+
}
|
|
277
382
|
async resolveSession(ownerId, sessionId) {
|
|
278
383
|
const session = await this.options.store.getSession(sessionId);
|
|
279
384
|
if (!session || session.ownerId !== ownerId)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdc-sdk-node-ts",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.37",
|
|
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,7 +38,7 @@
|
|
|
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.5.
|
|
41
|
+
"gdc-common-utils-ts": "2.5.20",
|
|
42
42
|
"gdc-sdk-core-ts": "~2.3.18"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
@@ -1,16 +0,0 @@
|
|
|
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>;
|
|
@@ -1,34 +0,0 @@
|
|
|
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
|
-
}
|