@ibgib/web-gib 0.0.60 → 0.0.61

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 (60) hide show
  1. package/dist/AUTO-GENERATED-version.d.mts +1 -1
  2. package/dist/AUTO-GENERATED-version.mjs +1 -1
  3. package/dist/helpers.d.mts.map +1 -1
  4. package/dist/helpers.mjs +1 -0
  5. package/dist/helpers.mjs.map +1 -1
  6. package/dist/identity/device-delegate-helper.d.mts +43 -0
  7. package/dist/identity/device-delegate-helper.d.mts.map +1 -0
  8. package/dist/identity/device-delegate-helper.mjs +106 -0
  9. package/dist/identity/device-delegate-helper.mjs.map +1 -0
  10. package/dist/identity/device-delegate.respec.d.mts +2 -0
  11. package/dist/identity/device-delegate.respec.d.mts.map +1 -0
  12. package/dist/identity/device-delegate.respec.mjs +106 -0
  13. package/dist/identity/device-delegate.respec.mjs.map +1 -0
  14. package/dist/identity/keystone-sync-service.d.mts +18 -0
  15. package/dist/identity/keystone-sync-service.d.mts.map +1 -1
  16. package/dist/identity/keystone-sync-service.mjs +77 -1
  17. package/dist/identity/keystone-sync-service.mjs.map +1 -1
  18. package/dist/index.d.mts +1 -1
  19. package/dist/index.d.mts.map +1 -1
  20. package/dist/index.mjs +1 -2
  21. package/dist/index.mjs.map +1 -1
  22. package/dist/storage/storage-helpers.web.d.mts +9 -0
  23. package/dist/storage/storage-helpers.web.d.mts.map +1 -1
  24. package/dist/storage/storage-helpers.web.mjs +42 -0
  25. package/dist/storage/storage-helpers.web.mjs.map +1 -1
  26. package/dist/ui/component/identity/add-device/add-device.d.mts.map +1 -1
  27. package/dist/ui/component/identity/add-device/add-device.mjs +30 -43
  28. package/dist/ui/component/identity/add-device/add-device.mjs.map +1 -1
  29. package/dist/ui/component/identity/keystone-creator/keystone-creator.d.mts.map +1 -1
  30. package/dist/ui/component/identity/keystone-creator/keystone-creator.mjs +41 -57
  31. package/dist/ui/component/identity/keystone-creator/keystone-creator.mjs.map +1 -1
  32. package/dist/ui/component/identity/keystone-details/keystone-details.d.mts +3 -0
  33. package/dist/ui/component/identity/keystone-details/keystone-details.d.mts.map +1 -1
  34. package/dist/ui/component/identity/keystone-details/keystone-details.mjs +307 -46
  35. package/dist/ui/component/identity/keystone-details/keystone-details.mjs.map +1 -1
  36. package/dist/ui/ui-constants.d.mts +1 -0
  37. package/dist/ui/ui-constants.d.mts.map +1 -1
  38. package/dist/ui/ui-constants.mjs +1 -0
  39. package/dist/ui/ui-constants.mjs.map +1 -1
  40. package/dist/ui/ui-types.d.mts +5 -0
  41. package/dist/ui/ui-types.d.mts.map +1 -1
  42. package/package.json +2 -2
  43. package/src/AUTO-GENERATED-version.mts +1 -1
  44. package/src/helpers.mts +1 -0
  45. package/src/identity/custodian-delegate-helper.mts.bak +7 -0
  46. package/src/identity/device-delegate-helper.mts +135 -0
  47. package/src/identity/device-delegate.respec.mts +125 -0
  48. package/src/identity/keystone-sync-service.mts +95 -2
  49. package/src/index.mts +1 -2
  50. package/src/storage/storage-helpers.web.mts +47 -0
  51. package/src/ui/component/identity/add-device/add-device.mts +36 -48
  52. package/src/ui/component/identity/keystone-creator/keystone-creator.mts +48 -65
  53. package/src/ui/component/identity/keystone-details/keystone-details.mts +317 -49
  54. package/src/ui/ui-constants.mts +1 -0
  55. package/src/ui/ui-types.mts +6 -0
  56. package/dist/identity/custodian-delegate-helper.d.mts +0 -22
  57. package/dist/identity/custodian-delegate-helper.d.mts.map +0 -1
  58. package/dist/identity/custodian-delegate-helper.mjs +0 -67
  59. package/dist/identity/custodian-delegate-helper.mjs.map +0 -1
  60. package/src/identity/custodian-delegate-helper.mts +0 -86
