gdc-sdk-node-ts 2.3.13 → 2.3.15

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.
@@ -486,7 +486,9 @@ export class NodeManagedWallet {
486
486
  alg: algorithm,
487
487
  purpose: `${ownerId}:${purpose}`,
488
488
  seed: request.mode === 'deterministic' && request.seedMaterial !== undefined
489
- ? this.deriveSignerSeed(request.seedMaterial, ownerId, purpose)
489
+ ? (algorithm.startsWith('ML-DSA')
490
+ ? this.deriveSeedBytes(request.seedMaterial, ownerId, purpose, 32)
491
+ : this.deriveSignerSeed(request.seedMaterial, ownerId, purpose))
490
492
  : undefined,
491
493
  cryptography: this.cryptography,
492
494
  });
@@ -28,4 +28,14 @@ type RecoverOrganizationControllerWithIssueDeps = {
28
28
  } & Record<string, unknown>, pollOptions?: PollOptions) => Promise<SubmitAndPollResult>;
29
29
  };
30
30
  export declare function recoverOrganizationControllerWithIssueWithDeps(deps: RecoverOrganizationControllerWithIssueDeps): Promise<OrganizationControllerRecoveryResult>;
31
+ /**
32
+ * Reads the opaque activation code from a successful Organization/_issue poll result.
33
+ *
34
+ * Gateway deployments may retain one or more transport/job envelopes around
35
+ * the terminal batch response. Only a response entry carrying the canonical
36
+ * serial-number claim, or an explicitly typed `License:Issued` entry carrying
37
+ * its public `id`, is accepted. Unrelated identifiers are never interpreted as
38
+ * activation material.
39
+ */
40
+ export declare function readOrganizationIssueActivationCode(result: SubmitAndPollResult): string;
31
41
  export {};
@@ -8,7 +8,7 @@ export async function recoverOrganizationControllerWithIssueWithDeps(deps) {
8
8
  * - this flow must not depend on a new commercial Offer or Order step
9
9
  */
10
10
  const issue = await deps.submitLegalOrganizationIssue(deps.hostCtx, deps.input.issueInput, deps.input.issuePollOptions);
11
- const activationCode = readActivationCodeFromIssueResult(issue);
11
+ const activationCode = readOrganizationIssueActivationCode(issue);
12
12
  if (!activationCode) {
13
13
  throw new Error('recoverOrganizationControllerWithIssue: missing org.schema.IndividualProduct.serialNumber in Organization/_issue response.');
14
14
  }
@@ -32,19 +32,30 @@ export async function recoverOrganizationControllerWithIssueWithDeps(deps) {
32
32
  activation,
33
33
  };
34
34
  }
