@ibgib/web-gib 0.0.56 → 0.0.58

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.
Files changed (65) hide show
  1. package/dist/AUTO-GENERATED-version.d.mts +1 -1
  2. package/dist/AUTO-GENERATED-version.mjs +1 -1
  3. package/dist/api/api-index.mjs +1 -1
  4. package/dist/api/api-index.mjs.map +1 -1
  5. package/dist/api/ibgib-api-bridge.d.mts +97 -0
  6. package/dist/api/ibgib-api-bridge.d.mts.map +1 -1
  7. package/dist/api/ibgib-api-bridge.mjs +205 -3
  8. package/dist/api/ibgib-api-bridge.mjs.map +1 -1
  9. package/dist/identity/custodian-delegate-helper.d.mts +22 -0
  10. package/dist/identity/custodian-delegate-helper.d.mts.map +1 -0
  11. package/dist/identity/custodian-delegate-helper.mjs +50 -0
  12. package/dist/identity/custodian-delegate-helper.mjs.map +1 -0
  13. package/dist/identity/sso-popup-helper.d.mts +33 -0
  14. package/dist/identity/sso-popup-helper.d.mts.map +1 -0
  15. package/dist/identity/sso-popup-helper.mjs +124 -0
  16. package/dist/identity/sso-popup-helper.mjs.map +1 -0
  17. package/dist/index.d.mts +2 -0
  18. package/dist/index.d.mts.map +1 -1
  19. package/dist/index.mjs +3 -0
  20. package/dist/index.mjs.map +1 -1
  21. package/dist/ui/component/identity/add-device/add-device.d.mts.map +1 -1
  22. package/dist/ui/component/identity/add-device/add-device.html +6 -7
  23. package/dist/ui/component/identity/add-device/add-device.mjs +13 -10
  24. package/dist/ui/component/identity/add-device/add-device.mjs.map +1 -1
  25. package/dist/ui/component/identity/identity-header/identity-header.d.mts.map +1 -1
  26. package/dist/ui/component/identity/identity-header/identity-header.mjs +2 -1
  27. package/dist/ui/component/identity/identity-header/identity-header.mjs.map +1 -1
  28. package/dist/ui/component/identity/keystone-creator/keystone-creator.css +114 -9
  29. package/dist/ui/component/identity/keystone-creator/keystone-creator.d.mts +23 -0
  30. package/dist/ui/component/identity/keystone-creator/keystone-creator.d.mts.map +1 -1
  31. package/dist/ui/component/identity/keystone-creator/keystone-creator.html +122 -50
  32. package/dist/ui/component/identity/keystone-creator/keystone-creator.mjs +529 -83
  33. package/dist/ui/component/identity/keystone-creator/keystone-creator.mjs.map +1 -1
  34. package/dist/ui/component/identity/keystone-details/keystone-details.d.mts.map +1 -1
  35. package/dist/ui/component/identity/keystone-details/keystone-details.mjs +19 -61
  36. package/dist/ui/component/identity/keystone-details/keystone-details.mjs.map +1 -1
  37. package/package.json +4 -4
  38. package/src/AUTO-GENERATED-version.mts +1 -1
  39. package/src/api/api-index.mts +1 -1
  40. package/src/api/ibgib-api-bridge.mts +288 -4
  41. package/src/identity/custodian-delegate-helper.mts +61 -0
  42. package/src/identity/sso/{sso-custodian-service.mts → sso-custodian-service.mts.bak} +65 -38
  43. package/src/identity/sso/{sso-custodian-service.respec.mts → sso-custodian-service.respec.mts.bak} +15 -14
  44. package/src/identity/sso-popup-helper.mts +155 -0
  45. package/src/index.mts +3 -0
  46. package/src/ui/component/identity/add-device/add-device.html +6 -7
  47. package/src/ui/component/identity/add-device/add-device.mts +17 -16
  48. package/src/ui/component/identity/identity-header/identity-header.mts +2 -1
  49. package/src/ui/component/identity/keystone-creator/keystone-creator.css +114 -9
  50. package/src/ui/component/identity/keystone-creator/keystone-creator.html +122 -50
  51. package/src/ui/component/identity/keystone-creator/keystone-creator.mts +599 -100
  52. package/src/ui/component/identity/keystone-details/keystone-details.mts +26 -77
  53. package/dist/identity/sso/sso-config-helper.d.mts +0 -7
  54. package/dist/identity/sso/sso-config-helper.d.mts.map +0 -1
  55. package/dist/identity/sso/sso-config-helper.mjs +0 -42
  56. package/dist/identity/sso/sso-config-helper.mjs.map +0 -1
  57. package/dist/identity/sso/sso-custodian-service.d.mts +0 -139
  58. package/dist/identity/sso/sso-custodian-service.d.mts.map +0 -1
  59. package/dist/identity/sso/sso-custodian-service.mjs +0 -443
  60. package/dist/identity/sso/sso-custodian-service.mjs.map +0 -1
  61. package/dist/identity/sso/sso-custodian-service.respec.d.mts +0 -2
  62. package/dist/identity/sso/sso-custodian-service.respec.d.mts.map +0 -1
  63. package/dist/identity/sso/sso-custodian-service.respec.mjs +0 -254
  64. package/dist/identity/sso/sso-custodian-service.respec.mjs.map +0 -1
  65. /package/src/identity/sso/{sso-config-helper.mts → sso-config-helper.mts.bak} +0 -0
@@ -1,20 +1,23 @@
1
1
  import thisCss from "./keystone-creator.css";