@@ -15,7 +15,9 @@ import { parseSpaceIb } from "@ibgib/core-gib/dist/witness/space/space-helper.mj
15
15
  import { executeSsoOAuthPopup } from "../../../../identity/sso-popup-helper.mjs";
16
16
  import { getComponentCtorArg } from "../../../../app-bootstrap/init-orchestration.mjs";
17
17
  import { promptForSecret } from "../../../../helpers.web.mjs";
18
- import { isSsoProviderLinked } from "../../../../identity/sso/sso-helpers.mjs";
18
+ import { isSsoProviderLinked, getKeystoneCapabilities, isDelegateExpired } from "@ibgib/core-gib/dist/keystone/delegate/delegate-helpers.mjs";
19
+ import { generatePoolChallenges } from "@ibgib/core-gib/dist/keystone/keystone-helpers.mjs";
20
+ import { POOL_ID_MANAGE } from "@ibgib/core-gib/dist/keystone/keystone-constants.mjs";
19
21
  import { getGlobalMetaspace_waitIfNeeded } from "../../../../helpers.mjs";
20
22
  const logalot = false;
21
23
  export const KEYSTONE_DETAILS_COMPONENT_NAME = 'ibgib-keystone-details';
@@ -51,6 +53,7 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
51
53
  isHistoricalFrame = false;
52
54
  _onIdentityChanged = null;
53
55
  keystoneCache = new Map();
56
+ aggregatedDetails;
54
57
  constructor() {
55
58
  super();
56
59
  }
@@ -394,21 +397,36 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
394
397
  this.elements.btnSetActiveEl.disabled = isTimelineActive;
395
398
  this.elements.btnLinkGoogleEl.classList.remove('disabled');
396
399
  this.elements.btnLinkGithubEl.classList.remove('disabled');
397
- // Check if already linked
398
- const isGoogleLinked = isSsoProviderLinked({ keystone: this.ibGib, providerId: 'google' });
399
- const isGithubLinked = isSsoProviderLinked({ keystone: this.ibGib, providerId: 'github' });
400
+ // Check if origin provider or linked
401
+ const originProvider = this.aggregatedDetails?.custodianProvider || this.ibGib?.data?.frameDetails?.custodianProvider;
402
+ const isGoogleLinked = isSsoProviderLinked({ keystone: this.ibGib, aggregatedDetails: this.aggregatedDetails, providerId: 'google' });
403
+ const isGithubLinked = isSsoProviderLinked({ keystone: this.ibGib, aggregatedDetails: this.aggregatedDetails, providerId: 'github' });
404
+ const caps = getKeystoneCapabilities({ keystone: this.ibGib, aggregatedDetails: this.aggregatedDetails });
400
405
  // Google Card States
401
406
  const googleCard = this.elements.btnLinkGoogleEl;
402
407
  const googleLabel = googleCard.querySelector('.toggle-state-text');
403
408
  if (isGoogleLinked) {
404
409
  googleCard.classList.remove('unlinked');
405
410
  googleCard.classList.add('linked');
406
- googleLabel.textContent = "Google Linked ✅";
411
+ const isOrigin = originProvider === 'google';
412
+ if (isOrigin && !caps.isSovereign) {
413
+ googleLabel.textContent = "Google (Primary Account) 🔒";
414
+ googleCard.title = "Cannot unlink primary account without first adding a Master Password below.";
415
+ }
416
+ else if (isOrigin) {
417
+ googleLabel.textContent = "Google (Primary Account) ✅";
418
+ googleCard.title = "Click to unlink Google provider (requires Master Password).";
419
+ }
420
+ else {
421
+ googleLabel.textContent = "Google Linked ✅";
422
+ googleCard.title = "Click to unlink Google provider.";
423
+ }
407
424
  }