35
- function readActivationCodeFromIssueResult(result) {
35
+ /**
36
+ * Reads the opaque activation code from a successful Organization/_issue poll result.
37
+ *
38
+ * Gateway deployments may retain one or more transport/job envelopes around
39
+ * the terminal batch response. Only a response entry carrying the canonical
40
+ * serial-number claim, or an explicitly typed `License:Issued` entry carrying
41
+ * its public `id`, is accepted. Unrelated identifiers are never interpreted as
42
+ * activation material.
43
+ */
44
+ export function readOrganizationIssueActivationCode(result) {
36
45
  const pollBody = (result?.poll?.body || {});
37
- const body = (pollBody.body || pollBody);
38
- const data = Array.isArray(body.data) ? body.data : Array.isArray(pollBody.data) ? pollBody.data : [];
39
- const firstEntry = data[0] || {};
40
- const claims = firstEntry.meta?.claims;
41
- const activationCode = String(claims?.['org.schema.IndividualProduct.serialNumber'] || '').trim();
42
- if (activationCode) {
43
- return activationCode;
46
+ for (const candidate of nestedObjects(pollBody)) {
47
+ const claims = candidate.meta?.claims;
48
+ const activationCode = String(claims?.['org.schema.IndividualProduct.serialNumber'] || '').trim();
49
+ if (activationCode)
50
+ return activationCode;
51
+ if (candidate.type === 'License:Issued') {
52
+ const issuedId = String(candidate.id || '').trim();
53
+ if (issuedId)
54
+ return issuedId;
55
+ }
44
56
  }
45
- const diagnostics = firstEntry.response?.outcome
46
- ?.issue
47
- ?.map((issue) => String(issue?.diagnostics || '').trim())
57
+ const diagnostics = nestedObjects(pollBody)
58
+ .map((candidate) => String(candidate.diagnostics || '').trim())
48
59
  .filter(Boolean)
49
60
  .join(' | ');
50
61
  if (diagnostics) {
@@ -52,3 +63,24 @@ function readActivationCodeFromIssueResult(result) {
52
63
  }
53
64
  return '';
54
65
  }
66
+ function nestedObjects(root) {
67
+ const found = [];
68
+ const pending = [{ value: root, depth: 0 }];
69
+ const visited = new Set();
70
+ while (pending.length > 0) {
71
+ const current = pending.shift();
72
+ if (!current.value || typeof current.value !== 'object' || current.depth > 8)
73
+ continue;
74
+ if (visited.has(current.value))
75
+ continue;
76
+ visited.add(current.value);
77
+ if (Array.isArray(current.value)) {
78
+ current.value.forEach((value) => pending.push({ value, depth: current.depth + 1 }));
79
+ continue;
80
+ }
81
+ const record = current.value;
82
+ found.push(record);
83
+ Object.values(record).forEach((value) => pending.push({ value, depth: current.depth + 1 }));
84
+ }
85
+ return found;
86
+ }
@@ -9,6 +9,8 @@ export type ServerActorMode = 'self' | 'controller' | 'member';
9
9
  /** Durable public metadata plus PIN-and-host protected private material. */
10
10
  export type ServerProfileRecord = Readonly<{
11
11
  profileId: string;
12
+ /** Stable key-derivation identity. Defaults to profileId for legacy records. */
13
+ walletKeyDerivationId?: string;
12
14
  ownerId: string;
13
15
  actorKind: ActorKind;
14
16
  actorMode: ServerActorMode;
@@ -64,6 +66,21 @@ export type ServerProfileEnrollmentInput = Readonly<{
64
66
  idToken: string;
65
67
  activationCode: string;
66
68
  vpToken: string;
69
+ /**
70
+ * Optional server-only recovery seed. It must be 32 bytes encoded as
71
+ * base64url and must never be accepted from an untrusted browser payload.
72
+ */
73
+ walletSeed?: string;
74
+ /** Stable identity used to reproduce the same keys independently of profile storage IDs. */
75
+ walletKeyDerivationId?: string;
76
+ }>;
77
+ export type ServerProfileEnrollmentPublicKey = Readonly<{
78
+ ownerScope: string;
79
+ purpose: string;
80
+ use: string;
81
+ alg: string;
82
+ kid: string;
83
+ publicJwk: Record<string, unknown>;
67
84
  }>;
68
85
  /** One explicit unlock request; scopes and subject remain session-bound. */
69
86
  export type ServerProfileUnlockInput = Readonly<{
@@ -120,6 +137,15 @@ export declare class ServerProfileSessionManager {
120
137
  private readonly options;
121
138
  constructor(options: ServerProfileSessionManagerOptions);
122
139
  enroll(input: ServerProfileEnrollmentInput): Promise<ServerProfileRecord>;
140
+ /**
141
+ * Derives only the public enrollment descriptors for a server-governed
142
+ * recovery seed. This is intended for pre-DCR controller binding requests;
143
+ * no private material or seed is returned.
144
+ */
145
+ prepareEnrollmentPublicKeys(input: Readonly<{
146
+ walletSeed: string;
147
+ walletKeyDerivationId: string;
148
+ }>): Promise<ServerProfileEnrollmentPublicKey[]>;
123
149
  listProfiles(ownerId: string): Promise<ServerProfileRecord[]>;
124
150
  unlock(input: ServerProfileUnlockInput): Promise<ResolvedServerProfileSession>;
125
151
  resolveSession(ownerId: string, sessionId: string): Promise<ResolvedServerProfileSession>;
@@ -18,9 +18,12 @@ export class ServerProfileSessionManager {
18
18
  }
19
19
  async enroll(input) {
20
20
  requireEnrollment(input);
21
- const seed = randomBytes(32).toString('base64url');
22
- const wallet = await this.createWallet(input.profileId, seed);
23
- const context = walletContext(input.profileId);
21
+ const seed = input.walletSeed || randomBytes(32).toString('base64url');
22
+ if (input.walletSeed)
23
+ requireBase64UrlSeed32(input.walletSeed);
24
+ const walletKeyDerivationId = normalizedWalletKeyDerivationId(input.walletKeyDerivationId, input.profileId);
25
+ const wallet = await this.createWallet(walletKeyDerivationId, seed);
26
+ const context = walletContext(walletKeyDerivationId);
24
27
  const publicKeys = await wallet.getPublicJwks(context, {});
25
28
  const storagePublicJwk = publicKeys.find((entry) => entry.purpose === 'document-at-rest')?.publicJwk;
26
29
  if (!storagePublicJwk)
@@ -45,6 +48,7 @@ export class ServerProfileSessionManager {
45
48
  const now = this.now();
46
49
  const record = {
47
50
  profileId: input.profileId,
51
+ walletKeyDerivationId,
48
52
  ownerId: input.ownerId,
49
53
  actorKind: input.actorKind,
50
54
  actorMode: input.actorMode,
@@ -67,6 +71,27 @@ export class ServerProfileSessionManager {
67
71
  await this.options.store.putProfile(record);
68
72
  return record;
69
73
  }
74
+ /**
75
+ * Derives only the public enrollment descriptors for a server-governed
76
+ * recovery seed. This is intended for pre-DCR controller binding requests;
77
+ * no private material or seed is returned.
78
+ */
79
+ async prepareEnrollmentPublicKeys(input) {
80
+ requireBase64UrlSeed32(input.walletSeed);
81
+ const walletKeyDerivationId = normalizedWalletKeyDerivationId(input.walletKeyDerivationId, '');
82
+ if (!walletKeyDerivationId)
83
+ throw new Error('prepareEnrollmentPublicKeys requires walletKeyDerivationId.');
84
+ const wallet = await this.createWallet(walletKeyDerivationId, input.walletSeed);
85
+ const descriptors = await wallet.getPublicJwks(walletContext(walletKeyDerivationId), {});
86
+ return descriptors.map((entry) => ({
87
+ ownerScope: entry.ownerScope,
88
+ purpose: entry.purpose,
89
+ use: entry.use,
90
+ alg: entry.alg,
91
+ kid: entry.kid,
92
+ publicJwk: entry.publicJwk,
93
+ }));
94
+ }
70
95
  listProfiles(ownerId) {
71
96
  return this.options.store.listProfiles(ownerId);
72
97
  }
@@ -95,7 +120,8 @@ export class ServerProfileSessionManager {
95
120
  throw new Error('Profile PIN rejected.');
96
121
  }
97
122
  profile = await this.ensureRequiredStorageProfile(profile, seed);
98
- const wallet = await this.createWallet(profile.profileId, seed);
123
+ const walletKeyDerivationId = profile.walletKeyDerivationId || profile.profileId;
124
+ const wallet = await this.createWallet(walletKeyDerivationId, seed);
99
125
  const smartTokenEndpoint = [
100
126
  this.options.gatewayBaseUrl.replace(/\/+$/, ''),
101
127
  buildIdentityOpenIdSmartTokenPath(profile.routeContext),
@@ -143,8 +169,9 @@ export class ServerProfileSessionManager {
143
169
  }
144
170
  const profile = await this.requireOwnedProfile(ownerId, session.profileId);
145
171
  const seed = await this.options.sealer.unseal(session.sealedUnlockedWalletSeed, `${sessionId}:unlocked-wallet-seed`);
146
- const wallet = await this.createWallet(profile.profileId, seed);
147
- const context = walletContext(profile.profileId);
172
+ const walletKeyDerivationId = profile.walletKeyDerivationId || profile.profileId;
173
+ const wallet = await this.createWallet(walletKeyDerivationId, seed);
174
+ const context = walletContext(walletKeyDerivationId);
148
175
  return {
149
176
  sessionId,
150
177
  profile,
@@ -174,9 +201,9 @@ export class ServerProfileSessionManager {
174
201
  fetchImpl: this.options.fetchImpl,
175
202
  });
176
203
  }
177
- async createWallet(profileId, seed) {
204
+ async createWallet(walletKeyDerivationId, seed) {
178
205
  const wallet = new NodeManagedWallet({ resolveRecipientJwk: this.options.resolveRecipientJwk });
179
- const context = walletContext(profileId);
206
+ const context = walletContext(walletKeyDerivationId);
180
207
  await wallet.provisionManagedKeys(context, {
181
208
  ownerScope: 'profile',
182
209
  purposes: ['actor-signing', 'document-at-rest'],
@@ -201,8 +228,9 @@ export class ServerProfileSessionManager {
201
228
  const required = this.options.requiredConfidentialStorageProfile ?? profile.confidentialStorageProfile ?? 'confidential-basic-v1';
202
229
  if (required !== 'confidential-pqc-v1')
203
230
  return profile;
204
- const wallet = await this.createWallet(profile.profileId, seed);
205
- const storageKeys = await wallet.getPublicJwks(walletContext(profile.profileId), {
231
+ const walletKeyDerivationId = profile.walletKeyDerivationId || profile.profileId;
232
+ const wallet = await this.createWallet(walletKeyDerivationId, seed);
233
+ const storageKeys = await wallet.getPublicJwks(walletContext(walletKeyDerivationId), {
206
234
  ownerScope: 'profile', purpose: 'document-at-rest', alg: 'ML-KEM-768',
207
235
  });
208
236
  const storagePublicJwk = storageKeys[0]?.publicJwk;
@@ -234,7 +262,7 @@ export class ServerProfileSessionManager {
234
262
  }
235
263
  async function buildWalletClientAssertion(wallet, profile, audience, now) {
236
264
  const seconds = Math.floor(now.getTime() / 1000);
237
- return wallet.signCompactJws(walletContext(profile.profileId), {
265
+ return wallet.signCompactJws(walletContext(profile.walletKeyDerivationId || profile.profileId), {
238
266
  header: { alg: 'ES384', typ: 'JWT' },
239
267
  claims: {
240
268
  iss: profile.clientId,
@@ -247,12 +275,20 @@ async function buildWalletClientAssertion(wallet, profile, audience, now) {
247
275
  key: { ownerScope: 'runtime', purpose: 'openid-id-token-signing' },
248
276
  });
249
277
  }
250
- function walletContext(profileId) {
278
+ function walletContext(walletKeyDerivationId) {
251
279
  return {
252
- profile: { profileId },
253
- runtime: { runtimeId: `${profileId}:server-runtime`, runtimeType: 'backend-service' },
280
+ profile: { profileId: walletKeyDerivationId },
281
+ runtime: { runtimeId: `${walletKeyDerivationId}:server-runtime`, runtimeType: 'backend-service' },
254
282
  };
255
283
  }
284
+ function normalizedWalletKeyDerivationId(value, profileId) {
285
+ return String(value || profileId).trim();
286
+ }
287
+ function requireBase64UrlSeed32(seed) {
288
+ if (!/^[A-Za-z0-9_-]{43}$/.test(seed) || Buffer.from(seed, 'base64url').byteLength !== 32) {
289
+ throw new Error('Profile enrollment walletSeed must be a 32-byte base64url value.');
290
+ }
291
+ }
256
292
  function requireEnrollment(input) {
257
293
  for (const [name, value] of Object.entries({
258
294
  ownerId: input.ownerId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdc-sdk-node-ts",
3
- "version": "2.3.13",
3
+ "version": "2.3.15",
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.",