2
2
  import thisHtml from "./keystone-creator.html";
3
3
  import commonComponentStylesCss from "../../common-component-styles.css";
4
- import { delay, extractErrorMsg } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
4
+ import { delay, extractErrorMsg, } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
5
5
  import { getIbGibAddr } from "@ibgib/ts-gib/dist/helper.mjs";
6
6
  import { KeystoneService_V1 } from "@ibgib/core-gib/dist/keystone/keystone-service-v1.mjs";
7
7
  import { KeystoneProfileBuilder } from "@ibgib/core-gib/dist/keystone/policy/keystone-profile-builder.mjs";
8
- import { getGlobalMetaspace_waitIfNeeded } from "../../../../helpers.mjs";
8
+ import { updateSpecialIndex, } from "@ibgib/core-gib/dist/timeline/timeline-api.mjs";
9
+ import { toDto, getTjpAddr } from "@ibgib/core-gib/dist/common/other/ibgib-helper.mjs";
10
+ import { validateEmail } from "@ibgib/core-gib/dist/keystone/keystone-helpers.mjs";
11
+ import { KEYSTONE_USERNAME_REGEXP, KEYSTONE_DESCRIPTION_REGEXP, KEYSTONE_VERB_SYNC, KEYSTONE_VERB_CONNECT } from "@ibgib/core-gib/dist/keystone/keystone-constants.mjs";
12
+ import { deriveDelegateSecret } from "@ibgib/core-gib/dist/keystone/strategy/hash-reveal-v1/hash-reveal-v1.mjs";
13
+ import { getGlobalMetaspace_waitIfNeeded, } from "../../../../helpers.mjs";
9
14
  import { IbGibDynamicComponentMetaBase, IbGibFormInstanceBase, } from "../../ibgib-dynamic-component-bases.mjs";
10
15
  import { getComponentCtorArg } from "../../../../app-bootstrap/init-orchestration.mjs";
11
16
  import { getApiBridge } from "../../../../api/ibgib-api-bridge.mjs";
12
- import { updateSpecialIndex } from "@ibgib/core-gib/dist/timeline/timeline-api.mjs";
13
- import { toDto } from "@ibgib/core-gib/dist/common/other/ibgib-helper.mjs";
14
- import { validateEmail } from "@ibgib/core-gib/dist/keystone/keystone-helpers.mjs";
15
- import { KEYSTONE_USERNAME_REGEXP, KEYSTONE_DESCRIPTION_REGEXP, KEYSTONE_VERB_SYNC, KEYSTONE_VERB_CONNECT } from "@ibgib/core-gib/dist/keystone/keystone-constants.mjs";
17
+ import { deriveLocalCustodianDelegateSecret } from "../../../../identity/custodian-delegate-helper.mjs";
18
+ import { executeSsoOAuthPopup } from "../../../../identity/sso-popup-helper.mjs";
16
19
  import { EVENT_IBGIB_IDENTITY_REQUEST_CHANGE, EVENT_IBGIB_UI_MESSAGE } from "../../../ui-constants.mjs";
17
- import { deriveDelegateSecret } from "@ibgib/core-gib/dist/keystone/strategy/hash-reveal-v1/hash-reveal-v1.mjs";
20
+ import { alertUser, shadowRoot_getElementById } from "../../../../helpers.web.mjs";
18
21
  export const KEYSTONE_CREATOR_COMPONENT_NAME = 'ibgib-keystone-creator';