408
425
  else {
409
426
  googleCard.classList.remove('linked');
410
427
  googleCard.classList.add('unlinked');
411
428
  googleLabel.textContent = "Unlinked";
429
+ googleCard.title = "Click to link Google account.";
412
430
  }
413
431
  // GitHub Card States
414
432
  const githubCard = this.elements.btnLinkGithubEl;
@@ -416,15 +434,56 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
416
434
  if (isGithubLinked) {
417
435
  githubCard.classList.remove('unlinked');
418
436
  githubCard.classList.add('linked');
419
- githubLabel.textContent = "GitHub Linked ✅";
437
+ const isOrigin = originProvider === 'github';
438
+ if (isOrigin && !caps.isSovereign) {
439
+ githubLabel.textContent = "GitHub (Primary Account) 🔒";
440
+ githubCard.title = "Cannot unlink primary account without first adding a Master Password below.";
441
+ }
442
+ else if (isOrigin) {
443
+ githubLabel.textContent = "GitHub (Primary Account) ✅";
444
+ githubCard.title = "Click to unlink GitHub provider (requires Master Password).";
445
+ }
446
+ else {
447
+ githubLabel.textContent = "GitHub Linked ✅";
448
+ githubCard.title = "Click to unlink GitHub provider.";
449
+ }
420
450
  }
421
451
  else {
422
452
  githubCard.classList.remove('linked');
423
453
  githubCard.classList.add('unlinked');
424
454
  githubLabel.textContent = "Unlinked";
455
+ githubCard.title = "Click to link GitHub account.";
425
456
  }
426
457
  }
427
458
  }
459
+ updatePasswordCardView() {
460
+ const caps = getKeystoneCapabilities({ keystone: this.ibGib, aggregatedDetails: this.aggregatedDetails });
461
+ const cardTitleEl = this.elements?.changePasswordCardEl?.querySelector('h3');
462
+ const cardDescEl = this.elements?.changePasswordCardEl?.querySelector('.description');
463
+ const currentPasswordGroup = this.elements?.inputCurrentPasswordEl?.closest('.form-group');
464
+ const btnChangePassword = this.elements?.btnChangePasswordEl;
465
+ const isCustodialOnly = caps.isCustodial && !caps.isSovereign;
466
+ if (isCustodialOnly) {
467
+ if (cardTitleEl)
468
+ cardTitleEl.textContent = "Add Master Password";
469
+ if (cardDescEl)
470
+ cardDescEl.textContent = "Establish sovereign ownership by adding a master password. This enables direct, independent cryptographic management without relying solely on custodian authentication.";
471
+ if (currentPasswordGroup)
472
+ currentPasswordGroup.style.display = 'none';
473
+ if (btnChangePassword)
474
+ btnChangePassword.textContent = "Add Master Password";
475
+ }
476
+ else {
477
+ if (cardTitleEl)
478
+ cardTitleEl.textContent = "Change Password";
479
+ if (cardDescEl)
480
+ cardDescEl.textContent = "Rotate your local password that drives this identity. This will cryptographically evolve your primary identity and all registered user-owned delegate keystones to the new password.";
481
+ if (currentPasswordGroup)
482
+ currentPasswordGroup.style.display = '';
483
+ if (btnChangePassword)
484
+ btnChangePassword.textContent = "Update Password";
485
+ }
486
+ }
428
487
  async updateFrameDetailsView() {
429
488
  const lc = `${this.lc}[${this.updateFrameDetailsView.name}]`;
430
489
  try {
@@ -484,6 +543,7 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
484
543
  metaspace,
485
544
  space
486
545
  });
