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 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
- 1. [docs/101-SDK_END_TO_END.md](./docs/101-SDK_END_TO_END.md)
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
- 2. [docs/101-SDK_INTEGRATION.md](./docs/101-SDK_INTEGRATION.md)
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
- 3. [tests/101-live-full-cycle-bff-runtime.e2e.test.mjs](./tests/101-live-full-cycle-bff-runtime.e2e.test.mjs)
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
- 4. [docs/101-LIVE_GW_LOCAL.md](./docs/101-LIVE_GW_LOCAL.md)
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
- 5. [docs/101-DISCOVERY.md](./docs/101-DISCOVERY.md)
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
- 6. [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)
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
- 7. [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)
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
- 8. [gdc-common-utils-ts/src/examples/](https://github.com/Global-DataCare/gdc-common-utils-ts/tree/main/src/examples)
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
- 9. [gdc-common-utils-ts/docs/101-LIFECYCLE.md](https://github.com/Global-DataCare/gdc-common-utils-ts/blob/main/docs/101-LIFECYCLE.md)
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
- 10. [tests/101-backend-profile-runtime.test.mjs](./tests/101-backend-profile-runtime.test.mjs)
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
- 11. [tests/101-individual-controller-backend-runtime.test.mjs](./tests/101-individual-controller-backend-runtime.test.mjs)
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
- 12. [docs/V2_INDIVIDUAL_REGISTRATION_RECONCILIATION.md](./docs/V2_INDIVIDUAL_REGISTRATION_RECONCILIATION.md)
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
- 13. [docs/NEXT_STEPS.md](./docs/NEXT_STEPS.md)
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
- * Preferred input forms:
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
- * A legacy structured object is still accepted for compatibility, but new
376
- * integrations should send canonical strings or string arrays.
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 assertion = await buildWalletClientAssertion(wallet, profile, this.options.gatewayBaseUrl, now);
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: this.options.gatewayBaseUrl,
110
+ audience: smartTokenEndpoint,
106
111
  idToken: input.idToken,
107
112
  vpToken,
108
113
  clientAssertion: assertion,
@@ -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>;
@@ -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 resolvedAudience = deps.input.audience || deps.routeCtx.tenantId;
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(deps.identityOpenIdSmartTokenPath(deps.routeCtx), deps.identityOpenIdSmartTokenPollPath(deps.routeCtx), smartPayload, pollOptions);
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.5",
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.6",
40
- "gdc-sdk-core-ts": "~2.3.4"
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",