19
22
  /**
20
23
  * Metadata for the Keystone Creator component.
@@ -112,6 +115,50 @@ export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
112
115
  // cleanup if needed
113
116
  }
114
117
  initHandlers() {
118
+ // Mode Tab Switching (Quick vs Password-based)
119
+ const tabQuick = this.shadowRoot?.getElementById('tab-quick');
120
+ const tabPassword = this.shadowRoot?.getElementById('tab-password');
121
+ const panelQuick = this.shadowRoot?.getElementById('panel-quick');
122
+ const panelPassword = this.shadowRoot?.getElementById('panel-password');
123
+ const subtitleQuick = this.shadowRoot?.getElementById('subtitle-quick');
124
+ const subtitlePassword = this.shadowRoot?.getElementById('subtitle-password');
125
+ tabQuick?.addEventListener('click', () => {
126
+ tabQuick.classList.add('active');
127
+ tabPassword?.classList.remove('active');
128
+ panelQuick?.classList.remove('hidden');
129
+ panelPassword?.classList.add('hidden');
130
+ subtitleQuick?.classList.remove('hidden');
131
+ subtitlePassword?.classList.add('hidden');
132
+ });
133
+ tabPassword?.addEventListener('click', () => {
134
+ tabPassword.classList.add('active');
135
+ tabQuick?.classList.remove('active');
136
+ panelPassword?.classList.remove('hidden');
137
+ panelQuick?.classList.add('hidden');
138
+ subtitlePassword?.classList.remove('hidden');
139
+ subtitleQuick?.classList.add('hidden');
140
+ });
141
+ const btnGoogleSso = this.shadowRoot?.getElementById('btn-google-sso');
142
+ btnGoogleSso?.addEventListener('click', () => this.handleSsoClick('google'));
143
+ const btnGithubSso = this.shadowRoot?.getElementById('btn-github-sso');
144
+ btnGithubSso?.addEventListener('click', () => this.handleSsoClick('github'));
145
+ // Email Sign-In Info Button
146
+ const btnEmailInfo = this.shadowRoot?.getElementById('btn-email-info');
147
+ btnEmailInfo?.addEventListener('click', (e) => {
148
+ e.preventDefault();
149
+ alertUser({
150
+ title: 'Custom Email Sign-In',
151
+ msg: 'When you enter your email address, we dispatch a 6-digit one-time verification code to your inbox.\n\nEntering the code creates your cryptographic identity delegate on your device and signs you in seamlessly without needing a password!'
152
+ });
153
+ });
154
+ // Quick Email Verification Code Handlers
155
+ const btnQuickSendCode = this.shadowRoot?.getElementById('btn-quick-send-code');
156
+ const inputQuickEmail = this.shadowRoot?.getElementById('input-quick-email');
157
+ const secQuickOtp = this.shadowRoot?.getElementById('section-quick-otp');
158
+ const inputQuickOtp = this.shadowRoot?.getElementById('input-quick-otp');
159
+ const btnQuickVerifyCode = this.shadowRoot?.getElementById('btn-quick-verify-code');
160
+ btnQuickSendCode?.addEventListener('click', () => this.handleQuickSendCode());
161
+ btnQuickVerifyCode?.addEventListener('click', () => this.handleQuickVerifyCode());
115
162
  this.elements.btnGenerateEl.addEventListener('click', () => this.handleGenerate());
116
163
  this.shadowRoot.getElementById('btn-switch-sign-in')?.addEventListener('click', (e) => {
117
164
  e.preventDefault();
@@ -122,16 +169,16 @@ export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
122
169
  }));
123
170
  });
124
171
  // Clear invalid styling when user inputs text
125
- this.elements.inputUsername.addEventListener('input', () => {
172
+ this.elements.inputUsername?.addEventListener('input', () => {
126
173
  this.elements.inputUsername.classList.remove('invalid');
127
174
  });
128
- this.elements.inputDescription.addEventListener('input', () => {
175
+ this.elements.inputDescription?.addEventListener('input', () => {
129
176
  this.elements.inputDescription.classList.remove('invalid');
130
177
  });
131
- this.elements.inputEmail.addEventListener('input', () => {
178
+ this.elements.inputEmail?.addEventListener('input', () => {
132
179
  this.elements.inputEmail.classList.remove('invalid');
133
180
  });
134
- this.elements.inputSecret.addEventListener('input', () => {
181
+ this.elements.inputSecret?.addEventListener('input', () => {
135
182
  this.elements.inputSecret.classList.remove('invalid');
136
183
  });
137
184
  }
@@ -142,6 +189,223 @@ export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
142
189
  btn.textContent = isLoading ? 'Generating...' : 'Generate Identity Keystone';
143
190
  }
144
191
  }
192
+ /**
193
+ * Triggered by Google / GitHub SSO buttons.
194
+ * Opens OAuth popup, exchanges authorization code, builds local delegate keystone,
195
+ * registers delegate on server, and finalizes onboarding.
196
+ */
197
+ async handleSsoClick(providerId) {
198
+ const lc = `${this.lc}[${this.handleSsoClick.name}]`;
199
+ this.setStatus(`Opening ${providerId.toUpperCase()} SSO authorization window...`, 'info');
200
+ const resPopup = await executeSsoOAuthPopup({ providerId });
201
+ if (!resPopup.success || !resPopup.code) {
202
+ this.setStatus(resPopup.message || `${providerId.toUpperCase()} sign-in cancelled.`, 'error');
203
+ return;
204
+ }
205
+ this.setStatus(`Exchanging ${providerId.toUpperCase()} authorization code...`, 'info');
206
+ const apiBridge = getApiBridge();
207
+ const resLogin = await apiBridge.postSsoLogin({
208
+ code: resPopup.code,
209
+ providerId,
210
+ redirectUri: resPopup.redirectUri
211
+ });
212
+ if (!resLogin.success || !resLogin.primaryTjpAddr || !resLogin.custodianReqs) {
213
+ this.setStatus(resLogin.message || `${providerId.toUpperCase()} login failed.`, 'error');
214
+ return;
215
+ }
216
+ const { primaryTjpAddr, custodianReqs } = resLogin;
217
+ this.setStatus('Deriving local device delegate keys...', 'info');
218
+ const delegateSecret = await deriveLocalCustodianDelegateSecret({ primaryTjpAddr });
219
+ const email = resLogin.userInfo?.email || '';
220
+ const username = email ? email.split('@')[0] : 'user';
221
+ const delegateBuilder = KeystoneProfileBuilder.buildKeystone('sync')
222
+ .withUsername(`${username}-sync`)
223
+ .withDescription(`Sync Delegate for ${username}`)
224
+ .withDetails({
225
+ client: 'space-gib-web',
226
+ role: 'sync-delegate',
227
+ primaryTjpAddr,
228
+ custodianReqs,
229
+ });
230
+ const configs = await delegateBuilder.compileConfigs();
231
+ const metaspace = await getGlobalMetaspace_waitIfNeeded();
232
+ const space = await metaspace.getLocalUserSpace({ lock: false });
233
+ if (!space) {
234
+ this.setStatus('Could not access local user space.', 'error');
235
+ return;
236
+ }
237
+ const keystoneService = new KeystoneService_V1();
238
+ const delegateKeystone = await keystoneService.genesis({
239
+ masterSecret: delegateSecret,
240
+ configs,
241
+ metaspace,
242
+ space,
243
+ frameDetails: delegateBuilder.getFrameDetails(),
244
+ isPrimary: false
245
+ });
246
+ this.setStatus('Registering device delegate with custodian server...', 'info');
247
+ const resRegister = await apiBridge.postCustodianRegisterDelegate({ delegateKeystone });
248
+ if (!resRegister.success) {
249
+ this.setStatus(resRegister.message || 'Custodian delegate registration failed.', 'error');
250
+ return;
251
+ }
252
+ // Finalize onboarding: update keystones index, activate identity, and navigate to identity manager
253
+ await this.finalizeIdentityOnboarding({
254
+ primaryKeystone: resRegister.evolvedPrimaryKeystone || resLogin.primaryKeystone,
255
+ primaryGraph: resRegister.primaryGraph || resLogin.primaryGraph,
256
+ delegateKeystone,
257
+ primaryTjpAddr,
258
+ delegateSecret,
259
+ makeActive: true
260
+ });
261
+ }
262
+ /**
263
+ * Triggered by the "Send 6-Digit Verification Code" button.
264
+ * Validates quick email input and dispatches a 6-digit OTP code to the user's inbox.
265
+ */
266
+ async handleQuickSendCode() {
267
+ const lc = `${this.lc}[${this.handleQuickSendCode.name}]`;
268
+ const btnQuickSendCode = this.shadowRoot?.getElementById('btn-quick-send-code');
269
+ const inputQuickEmail = this.shadowRoot?.getElementById('input-quick-email');
270
+ const secQuickOtp = this.shadowRoot?.getElementById('section-quick-otp');
271
+ const email = (inputQuickEmail?.value || '').trim();
272
+ if (!email || !validateEmail(email)) {
273
+ this.setStatus("Please enter a valid email address.", "error");
274
+ return;
275
+ }
276
+ if (btnQuickSendCode) {
277
+ btnQuickSendCode.disabled = true;
278
+ btnQuickSendCode.textContent = "Sending Verification Code...";
279
+ }
280
+ this.setStatus("Requesting verification code...", "info");
281
+ try {
282
+ const resCode = await getApiBridge().postVerificationCode({ email });
283
+ if (resCode.success) {
284
+ secQuickOtp?.classList.remove('hidden');
285
+ this.setStatus("✉️ 6-digit verification code sent to your email!", "success");
286
+ }
287
+ else {
288
+ this.setStatus(resCode.message || "Failed to dispatch verification code.", "error");
289
+ }
290
+ }
291
+ catch (error) {
292
+ const emsg = extractErrorMsg(error);
293
+ console.error(`${lc} ${emsg}`);
294
+ this.setStatus(`Failed to dispatch verification code: ${emsg}`, "error");
295
+ }
296
+ finally {
297
+ if (btnQuickSendCode) {
298
+ btnQuickSendCode.disabled = false;
299
+ btnQuickSendCode.textContent = "Send 6-Digit Verification Code";
300
+ }
301
+ }
302
+ }
303
+ /**
304
+ * Triggered by the "Verify Code" button.
305
+ * Submits 6-digit OTP code to custodian server, builds local sync delegate keystone,
306
+ * registers delegate on server, and finalizes onboarding.
307
+ */
308
+ async handleQuickVerifyCode() {
309
+ const lc = `${this.lc}[${this.handleQuickVerifyCode.name}]`;
310
+ const inputQuickEmail = this.shadowRoot?.getElementById('input-quick-email');
311
+ const inputQuickOtp = this.shadowRoot?.getElementById('input-quick-otp');
312
+ const btnQuickVerifyCode = this.shadowRoot?.getElementById('btn-quick-verify-code');
313
+ const email = (inputQuickEmail?.value || '').trim();
314
+ const code = (inputQuickOtp?.value || '').trim();
315
+ if (!code || code.length !== 6 || !/^\d{6}$/.test(code)) {
316
+ this.setStatus("Please enter a valid 6-digit verification code.", "error");
317
+ return;
318
+ }
319
+ if (btnQuickVerifyCode) {
320
+ btnQuickVerifyCode.disabled = true;
321
+ btnQuickVerifyCode.textContent = "Verifying...";
322
+ }
323
+ this.setStatus("Verifying code & completing onboarding...", "info");
324
+ this.showBusy({
325
+ isBusy: true,
326
+ title: 'Verifying Code...',
327
+ msg: 'Great! Juust a moment...\n(Validating code, promoting server domain space, other security stuff...)',
328
+ animationEmoji: '🔐'
329
+ });
330
+ try {
331
+ const resGenesis = await getApiBridge().postCustodianGenesis({ email, code });
332
+ if (!resGenesis.success || !resGenesis.primaryTjpAddr || !resGenesis.custodianReqs) {
333
+ this.setStatus(resGenesis.message || "Custodian verification failed.", "error");
334
+ return;
335
+ }
336
+ const primaryTjpAddr = resGenesis.primaryTjpAddr;
337
+ const custodianReqs = resGenesis.custodianReqs;
338
+ // Construct local delegate keystone
339
+ this.setStatus("Constructing local sync delegate...", "info");
340
+ this.showBusy({
341
+ isBusy: true,
342
+ title: 'Creating Sync Delegate...',
343
+ msg: 'Generating cryptographic sync delegate keys...',
344
+ animationEmoji: '🔑'
345
+ });
346
+ const delegateSecret = await deriveLocalCustodianDelegateSecret({ primaryTjpAddr });
347
+ const username = email.split('@')[0];
348
+ const delegateBuilder = KeystoneProfileBuilder.buildKeystone('sync')
349
+ .withUsername(`${username}-sync`)
350
+ .withDescription(`Sync Delegate for ${username}`)
351
+ .withDetails({
352
+ client: 'space-gib-web',
353
+ role: 'sync-delegate',
354
+ primaryTjpAddr,
355
+ custodianReqs,
356
+ });
357
+ const configs = await delegateBuilder.compileConfigs();
358
+ const metaspace = await getGlobalMetaspace_waitIfNeeded();
359
+ const space = await metaspace.getLocalUserSpace({ lock: false });
360
+ if (!space) {
361
+ this.setStatus("Failed to access local user space.", "error");
362
+ return;
363
+ }
364
+ const keystoneService = new KeystoneService_V1();
365
+ const delegateKeystone = await keystoneService.genesis({
366
+ masterSecret: delegateSecret,
367
+ configs,
368
+ metaspace,
369
+ space,
370
+ frameDetails: delegateBuilder.getFrameDetails(),
371
+ isPrimary: false
372
+ });
373
+ // Register delegate with server
374
+ this.setStatus("Registering device delegate with custodian server...", "info");
375
+ this.showBusy({
376
+ isBusy: true,
377
+ title: 'Registering Delegate...',
378
+ msg: 'Registering local delegate with custodian server...',
379
+ animationEmoji: '📝'
380
+ });
381
+ const resRegister = await getApiBridge().postCustodianRegisterDelegate({ delegateKeystone });
382
+ if (!resRegister.success) {
383
+ this.setStatus(resRegister.message || "Failed to register delegate with custodian server.", "error");
384
+ return;
385
+ }
386
+ // Finalize onboarding: update keystones index, activate identity, and navigate to identity manager
387
+ await this.finalizeIdentityOnboarding({
388
+ primaryKeystone: resRegister.evolvedPrimaryKeystone || resGenesis.primaryKeystone,
389
+ primaryGraph: resRegister.primaryGraph || resGenesis.primaryGraph,
390
+ delegateKeystone,
391
+ primaryTjpAddr,
392
+ delegateSecret,
393
+ makeActive: true
394
+ });
395
+ }
396
+ catch (error) {
397
+ const emsg = extractErrorMsg(error);
398
+ console.error(`${lc} ${emsg}`, error);
399
+ this.setStatus(`Error: ${emsg}`, "error");
400
+ }
401
+ finally {
402
+ this.showBusy({ isBusy: false });
403
+ if (btnQuickVerifyCode) {
404
+ btnQuickVerifyCode.disabled = false;
405
+ btnQuickVerifyCode.textContent = "Verify Code";
406
+ }
407
+ }
408
+ }
145
409
  /**
146
410
  * Triggered by the "Generate" button.
147
411
  * Orchestrates: Secret -> Local Genesis -> Graph Collection -> Server Sync.
@@ -218,7 +482,11 @@ export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
218
482
  animationEmoji: '⚙️'
219
483
  });
220
484
  // 1. Prepare Primary/Domain Keystone Configs
221
- const builder = KeystoneProfileBuilder.buildKeystone('domain')
485
+ const isProd = process.env.NODE_ENV === 'production';
486
+ const requestedProfile = (process.env.KEYSTONE_PROFILE || 'domain').toLowerCase();
487
+ const profileName = (isProd && requestedProfile === 'test') ? 'domain' : requestedProfile;
488
+ console.log(`${lc} Using keystone policy profile '${profileName}' (I: 90123456789abcdef0123456789abcde)`);
489
+ const builder = KeystoneProfileBuilder.buildKeystone(profileName)
222
490
  .withUsername(username)
223
491
  .withDescription(description);
224
492
  if (email) {
@@ -252,11 +520,11 @@ export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
252
520
  frameDetails: builder.getFrameDetails(),
253
521
  isPrimary: true
254
522
  });
255
- const addr = getIbGibAddr({ ibGib: keystoneIbGib });
256
- console.log(`${lc} local genesis complete: ${addr}`);
523
+ const primaryTjpAddr = getTjpAddr({ ibGib: keystoneIbGib, defaultIfNone: 'incomingAddr' });
524
+ console.log(`${lc} local genesis complete: ${primaryTjpAddr}`);
257
525
  await delay(humanEyeDelay); // for human eyes, not needed for functionality
258
526
  this.setStatus("Local genesis complete. Creating sync delegate...", "info");
259
- console.log(`✓ Local genesis complete: ${addr}`);
527
+ console.log(`✓ Local genesis complete: ${primaryTjpAddr}`);
260
528
  // Automatically generate Sync Delegate Keystone
261
529
  this.showBusy({
262
530
  isBusy: true,
@@ -265,12 +533,13 @@ export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
265
533
  animationEmoji: '🔑'
266
534
  });
267
535
  const delegateSecret = await deriveDelegateSecret({ masterSecret });
268
- const delegateBuilder = KeystoneProfileBuilder.buildKeystone('sync')
536
+ const delegateBuilder = KeystoneProfileBuilder.buildKeystone(profileName)
269
537
  .withUsername(`${username}-sync`)
270
538
  .withDescription(`Sync Delegate for ${username}`)
271
539
  .withDetails({
272
540
  client: 'space-gib-web',
273
- role: 'sync-delegate'
541
+ role: 'sync-delegate',
542
+ primaryTjpAddr: primaryTjpAddr,
274
543
  });
275
544
  const delegateConfigs = await delegateBuilder.compileConfigs();
276
545
  const delegateKeystoneIbGib = await keystoneService.genesis({
@@ -303,15 +572,11 @@ export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
303
572
  console.log(`${lc} delegate registered. evolved parent address: ${evolvedParentAddr}`);
304
573
  await delay(humanEyeDelay);
305
574
  // 4. Post to Server
306
- console.log(`Posting genesis keystone to server...`);
307
- this.setStatus("Syncing genesis to server...", "info");
575
+ this.setStatus("Syncing genesis keystone to server...", "info");
308
576
  this.showBusy({
309
577
  isBusy: true,
310
578
  title: 'Syncing to Server...',
311
- msg: [
312
- 'Posting genesis keystone to server sync node...',
313
- '(may take awhile, depending on strength of keystone...)',
314
- ].join('\n'),
579
+ msg: 'Staging pending genesis keystone on server...',
315
580
  animationEmoji: '🔄'
316
581
  });
317
582
  const resSync = await getApiBridge().postGenesisKeystone(keystoneIbGib);
@@ -323,69 +588,115 @@ export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
323
588
  this.setButtonLoading(false);
324
589
  return;
325
590
  }
326
- // Post evolved keystone + delegate keystone
327
- this.setStatus("Syncing delegate registration to server...", "info");
328
- this.showBusy({
329
- isBusy: true,
330
- title: 'Syncing Delegate...',
331
- msg: 'Posting evolved domain identity and sync delegate to server...',
332
- animationEmoji: '🔄'
333
- });
334
- const resEvolve = await getApiBridge().putEvolveKeystone({
335
- domainAddr: addr,
336
- keystoneIbGib: evolvedParentKeystoneIbGib,
337
- relatedKeystoneIbGibs: [delegateKeystoneIbGib]
338
- });
339
- if (!resEvolve.success) {
340
- const msg = `Delegate registration sync failed: ${resEvolve.message}`;
341
- console.error(`${lc} ${msg}`);
342
- this.setStatus(msg, "error");
591
+ const isPending = resSync.pending || resSync.statusCode === 202;
592
+ if (isPending) {
593
+ console.log(`${lc} Genesis keystone quarantined in pending staging area. Prompting for 6-digit verification code.`);
343
594
  this.showBusy({ isBusy: false });
344
- this.setButtonLoading(false);
345
- return;
595
+ this.setFormDisabled(true);
596
+ if (!this.shadowRoot) {
597
+ throw new Error(`(UNEXPECTED) this.shadowRoot falsy? (E: 6d316f5a2bf8e6a0286306468d35c826)`);
598
+ }
599
+ const secVerify = shadowRoot_getElementById(this.shadowRoot, 'section-code-verification');
600
+ const inputVerifyCode = shadowRoot_getElementById(this.shadowRoot, 'input-verify-code');
601
+ const btnSubmitCode = shadowRoot_getElementById(this.shadowRoot, 'btn-submit-code');
602
+ const btnResendCode = shadowRoot_getElementById(this.shadowRoot, 'btn-resend-code');
603
+ secVerify.classList.remove('hidden');
604
+ secVerify.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
605
+ this.setStatus("✉️ Verification code sent to your email. Enter 6-digit code below:", "info");
606
+ const onResendCode = async () => {
607
+ btnResendCode.disabled = true;
608
+ btnResendCode.textContent = "Sending...";
609
+ this.setStatus("Resending verification code to your email...", "info");
610
+ const resResend = await getApiBridge().postVerificationCode({ email, primaryTjpAddr: primaryTjpAddr });
611
+ if (resResend.success) {
612
+ this.setStatus("✉️ New verification code sent to your email!", "success");
613
+ }
614
+ else {
615
+ this.setStatus(resResend.message || "Failed to resend verification code.", "error");
616
+ }
617
+ btnResendCode.disabled = false;
618
+ btnResendCode.textContent = "Resend Code";
619
+ };
620
+ btnResendCode.addEventListener('click', onResendCode);
621
+ await new Promise((resolve, reject) => {
622
+ const onSubmitCode = async () => {
623
+ const code = inputVerifyCode.value.trim();
624
+ if (!code || code.length !== 6 || !/^\d{6}$/.test(code)) {
625
+ this.setStatus("Please enter a valid 6-digit numeric verification code (e.g. 482901).", "error");
626
+ return;
627
+ }
628
+ btnSubmitCode.disabled = true;
629
+ btnSubmitCode.textContent = "Verifying...";
630
+ this.showBusy({
631
+ isBusy: true,
632
+ title: 'Verifying & Activating...',
633
+ msg: 'Great! Juust a moment...\n(Validating code, promoting server domain space, other security stuff...)',
634
+ animationEmoji: '🔐'
635
+ });
636
+ const resVerify = await getApiBridge().postVerifyCode({ email, primaryTjpAddr: primaryTjpAddr, code });
637
+ if (!resVerify.success) {
638
+ this.showBusy({ isBusy: false });
639
+ this.setStatus(resVerify.message || "Incorrect verification code.", "error");
640
+ btnSubmitCode.disabled = false;
641
+ btnSubmitCode.textContent = "Verify Code";
642
+ return;
643
+ }
644
+ console.log(`${lc} Code verified & server domain promoted! Syncing delegate...`);
645
+ this.showBusy({
646
+ isBusy: true,
647
+ title: 'Syncing Delegate...',
648
+ msg: 'Posting evolved domain identity and sync delegate to server...',
649
+ animationEmoji: '⚡'
650
+ });
651
+ btnSubmitCode.removeEventListener('click', onSubmitCode);
652
+ secVerify.classList.add('hidden');
653
+ this.setFormDisabled(false);
654
+ // Now that server domain space is promoted, post evolved keystone + delegate
655
+ const resEvolve = await getApiBridge().putEvolveKeystone({
656
+ domainAddr: primaryTjpAddr,
657
+ keystoneIbGib: evolvedParentKeystoneIbGib,
658
+ relatedKeystoneIbGibs: [delegateKeystoneIbGib]
659
+ });
660
+ if (!resEvolve.success) {
661
+ console.warn(`${lc} Delegate sync warning after code verification: ${resEvolve.message}`);
662
+ }
663
+ resolve();
664
+ };
665
+ btnSubmitCode.addEventListener('click', onSubmitCode);
666
+ });
346
667
  }
347
- // 5. Register with the keystones special index
348
- this.showBusy({
349
- isBusy: true,
350
- title: 'Registering Identity...',
351
- msg: 'Server sync succeeded!\nUpdating local identity indexes and scope settings...',
352
- animationEmoji: '💾'
353
- });
354
- await delay(humanEyeDelay); // for human eyes, not needed for functionality
355
- await updateSpecialIndex({
356
- type: "keystones",
357
- rel8nInfos: [
358
- {
359
- rel8nName: "keystone",
360
- ibGibs: [
361
- toDto({ ibGib: evolvedParentKeystoneIbGib }),
362
- toDto({ ibGib: delegateKeystoneIbGib })
363
- ],
364
- },
365
- ],
366
- metaspace,
367
- space,
368
- });
369
- // Expose for dev tools
370
- window.dev_domainI = evolvedParentKeystoneIbGib;
371
- window.dev_domainIMasterSecret = masterSecret;
372
- window.dev_syncDelegate = delegateKeystoneIbGib;
373
- window.dev_syncDelegateSecret = delegateSecret;
374
- // 6. Request to make the new identity active if the checkbox is checked
375
- const makeActive = this.elements.checkboxMakeActive?.checked;
376
- if (makeActive) {
377
- window.dispatchEvent(new CustomEvent(EVENT_IBGIB_IDENTITY_REQUEST_CHANGE, {
378
- detail: { activeIdentityAddr: evolvedParentAddr },
379
- bubbles: true,
380
- composed: true
381
- }));
668
+ else {
669
+ // Post evolved keystone + delegate keystone immediately (no email or pre-verified)
670
+ this.setStatus("Syncing delegate registration to server...", "info");
671
+ this.showBusy({
672
+ isBusy: true,
673
+ title: 'Syncing Delegate...',
674
+ msg: 'Posting evolved domain identity and sync delegate to server...',
675
+ animationEmoji: '🔄'
676
+ });
677
+ const resEvolve = await getApiBridge().putEvolveKeystone({
678
+ domainAddr: primaryTjpAddr,
679
+ keystoneIbGib: evolvedParentKeystoneIbGib,
680
+ relatedKeystoneIbGibs: [delegateKeystoneIbGib]
681
+ });
682
+ if (!resEvolve.success) {
683
+ const msg = `Delegate registration sync failed: ${resEvolve.message}`;
684
+ console.error(`${lc} ${msg}`);
685
+ this.setStatus(msg, "error");
686
+ this.showBusy({ isBusy: false });
687
+ this.setButtonLoading(false);
688
+ return;
689
+ }
382
690
  }
383
- // 7. Success
384
- this.elements.keystoneAddrEl.textContent = evolvedParentAddr;
385
- this.elements.statusArea.classList.remove('hidden');
386
- this.setStatus("Keystone successfully created and synced!", "success");
387
- await delay(humanEyeDelay); // for human eyes, not needed for functionality
388
- console.log(`✓ Keystone successfully created and synced!`);
691
+ // 5. Finalize onboarding: update keystones index, activate identity, and navigate to identity manager
692
+ await this.finalizeIdentityOnboarding({
693
+ primaryKeystone: evolvedParentKeystoneIbGib,
694
+ delegateKeystone: delegateKeystoneIbGib,
695
+ primaryTjpAddr: evolvedParentAddr,
696
+ masterSecret,
697
+ delegateSecret,
698
+ makeActive: this.elements.checkboxMakeActive?.checked ?? true
699
+ });
389
700
  }
390
701
  catch (error) {
391
702
  const emsg = extractErrorMsg(error);
@@ -400,6 +711,141 @@ export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
400
711
  this.setButtonLoading(false);
401
712
  }
402
713
  }
714
+ /**
715
+ * DRY helper to register keystones in the local keystones special index,
716
+ * set the active identity, and navigate to the Identity Manager component.
717
+ */
718
+ async finalizeIdentityOnboarding({ primaryKeystone, primaryGraph, delegateKeystone, primaryTjpAddr, masterSecret, delegateSecret, makeActive = true, }) {
719
+ const lc = `${this.lc}[${this.finalizeIdentityOnboarding.name}]`;
720
+ const humanEyeDelay = 500;
721
+ const metaspace = await getGlobalMetaspace_waitIfNeeded();
722
+ if (!metaspace) {
723
+ throw new Error(`(UNEXPECTED) metaspace falsy? Expected active global metaspace instance. (E: 2e37a8b6fe987449c5d8693a7d4a2827)`);
724
+ }
725
+ const space = await metaspace.getLocalUserSpace({});
726
+ if (!space) {
727
+ throw new Error(`(UNEXPECTED) space falsy? Expected valid default local user space from metaspace. (E: 1e37a8b6fe987449c5d8693a7d4a2826)`);
728
+ }
729
+ // 0. Persist all frames of primary identity graph into local browser space
730
+ if (primaryGraph) {
731
+ const graphIbGibs = Object.values(primaryGraph);
732
+ for (const frame of graphIbGibs) {
733
+ await metaspace.put({ ibGibs: [frame], space });
734
+ await metaspace.registerNewIbGib({ ibGib: frame, space });
735
+ }
736
+ }
737
+ else if (primaryKeystone) {
738
+ await metaspace.put({ ibGibs: [primaryKeystone], space });
739
+ await metaspace.registerNewIbGib({ ibGib: primaryKeystone, space });
740
+ }
741
+ // 0.5 Verify primary keystone exists in local browser space via getLatestKeystone
742
+ const keystoneService = new KeystoneService_V1();
743
+ try {
744
+ await keystoneService.getLatestKeystone({
745
+ addr: primaryTjpAddr,
746
+ metaspace,
747
+ space
748
+ });
749
+ }
750
+ catch (error) {
751
+ const emsg = `Primary identity keystone ${primaryTjpAddr} not found in local browser space! Error: ${extractErrorMsg(error)}`;
752
+ console.error(`${lc} ${emsg} (E: a123456789abcdef0123456789c0001)`);
753
+ throw new Error(emsg);
754
+ }
755
+ // 1. Register with the local keystones special index
756
+ this.showBusy({
757
+ isBusy: true,
758
+ title: 'Registering Identity...',
759
+ msg: 'Updating local identity indexes and scope settings...',
760
+ animationEmoji: '💾'
761
+ });
762
+ const ibGibsToRegister = [toDto({ ibGib: delegateKeystone })];
763
+ if (!primaryKeystone && !primaryGraph) {
764
+ throw new Error(`(UNEXPECTED) primaryKeystone and primaryGraph both falsy? We should have gotten this back from the server. (E: e34cd882ac66633c75c53318a3b28826)`);
765
+ }
766
+ if (!delegateKeystone) {
767
+ throw new Error(`(UNEXPECTED) delegateKeystone falsy? We should have made this locally and even registered it with the server. (E: 56b1c396269e15f008b7e298c71bc826)`);
768
+ }
769
+ if (primaryKeystone) {
770
+ ibGibsToRegister.unshift(toDto({ ibGib: primaryKeystone }));
771
+ }
772
+ await updateSpecialIndex({
773
+ type: "keystones",
774
+ rel8nInfos: [
775
+ {
776
+ rel8nName: "keystone",
777
+ ibGibs: ibGibsToRegister,
778
+ },
779
+ ],
780
+ metaspace,
781
+ space,
782
+ });
783
+ // Expose for dev tools debugging if available
784
+ if (masterSecret) {
785
+ window.dev_domainI = primaryKeystone;
786
+ window.dev_domainIMasterSecret = masterSecret;
787
+ window.dev_syncDelegate = delegateKeystone;
788
+ window.dev_syncDelegateSecret = delegateSecret;
789
+ }
790
+ // 2. Request to make the new identity active
791
+ if (makeActive) {
792
+ window.dispatchEvent(new CustomEvent(EVENT_IBGIB_IDENTITY_REQUEST_CHANGE, {
793
+ detail: { activeIdentityAddr: primaryTjpAddr },
794
+ bubbles: true,
795
+ composed: true
796
+ }));
797
+ }
798
+ // 3. Display success status
799
+ const successMsg = "Identity successfully created and activated!";
800
+ this.showBusy({
801
+ isBusy: true,
802
+ title: 'Success!',
803
+ msg: successMsg,
804
+ animationEmoji: '✅'
805
+ });
806
+ await delay(humanEyeDelay);
807
+ if (this.elements?.keystoneAddrEl) {
808
+ this.elements.keystoneAddrEl.textContent = primaryTjpAddr;
809
+ }
810
+ if (this.elements?.statusArea) {
811
+ this.elements.statusArea.classList.remove('hidden');
812
+ }
813
+ this.setStatus(successMsg, "success");
814
+ console.log(`${lc} ✓ ${successMsg}`);
815
+ // 4. Dispatch UI navigation message to close creator and open identity manager
816
+ window.dispatchEvent(new CustomEvent(EVENT_IBGIB_UI_MESSAGE, {
817
+ detail: { action: 'show-identity-manager', addr: primaryTjpAddr },
818
+ bubbles: true,
819
+ composed: true
820
+ }));
821
+ this.showBusy({ isBusy: false });
822
+ }
823
+ setFormDisabled(disabled) {
824
+ const root = this.shadowRoot;
825
+ const inputIds = [
826
+ 'input-username',
827
+ 'input-description',
828
+ 'input-email',
829
+ 'input-secret',
830
+ 'checkbox-make-active',
831
+ 'btn-generate',
832
+ 'btn-switch-sign-in',
833
+ ];
834
+ for (const id of inputIds) {
835
+ const el = root.getElementById(id);
836
+ if (el) {
837
+ el.disabled = disabled;
838
+ if (disabled) {
839
+ el.style.opacity = '0.5';
840
+ el.style.pointerEvents = 'none';
841
+ }
842
+ else {
843
+ el.style.opacity = '1';
844
+ el.style.pointerEvents = 'auto';
845
+ }
846
+ }
847
+ }
848
+ }
403
849
  async renderUI() {
404
850
  // No complex rendering needed for this simple creator component yet
405
851
  }