546
+ this.aggregatedDetails = aggregated;
487
547
  this.elements.identityAggrDetailsEl.textContent = aggregated ? pretty(aggregated) : '{}';
488
548
  // Update Username and Description fields
489
549
  this.elements.identityNameEl.textContent = aggregated?.username || aggregated?.profile?.name || aggregated?.name || '-';
@@ -491,18 +551,21 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
491
551
  }
492
552
  catch (err) {
493
553
  console.warn(`${lc} Error aggregating details: ${extractErrorMsg(err)}`);
554
+ this.aggregatedDetails = undefined;
494
555
  this.elements.identityAggrDetailsEl.textContent = '{}';
495
556
  this.elements.identityNameEl.textContent = '-';
496
557
  this.elements.identityDescriptionEl.textContent = '-';
497
558
  }
498
559
  }
499
560
  else {
561
+ this.aggregatedDetails = undefined;
500
562
  this.elements.identityAggrDetailsEl.textContent = '{}';
501
563
  this.elements.identityNameEl.textContent = '-';
502
564
  this.elements.identityDescriptionEl.textContent = '-';
503
565
  }
504
566
  }
505
567
  else {
568
+ this.aggregatedDetails = undefined;
506
569
  this.elements.identityAggrDetailsEl.textContent = '{}';
507
570
  this.elements.identityNameEl.textContent = '-';
508
571
  this.elements.identityDescriptionEl.textContent = '-';
@@ -532,6 +595,7 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
532
595
  await this.renderDelegates(keystone.data?.delegates);
533
596
  this.elements.ssoLinkCardEl.classList.remove('hidden');
534
597
  this.elements.changePasswordCardEl.classList.remove('hidden');
598
+ this.updatePasswordCardView();
535
599
  }
