gdc-sdk-node-ts 2.3.5 → 2.3.7
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/README.md +16 -13
- package/dist/node-runtime-client.d.ts +7 -0
- package/dist/node-runtime-client.js +2 -0
- package/dist/orchestration/professional-sdk.d.ts +5 -0
- package/dist/orchestration/professional-sdk.js +5 -0
- package/dist/resource-operations.d.ts +8 -4
- package/dist/server-profile-session.js +7 -2
- package/dist/smart-token.d.ts +7 -0
- package/dist/smart-token.js +41 -2
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -66,43 +66,46 @@ If you are integrating this package for the first time, open these in order:
|
|
|
66
66
|
1. [gdc-sdk-core-ts/docs/101-SDK_PACKAGE_BOUNDARIES.md](https://github.com/Global-DataCare/gdc-sdk-core-ts/blob/main/docs/101-SDK_PACKAGE_BOUNDARIES.md)
|
|
67
67
|
Why `core`, `node`, and `front` are separate packages, what belongs in each
|
|
68
68
|
one, and why actor-scoped facades must stay aligned across runtimes.
|
|
69
|
-
|
|
69
|
+
2. [docs/101-SDK_END_TO_END.md](./docs/101-SDK_END_TO_END.md)
|
|
70
70
|
Ordered onboarding guide with end-to-end journeys, copy/paste snippets, and
|
|
71
71
|
the recommended reading path for new backend integrators.
|
|
72
|
-
|
|
72
|
+
3. [docs/101-PROFESSIONAL-CONSENT-SMART.md](./docs/101-PROFESSIONAL-CONSENT-SMART.md)
|
|
73
|
+
Canonical professional DID, consent, VP and SMART flow without literal
|
|
74
|
+
sections or caller-built audience URLs.
|
|
75
|
+
4. [docs/101-SDK_INTEGRATION.md](./docs/101-SDK_INTEGRATION.md)
|
|
73
76
|
Real backend setup plus the public runtime entrypoints:
|
|
74
77
|
`HostOnboardingSdk`, `OrganizationControllerSdk`,
|
|
75
78
|
`IndividualControllerSdk`, `ProfessionalSdk`, route-context usage, and the
|
|
76
79
|
canonical `ProfileRuntime -> loadProfile(...) -> workspace/session -> actor facade -> submit/poll` shape.
|
|
77
|
-
|
|
80
|
+
5. [tests/101-live-full-cycle-bff-runtime.e2e.test.mjs](./tests/101-live-full-cycle-bff-runtime.e2e.test.mjs)
|
|
78
81
|
Canonical live backend/BFF walkthrough on a fresh local GW lifecycle:
|
|
79
82
|
host/tenant activation, employee provisioning, individual bootstrap,
|
|
80
83
|
consent grant, professional SMART token, clinical read, and final cleanup.
|
|
81
|
-
|
|
84
|
+
6. [docs/101-LIVE_GW_LOCAL.md](./docs/101-LIVE_GW_LOCAL.md)
|
|
82
85
|
Exact TTY/local/Docker commands for running the SDK against a real local GW
|
|
83
86
|
CORE, including tenant bootstrap and employee-seat setup.
|
|
84
|
-
|
|
87
|
+
7. [docs/101-DISCOVERY.md](./docs/101-DISCOVERY.md)
|
|
85
88
|
Node/BFF dataspace discovery, hosting-operator resolution, provider
|
|
86
89
|
resolution, and the correct integration boundary for fallback and cache.
|
|
87
|
-
|
|
90
|
+
8. [gdc-sdk-core-ts/docs/101-SDK_FLOWS.md](https://github.com/Global-DataCare/gdc-sdk-core-ts/blob/main/docs/101-SDK_FLOWS.md)
|
|
88
91
|
Actor split and business-flow map across organization, individual,
|
|
89
92
|
permissions, invitation, import, and SMART flows.
|
|
90
|
-
|
|
93
|
+
9. [gwtemplate-node-ts/docs/PORTAL_API_TO_GW_CORE.md](https://github.com/Global-DataCare/gwtemplate-node-ts/blob/main/docs/PORTAL_API_TO_GW_CORE.md)
|
|
91
94
|
Canonical portal/BFF functional map over GW CORE, including the domain
|
|
92
95
|
split between `employees`, `related persons`, `members`, and `consents`.
|
|
93
|
-
|
|
96
|
+
10. [gdc-common-utils-ts/src/examples/](https://github.com/Global-DataCare/gdc-common-utils-ts/tree/main/src/examples)
|
|
94
97
|
Shared payload values used by the docs and tests.
|
|
95
|
-
|
|
98
|
+
11. [gdc-common-utils-ts/docs/101-LIFECYCLE.md](https://github.com/Global-DataCare/gdc-common-utils-ts/blob/main/docs/101-LIFECYCLE.md)
|
|
96
99
|
Canonical `enable/disable/delete` semantics and copy/paste placeholders.
|
|
97
|
-
|
|
100
|
+
12. [tests/101-backend-profile-runtime.test.mjs](./tests/101-backend-profile-runtime.test.mjs)
|
|
98
101
|
Technical runtime slice for profile/device/session orchestration internals.
|
|
99
102
|
Read this after the public actor SDK guides, not before them.
|
|
100
|
-
|
|
103
|
+
13. [tests/101-individual-controller-backend-runtime.test.mjs](./tests/101-individual-controller-backend-runtime.test.mjs)
|
|
101
104
|
Technical wrapper slice around the generic profile runtime. This is not the
|
|
102
105
|
main onboarding path for new integrators.
|
|
103
|
-
|
|
106
|
+
14. [docs/V2_INDIVIDUAL_REGISTRATION_RECONCILIATION.md](./docs/V2_INDIVIDUAL_REGISTRATION_RECONCILIATION.md)
|
|
104
107
|
Historical reconciliation note for the current CORE registration baseline.
|
|
105
|
-
|
|
108
|
+
15. [docs/NEXT_STEPS.md](./docs/NEXT_STEPS.md)
|
|
106
109
|
Follow-up scope after GW CORE live validation, including the future user job
|
|
107
110
|
manager boundary.
|
|
108
111
|
|
|
@@ -57,6 +57,12 @@ export type HttpRuntimeClientOptions = {
|
|
|
57
57
|
communicationFormatRenderers?: CommunicationClinicalFormatRenderers;
|
|
58
58
|
/** Injectable fetch implementation for BFF adapters and deterministic tests. */
|
|
59
59
|
fetchImpl?: typeof fetch;
|
|
60
|
+
/**
|
|
61
|
+
* Optional trusted provider-index/DID resolver for subject-scoped SMART
|
|
62
|
+
* requests. The runtime uses the resolved endpoint for transport and JWT
|
|
63
|
+
* audience; application-level callers do not need to pass `audience`.
|
|
64
|
+
*/
|
|
65
|
+
smartTokenEndpointResolver?: (subjectDid: string) => Promise<string | undefined>;
|
|
60
66
|
};
|
|
61
67
|
/**
|
|
62
68
|
* @deprecated Prefer `HttpRuntimeClientOptions`.
|
|
@@ -82,6 +88,7 @@ export declare class HttpRuntimeClient implements NodeRuntimeClient {
|
|
|
82
88
|
private readonly secureTransportAdapter?;
|
|
83
89
|
private readonly communicationFormatRenderers;
|
|
84
90
|
private readonly fetchImpl?;
|
|
91
|
+
private readonly smartTokenEndpointResolver?;
|
|
85
92
|
private readonly httpTraceFile?;
|
|
86
93
|
private readonly tokenCache;
|
|
87
94
|
private readonly paths;
|
|
@@ -68,6 +68,7 @@ export class HttpRuntimeClient {
|
|
|
68
68
|
this.secureTransportAdapter = options.secureTransportAdapter;
|
|
69
69
|
this.communicationFormatRenderers = options.communicationFormatRenderers || {};
|
|
70
70
|
this.fetchImpl = options.fetchImpl;
|
|
71
|
+
this.smartTokenEndpointResolver = options.smartTokenEndpointResolver;
|
|
71
72
|
this.httpTraceFile = String(process.env.SDK_HTTP_TRACE_FILE || '').trim() || undefined;
|
|
72
73
|
this.paths = new RuntimeClientPaths(this.ctx);
|
|
73
74
|
}
|
|
@@ -690,6 +691,7 @@ export class HttpRuntimeClient {
|
|
|
690
691
|
identityTokenExchangePollPath: this.paths.identityTokenExchangePollPath.bind(this.paths),
|
|
691
692
|
identityOpenIdSmartTokenPath: this.paths.identityOpenIdSmartTokenPath.bind(this.paths),
|
|
692
693
|
identityOpenIdSmartTokenPollPath: this.paths.identityOpenIdSmartTokenPollPath.bind(this.paths),
|
|
694
|
+
resolveSmartTokenEndpoint: this.smartTokenEndpointResolver,
|
|
693
695
|
submitAndPoll: this.submitAndPoll.bind(this),
|
|
694
696
|
setTokenCache: (tokenCacheKey, token) => this.tokenCache.set(tokenCacheKey, token),
|
|
695
697
|
});
|
|
@@ -26,6 +26,11 @@ export declare class ProfessionalSdk {
|
|
|
26
26
|
* - `subjectDid`
|
|
27
27
|
* - requested `scopes`
|
|
28
28
|
*
|
|
29
|
+
* The actor DID must be the same identifier used by the consent grant and
|
|
30
|
+
* the professional VP. High-level callers omit `audience`; the runtime
|
|
31
|
+
* resolves the subject provider's SMART endpoint or uses the exact endpoint
|
|
32
|
+
* of its configured GW route.
|
|
33
|
+
*
|
|
29
34
|
* Tenant route context can be inherited from the configured
|
|
30
35
|
* `NodeHttpClient({ ctx })` instead of being repeated on every call.
|
|
31
36
|
*/
|
|
@@ -26,6 +26,11 @@ export class ProfessionalSdk {
|
|
|
26
26
|
* - `subjectDid`
|
|
27
27
|
* - requested `scopes`
|
|
28
28
|
*
|
|
29
|
+
* The actor DID must be the same identifier used by the consent grant and
|
|
30
|
+
* the professional VP. High-level callers omit `audience`; the runtime
|
|
31
|
+
* resolves the subject provider's SMART endpoint or uses the exact endpoint
|
|
32
|
+
* of its configured GW route.
|
|
33
|
+
*
|
|
29
34
|
* Tenant route context can be inherited from the configured
|
|
30
35
|
* `NodeHttpClient({ ctx })` instead of being repeated on every call.
|
|
31
36
|
*/
|
|
@@ -365,15 +365,19 @@ export type GrantProfessionalAccessInput = {
|
|
|
365
365
|
/**
|
|
366
366
|
* Canonical flat actor identifier for the actor receiving the permission.
|
|
367
367
|
*
|
|
368
|
-
*
|
|
369
|
-
* - `did:web
|
|
368
|
+
* Canonical professional grant input:
|
|
369
|
+
* - the exact `did:web:...:employee:<multibase>:<role>` actor DID reused by
|
|
370
|
+
* the employee profile, VP credential subject and SMART request
|
|
371
|
+
*
|
|
372
|
+
* Compatibility input forms:
|
|
370
373
|
* - `user@example.org`
|
|
371
374
|
* - `tel:+34600111222`
|
|
372
375
|
* - `ES`
|
|
373
376
|
* - comma-separated lists or string arrays of those tokens
|
|
374
377
|
*
|
|
375
|
-
*
|
|
376
|
-
*
|
|
378
|
+
* Email/phone targets and legacy structured objects remain accepted for
|
|
379
|
+
* compatibility, but they do not automatically authorize a later SMART
|
|
380
|
+
* request made with a different derived professional DID.
|
|
377
381
|
*/
|
|
378
382
|
actorId?: ConsentActorTargetInput;
|
|
379
383
|
/**
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { randomBytes } from 'node:crypto';
|
|
3
3
|
import { NodeManagedWallet } from './node-managed-wallet.js';
|
|
4
4
|
import { NodeHttpClient } from './node-runtime-client.js';
|
|
5
|
+
import { buildIdentityOpenIdSmartTokenPath } from './runtime-paths.js';
|
|
5
6
|
import { ProfilePinRejectedError, openServerProfileSecret, protectServerProfileSecret, } from './server-profile-protection.js';
|
|
6
7
|
/**
|
|
7
8
|
* Coordinates device registration, two-factor profile protection and SMART sessions.
|
|
@@ -95,14 +96,18 @@ export class ServerProfileSessionManager {
|
|
|
95
96
|
}
|
|
96
97
|
profile = await this.ensureRequiredStorageProfile(profile, seed);
|
|
97
98
|
const wallet = await this.createWallet(profile.profileId, seed);
|
|
98
|
-
const
|
|
99
|
+
const smartTokenEndpoint = [
|
|
100
|
+
this.options.gatewayBaseUrl.replace(/\/+$/, ''),
|
|
101
|
+
buildIdentityOpenIdSmartTokenPath(profile.routeContext),
|
|
102
|
+
].join('');
|
|
103
|
+
const assertion = await buildWalletClientAssertion(wallet, profile, smartTokenEndpoint, now);
|
|
99
104
|
const token = await this.createClient(profile.routeContext, input.idToken).requestSmartToken({
|
|
100
105
|
...profile.routeContext,
|
|
101
106
|
actorDid: profile.actorDid,
|
|
102
107
|
subjectDid: input.subjectDid,
|
|
103
108
|
clientId: profile.clientId,
|
|
104
109
|
issuer: profile.clientId,
|
|
105
|
-
audience:
|
|
110
|
+
audience: smartTokenEndpoint,
|
|
106
111
|
idToken: input.idToken,
|
|
107
112
|
vpToken,
|
|
108
113
|
clientAssertion: assertion,
|
package/dist/smart-token.d.ts
CHANGED
|
@@ -93,6 +93,13 @@ type RequestSmartTokenDeps = {
|
|
|
93
93
|
identityTokenExchangePollPath: (ctx: RouteContext) => string;
|
|
94
94
|
identityOpenIdSmartTokenPath: (ctx: RouteContext) => string;
|
|
95
95
|
identityOpenIdSmartTokenPollPath: (ctx: RouteContext) => string;
|
|
96
|
+
/**
|
|
97
|
+
* Trusted runtime resolver for the provider that owns the subject.
|
|
98
|
+
*
|
|
99
|
+
* Product code may back this with an index/DID discovery adapter. The
|
|
100
|
+
* high-level caller does not receive or manage the resolver's cache.
|
|
101
|
+
*/
|
|
102
|
+
resolveSmartTokenEndpoint?: (subjectDid: string) => Promise<string | undefined>;
|
|
96
103
|
submitAndPoll: (submitPath: string, pollPath: string, payload: {
|
|
97
104
|
thid?: string;
|
|
98
105
|
} & Record<string, unknown>, options?: PollOptions) => Promise<SubmitAndPollResult>;
|
package/dist/smart-token.js
CHANGED
|
@@ -27,7 +27,19 @@ export async function requestSmartTokenWithDeps(deps) {
|
|
|
27
27
|
if (deps.input.smartTokenKind === 'openid-smart') {
|
|
28
28
|
const actorDid = String(deps.input.actorDid || '').trim() || undefined;
|
|
29
29
|
const resolvedClientId = deps.input.clientId || actorDid || deps.input.subjectDid || deps.routeCtx.tenantId;
|
|
30
|
-
const
|
|
30
|
+
const configuredSubmitPath = deps.identityOpenIdSmartTokenPath(deps.routeCtx);
|
|
31
|
+
const configuredPollPath = deps.identityOpenIdSmartTokenPollPath(deps.routeCtx);
|
|
32
|
+
const explicitAudience = String(deps.input.audience || '').trim();
|
|
33
|
+
const subjectDid = String(deps.input.subjectDid || '').trim();
|
|
34
|
+
const discoveredEndpoint = !explicitAudience && subjectDid && deps.resolveSmartTokenEndpoint
|
|
35
|
+
? normalizeResolvedSmartTokenEndpoint(await deps.resolveSmartTokenEndpoint(subjectDid))
|
|
36
|
+
: undefined;
|
|
37
|
+
const configuredEndpoint = joinRuntimeUrl(deps.baseUrl, configuredSubmitPath);
|
|
38
|
+
const resolvedAudience = explicitAudience || discoveredEndpoint || configuredEndpoint;
|
|
39
|
+
const submitPath = discoveredEndpoint || configuredSubmitPath;
|
|
40
|
+
const pollPath = discoveredEndpoint
|
|
41
|
+
? smartTokenPollEndpointFromTokenEndpoint(discoveredEndpoint)
|
|
42
|
+
: configuredPollPath;
|
|
31
43
|
const clientAssertion = await resolveClientAssertion({
|
|
32
44
|
clientAssertion: deps.input.clientAssertion,
|
|
33
45
|
clientAssertionBuilder: deps.input.clientAssertionBuilder,
|
|
@@ -59,7 +71,7 @@ export async function requestSmartTokenWithDeps(deps) {
|
|
|
59
71
|
},
|
|
60
72
|
...(deps.input.additionalClaims || {}),
|
|
61
73
|
};
|
|
62
|
-
const exchange = await deps.submitAndPoll(
|
|
74
|
+
const exchange = await deps.submitAndPoll(submitPath, pollPath, smartPayload, pollOptions);
|
|
63
75
|
return resolveTokenExchangeResult(exchange, normalizedScopes, tokenCacheKey, deps.setTokenCache);
|
|
64
76
|
}
|
|
65
77
|
const payload = {
|
|
@@ -75,6 +87,33 @@ export async function requestSmartTokenWithDeps(deps) {
|
|
|
75
87
|
const exchange = await deps.submitAndPoll(deps.identityTokenExchangePath(deps.routeCtx), deps.identityTokenExchangePollPath(deps.routeCtx), payload, pollOptions);
|
|
76
88
|
return resolveTokenExchangeResult(exchange, normalizedScopes, tokenCacheKey, deps.setTokenCache);
|
|
77
89
|
}
|
|
90
|
+
function joinRuntimeUrl(baseUrl, path) {
|
|
91
|
+
return `${String(baseUrl || '').replace(/\/+$/, '')}/${String(path || '').replace(/^\/+/, '')}`;
|
|
92
|
+
}
|
|
93
|
+
function normalizeResolvedSmartTokenEndpoint(value) {
|
|
94
|
+
const normalized = String(value || '').trim();
|
|
95
|
+
if (!normalized)
|
|
96
|
+
return undefined;
|
|
97
|
+
let endpoint;
|
|
98
|
+
try {
|
|
99
|
+
endpoint = new URL(normalized);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
throw new Error('Resolved SMART token endpoint must be an absolute HTTP(S) URL.');
|
|
103
|
+
}
|
|
104
|
+
if (!['http:', 'https:'].includes(endpoint.protocol)) {
|
|
105
|
+
throw new Error('Resolved SMART token endpoint must use HTTP(S).');
|
|
106
|
+
}
|
|
107
|
+
if (!endpoint.pathname.endsWith('/identity/openid/smart/token')) {
|
|
108
|
+
throw new Error('Resolved SMART token endpoint must target /identity/openid/smart/token.');
|
|
109
|
+
}
|
|
110
|
+
return endpoint.toString();
|
|
111
|
+
}
|
|
112
|
+
function smartTokenPollEndpointFromTokenEndpoint(tokenEndpoint) {
|
|
113
|
+
const endpoint = new URL(tokenEndpoint);
|
|
114
|
+
endpoint.pathname = endpoint.pathname.replace(/\/identity\/openid\/smart\/token$/, '/identity/openid/smart/_batch-response');
|
|
115
|
+
return endpoint.toString();
|
|
116
|
+
}
|
|
78
117
|
async function resolveClientAssertion(input) {
|
|
79
118
|
const explicit = String(input.clientAssertion || '').trim();
|
|
80
119
|
if (explicit)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdc-sdk-node-ts",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.7",
|
|
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.",
|
|
@@ -36,8 +36,8 @@
|
|
|
36
36
|
"test:e2e:live-gw:clean": "bash ./scripts/run-live-gw-clean.sh"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"gdc-common-utils-ts": "^2.3.
|
|
40
|
-
"gdc-sdk-core-ts": "~2.3.
|
|
39
|
+
"gdc-common-utils-ts": "^2.3.9",
|
|
40
|
+
"gdc-sdk-core-ts": "~2.3.6"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/node": "^20.14.10",
|