536
600
  else {
537
601
  this.elements.delegatesCardEl.classList.add('hidden');
@@ -554,6 +618,7 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
554
618
  if (!this.elements) {
555
619
  return;
556
620
  }
621
+ this.aggregatedDetails = undefined;
557
622
  this.isHistoricalFrame = false;
558
623
  if (this.elements.historicalWarningBannerEl) {
559
624
  this.elements.historicalWarningBannerEl.classList.add('hidden');
@@ -730,39 +795,66 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
730
795
  return;
731
796
  }
732
797
  const metaspace = await getGlobalMetaspace_waitIfNeeded();
733
- let localSpaceId;
734
- if (metaspace) {
735
- const space = await metaspace.getLocalUserSpace({ lock: false });
736
- if (space?.ib) {
737
- localSpaceId = parseSpaceIb({ spaceIb: space.ib }).spaceId;
738
- }
798
+ if (!metaspace) {
799
+ throw new Error(`(UNEXPECTED) metaspace falsy? (E: 8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3e)`);
800
+ }
801
+ const space = await metaspace.getLocalUserSpace({ lock: false });
802
+ if (!space) {
803
+ throw new Error(`(UNEXPECTED) space falsy? (E: 9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4f)`);
739
804
  }
805
+ const localSpaceId = space.ib ? parseSpaceIb({ spaceIb: space.ib }).spaceId : undefined;
740
806
  for (const [delegateTjpAddr, info] of Object.entries(delegates)) {
741
807
  const tr = document.createElement('tr');
742
808
  const deviceName = info.deviceName || info.details?.deviceTag || info.details?.name || 'Device Delegate';
743
809
  const tdName = document.createElement('td');
744
810
  const delegateSpaceId = info.spaceId || info.details?.spaceId;
745
- let badgeEl = null;
746
- if (delegateSpaceId) {
747
- badgeEl = document.createElement('span');
748
- badgeEl.style.marginLeft = '0.5rem';
749
- badgeEl.style.padding = '0.15rem 0.4rem';
750
- badgeEl.style.borderRadius = '4px';
751
- badgeEl.style.fontSize = '0.75rem';
752
- badgeEl.style.fontWeight = 'bold';
753
- if (localSpaceId && delegateSpaceId === localSpaceId) {
754
- badgeEl.textContent = 'This Device';
755
- badgeEl.className = 'badge badge-this-device';
756
- badgeEl.style.backgroundColor = '#1b5e20';
757
- badgeEl.style.color = '#a5d6a7';
811
+ // Check if delegate is present in local space (via spaceId match or direct presence check)
812
+ let isThisDevice = false;
813
+ if (localSpaceId && delegateSpaceId === localSpaceId) {
814
+ isThisDevice = true;
815
+ }
816
+ else {
817
+ try {
818
+ const resGet = await metaspace.get({ addrs: [delegateTjpAddr], space });
819
+ isThisDevice = !!(resGet.success && resGet.ibGibs && resGet.ibGibs.length > 0);
758
820
  }
759
- else {
760
- badgeEl.textContent = 'Remote Device';
761
- badgeEl.className = 'badge badge-remote-device';
762
- badgeEl.style.backgroundColor = '#0d47a1';
763
- badgeEl.style.color = '#90caf9';
821
+ catch {
822
+ isThisDevice = false;
764
823
  }
765
824
  }
825
+ const badgeEl = document.createElement('span');
826
+ badgeEl.style.marginLeft = '0.5rem';
827
+ badgeEl.style.padding = '0.15rem 0.4rem';
828
+ badgeEl.style.borderRadius = '4px';
829
+ badgeEl.style.fontSize = '0.75rem';
830
+ badgeEl.style.fontWeight = 'bold';
831
+ if (isThisDevice) {
832
+ badgeEl.textContent = 'This Device';
833
+ badgeEl.className = 'badge badge-this-device';
834
+ badgeEl.style.backgroundColor = '#1b5e20';
835
+ badgeEl.style.color = '#a5d6a7';
836
+ }
837
+ else {
838
+ badgeEl.textContent = 'Remote Device';
839
+ badgeEl.className = 'badge badge-remote-device';
840
+ badgeEl.style.backgroundColor = '#0d47a1';
841
+ badgeEl.style.color = '#90caf9';
842
+ }
843
+ // Check expiration
844
+ const isExpired = isDelegateExpired({ delegateInfo: info });
845
+ let expiredBadgeEl = null;
846
+ if (isExpired) {
847
+ expiredBadgeEl = document.createElement('span');
848
+ expiredBadgeEl.textContent = 'Expired';
849
+ expiredBadgeEl.className = 'badge badge-expired';
850
+ expiredBadgeEl.style.marginLeft = '0.3rem';
851
+ expiredBadgeEl.style.padding = '0.15rem 0.4rem';
852
+ expiredBadgeEl.style.borderRadius = '4px';
853
+ expiredBadgeEl.style.fontSize = '0.75rem';
854
+ expiredBadgeEl.style.fontWeight = 'bold';
855
+ expiredBadgeEl.style.backgroundColor = '#b71c1c';
856
+ expiredBadgeEl.style.color = '#ef9a9a';
857
+ }
766
858
  const applyNameAndBadge = (displayName, hoverText) => {
767
859
  tdName.innerHTML = '';
768
860
  const textSpan = document.createElement('span');
@@ -771,6 +863,9 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
771
863
  if (badgeEl) {
772
864
  tdName.appendChild(badgeEl);
773
865
  }
866
+ if (expiredBadgeEl) {
867
+ tdName.appendChild(expiredBadgeEl);
868
+ }
774
869
  tdName.title = hoverText || displayName;
775
870
  };
776
871
  const displayName = `💻 ${deviceName}`;
@@ -781,7 +876,7 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
781
876
  const tdVerbs = document.createElement('td');
782
877
  tdVerbs.textContent = info.allowedVerbs ? info.allowedVerbs.join(', ') : '-';
783
878
  tdVerbs.title = tdVerbs.textContent;
784
- const isRemote = !!(delegateSpaceId && localSpaceId && delegateSpaceId !== localSpaceId);
879
+ const isRemote = !isThisDevice;
785
880
  const tdActions = document.createElement('td');
786
881
  const btnView = document.createElement('button');
787
882
  btnView.className = 'action-btn secondary small';
@@ -792,22 +887,22 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
792
887
  this.showRemoteDelegateModal(info);
793
888
  }
794
889
  else {
795
- if (info.delegateAddr) {
890
+ if (info.delegateAddr || delegateTjpAddr) {
796
891
  let addrToLoad = info.delegateAddr;
797
- if (metaspace) {
798
- const space = await metaspace.getLocalUserSpace({ lock: false });
799
- if (space) {
800
- try {
801
- const latest = await metaspace.getLatestAddr({ addr: delegateTjpAddr, space });
802
- if (latest) {
803
- addrToLoad = latest;
804
- }
805
- }
806
- catch (err) {
807
- console.warn(`${lc} Error resolving latest delegate address: ${extractErrorMsg(err)}`);
808
- }
892
+ try {
893
+ const keystoneService = new KeystoneService_V1();
894
+ const latestKeystone = await keystoneService.getLatestKeystone({
895
+ addr: delegateTjpAddr,
896
+ metaspace,
897
+ space
898
+ });
899
+ if (latestKeystone) {
900
+ addrToLoad = getIbGibAddr({ ibGib: latestKeystone });
809
901
  }
810
902
  }
903
+ catch (err) {
904
+ console.warn(`${lc} Error resolving latest delegate keystone: ${extractErrorMsg(err)}`);
905
+ }
811
906
  this.dispatchEvent(new CustomEvent('ibgib-view-keystone-details', {
812
907
  detail: { addr: addrToLoad },
813
908
  bubbles: true,
@@ -1090,8 +1185,13 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
1090
1185
  }
1091
1186
  }
1092
1187
  async handleSSOClick(providerId) {
1093
- const isLinked = isSsoProviderLinked({ keystone: this.ibGib, providerId });
1188
+ const isLinked = isSsoProviderLinked({ keystone: this.ibGib, aggregatedDetails: this.aggregatedDetails, providerId });
1094
1189
  if (isLinked) {
1190
+ const caps = getKeystoneCapabilities({ keystone: this.ibGib, aggregatedDetails: this.aggregatedDetails });
1191
+ if (!caps.isSovereign) {
1192
+ this.setSSOStatus(`Cannot unlink ${providerId}: This is your only identity recovery and management method. Please add a Master Password first in the Password card below to enable sovereign ownership before unlinking.`, "error");
1193
+ return;
1194
+ }
1095
1195
  await this.handleSSOUnlink(providerId);
1096
1196
  }
1097
1197
  else {
@@ -1149,13 +1249,28 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
1149
1249
  }
1150
1250
  // 2. Evolve parent keystone to remove the custodian pool
1151
1251
  const targetPoolId = `custodian-manage-${providerId}`;
1152
- const evolvedParent = await keystoneService.removePools({
1252
+ let evolvedParent = await keystoneService.removePools({
1153
1253
  latestKeystone: this.ibGib,
1154
1254
  masterSecret: masterSecret,
1155
1255
  poolIds: [targetPoolId],
1156
1256
  metaspace,
1157
1257
  space
1158
1258
  });
1259
+ // Unregister any corresponding outward delegate for this provider if present
1260
+ const delegatesMap = this.ibGib?.data?.delegates;
1261
+ if (delegatesMap) {
1262
+ for (const [delegateTjpAddr, info] of Object.entries(delegatesMap)) {
1263
+ if (info.deviceName?.includes(providerId)) {
1264
+ evolvedParent = await keystoneService.unregisterDelegate({
1265
+ parentKeystone: evolvedParent,
1266
+ delegateTjpAddr,
1267
+ masterSecret: masterSecret,
1268
+ metaspace,
1269
+ space,
1270
+ });
1271
+ }
1272
+ }
1273
+ }
1159
1274
  // 3. Sync evolved parent keystone back to server
1160
1275
  this.setSSOStatus("Publishing evolved identity keystone to server...", "info");
1161
1276
  const syncRes = await getApiBridge().putEvolveKeystone({
@@ -1192,8 +1307,154 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
1192
1307
  el.className = `status-msg ${type}`;
1193
1308
  el.classList.remove('hidden');
1194
1309
  }
1310
+ async handleAddMasterPassword() {
1311
+ const lc = `${this.lc}[${this.handleAddMasterPassword.name}]`;
1312
+ this.setChangePasswordStatus("Validating entries...", "info");
1313
+ try {
1314
+ const newPassword = this.elements.inputNewPasswordEl.value;
1315
+ const confirmPassword = this.elements.inputConfirmPasswordEl.value;
1316
+ if (!newPassword) {
1317
+ this.setChangePasswordStatus("Master password is required.", "error");
1318
+ return;
1319
+ }
1320
+ if (newPassword !== confirmPassword) {
1321
+ this.setChangePasswordStatus("Master password and confirmation do not match.", "error");
1322
+ return;
1323
+ }
1324
+ const initialManagePool = this.ibGib?.data?.challengePools?.find(p => p.id === POOL_ID_MANAGE);
1325
+ if (!initialManagePool) {
1326
+ this.setChangePasswordStatus("Primary keystone missing active manage challenge pool.", "error");
1327
+ return;
1328
+ }
1329
+ this.showBusy({
1330
+ isBusy: true,
1331
+ title: 'Elevating Identity...',
1332
+ msg: 'Generating cryptographic sovereign challenge pool...',
1333
+ animationEmoji: '🔑'
1334
+ });
1335
+ this.setChangePasswordStatus("Generating sovereign challenge pool...", "info");
1336
+ const newChallenges = await generatePoolChallenges({
1337
+ config: initialManagePool.config,
1338
+ masterSecret: newPassword,
1339
+ });
1340
+ const newManagePool = {
1341
+ id: POOL_ID_MANAGE,
1342
+ config: initialManagePool.config,
1343
+ challenges: newChallenges,
1344
+ };
1345
+ const primaryTjpAddr = getTjpAddr({ ibGib: this.ibGib }) || getIbGibAddr({ ibGib: this.ibGib });
1346
+ const custodianProvider = this.aggregatedDetails?.custodianProvider || 'google';
1347
+ let requestBody = {
1348
+ primaryTjpAddr,
1349
+ newManagePool,
1350
+ };
1351
+ if (custodianProvider === 'google' || custodianProvider === 'github') {
1352
+ this.showBusy({
1353
+ isBusy: true,
1354
+ title: 'Re-authenticating with Provider...',
1355
+ msg: `Please complete authentication in the ${custodianProvider} popup window...`,
1356
+ animationEmoji: '🔒'
1357
+ });
1358
+ this.setChangePasswordStatus(`Please authenticate in the popup with ${custodianProvider} to verify ownership...`, "info");
1359
+ const resPopup = await executeSsoOAuthPopup({
1360
+ providerId: custodianProvider,
1361
+ parentTjpAddr: primaryTjpAddr,
1362
+ });
1363
+ if (!resPopup.success || !resPopup.code) {
1364
+ this.showBusy({ isBusy: false });
1365
+ this.setChangePasswordStatus(resPopup.message || "Re-authentication cancelled.", "error");
1366
+ return;
1367
+ }
1368
+ requestBody.providerId = custodianProvider;
1369
+ requestBody.code = resPopup.code;
1370
+ requestBody.redirectUri = resPopup.redirectUri;
1371
+ }
1372
+ else {
1373
+ this.showBusy({ isBusy: false });
1374
+ // Email OTP fallback
1375
+ const email = this.aggregatedDetails?.emails?.[0] || this.ibGib?.data?.emails?.[0];
1376
+ const otpCode = await promptForSecret({
1377
+ msg: `Enter 6-digit verification code sent to ${email}:`,
1378
+ confirm: false,
1379
+ });
1380
+ if (!otpCode) {
1381
+ this.setChangePasswordStatus("Verification code required.", "error");
1382
+ return;
1383
+ }
1384
+ requestBody.email = email;
1385
+ requestBody.code = otpCode;
1386
+ }
1387
+ this.showBusy({
1388
+ isBusy: true,
1389
+ title: 'Updating Keystone Timeline...',
1390
+ msg: 'Publishing sovereign master password and evolving identity DAG...',
1391
+ animationEmoji: '⏳'
1392
+ });
1393
+ this.setChangePasswordStatus("Elevating identity to sovereign master password on server...", "info");
1394
+ const response = await fetch('/api/identity/custodian/add-master-password', {
1395
+ method: 'POST',
1396
+ headers: { 'Content-Type': 'application/json' },
1397
+ body: JSON.stringify(requestBody),
1398
+ });
1399
+ const resJson = await response.json();
1400
+ if (!response.ok || !resJson.success) {
1401
+ throw new Error(resJson.error || resJson.message || `Server error (${response.status})`);
1402
+ }
1403
+ const evolvedParent = resJson.primaryKeystone;
1404
+ // Save to local user space & metaspace
1405
+ const metaspace = await getGlobalMetaspace_waitIfNeeded();
1406
+ if (!metaspace) {
1407
+ throw new Error("Global metaspace not available");
1408
+ }
1409
+ const space = await metaspace.getLocalUserSpace({ lock: false });
1410
+ if (!space) {
1411
+ throw new Error("Local user space not available");
1412
+ }
1413
+ await metaspace.put({ ibGibs: [evolvedParent], space });
1414
+ await metaspace.registerNewIbGib({ ibGib: evolvedParent, space });
1415
+ await updateSpecialIndex({
1416
+ type: "keystones",
1417
+ rel8nInfos: [
1418
+ {
1419
+ rel8nName: "keystone",
1420
+ ibGibs: [toDto({ ibGib: evolvedParent })]
1421
+ }
1422
+ ],
1423
+ metaspace,
1424
+ space
1425
+ });
1426
+ const evolvedAddr = getIbGibAddr({ ibGib: evolvedParent });
1427
+ // Dispatch EVENT_IBGIB_IDENTITY_CHANGED
1428
+ window.dispatchEvent(new CustomEvent(EVENT_IBGIB_IDENTITY_CHANGED, {
1429
+ detail: { activeIdentityAddr: evolvedAddr },
1430
+ bubbles: true,
1431
+ composed: true
1432
+ }));
1433
+ this.setChangePasswordStatus("Successfully added Master Password! Identity is now sovereign.", "success");
1434
+ // Refresh the view
1435
+ this.ibGibAddr = evolvedAddr;
1436
+ this.staticIbGib = evolvedParent;
1437
+ await this.loadIbGib({ getLatest: true });
1438
+ // Clear inputs
1439
+ this.elements.inputNewPasswordEl.value = '';
1440
+ this.elements.inputConfirmPasswordEl.value = '';
1441
+ }
1442
+ catch (error) {
1443
+ console.error(`${lc} Elevation failed: ${extractErrorMsg(error)}`);
1444
+ this.setChangePasswordStatus(`Failed to add master password: ${extractErrorMsg(error)}`, "error");
1445
+ }
1446
+ finally {
1447
+ this.showBusy({ isBusy: false });
1448
+ }
1449
+ }
1195
1450
  async handleChangePassword() {
1196
1451
  const lc = `${this.lc}[${this.handleChangePassword.name}]`;
1452
+ const caps = getKeystoneCapabilities({ keystone: this.ibGib, aggregatedDetails: this.aggregatedDetails });
1453
+ const isCustodialOnly = caps.isCustodial && !caps.isSovereign;
1454
+ if (isCustodialOnly) {
1455
+ await this.handleAddMasterPassword();
1456
+ return;
1457
+ }
1197
1458
  this.setChangePasswordStatus("Validating entries...", "info");
1198
1459
  try {
1199
1460
  const currentPassword = this.elements.inputCurrentPasswordEl.value;