@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
@@ -23,10 +23,11 @@ import { updateSpecialIndex } from "@ibgib/core-gib/dist/timeline/timeline-api.m
23
23
  import { getTjpAddr, toDto } from "@ibgib/core-gib/dist/common/other/ibgib-helper.mjs";
24
24
  import { parseSpaceIb } from "@ibgib/core-gib/dist/witness/space/space-helper.mjs";
25
25
  import { executeSsoOAuthPopup } from "../../../../identity/sso-popup-helper.mjs";
26
-
27
26
  import { getComponentCtorArg } from "../../../../app-bootstrap/init-orchestration.mjs";
28
27
  import { promptForSecret } from "../../../../helpers.web.mjs";
29
- import { isSsoProviderLinked } from "../../../../identity/sso/sso-helpers.mjs";
28
+ import { isSsoProviderLinked, getKeystoneCapabilities, isDelegateExpired } from "@ibgib/core-gib/dist/keystone/delegate/delegate-helpers.mjs";
29
+ import { generatePoolChallenges } from "@ibgib/core-gib/dist/keystone/keystone-helpers.mjs";
30
+ import { POOL_ID_MANAGE } from "@ibgib/core-gib/dist/keystone/keystone-constants.mjs";
30
31
  import { getGlobalMetaspace_waitIfNeeded } from "../../../../helpers.mjs";
31
32
 
32
33
  const logalot = false;
@@ -118,6 +119,7 @@ export class KeystoneDetailsComponentInstance
118
119
  private isHistoricalFrame: boolean = false;
119
120
  private _onIdentityChanged: ((e: any) => void) | null = null;
120
121
  private keystoneCache: Map<string, { username: string, description: string, isPrimary: boolean, n: number }> = new Map();
122
+ private aggregatedDetails?: any;
121
123
 
122
124
  constructor() {
123
125
  super();
@@ -454,9 +456,12 @@ export class KeystoneDetailsComponentInstance
454
456
  this.elements.btnLinkGoogleEl.classList.remove('disabled');
455
457
  this.elements.btnLinkGithubEl.classList.remove('disabled');
456
458
 
457
- // Check if already linked
458
- const isGoogleLinked = isSsoProviderLinked({ keystone: this.ibGib, providerId: 'google' });
459
- const isGithubLinked = isSsoProviderLinked({ keystone: this.ibGib, providerId: 'github' });
459
+ // Check if origin provider or linked
460
+ const originProvider = this.aggregatedDetails?.custodianProvider || this.ibGib?.data?.frameDetails?.custodianProvider;
461
+ const isGoogleLinked = isSsoProviderLinked({ keystone: this.ibGib, aggregatedDetails: this.aggregatedDetails, providerId: 'google' });
462
+ const isGithubLinked = isSsoProviderLinked({ keystone: this.ibGib, aggregatedDetails: this.aggregatedDetails, providerId: 'github' });
463
+
464
+ const caps = getKeystoneCapabilities({ keystone: this.ibGib, aggregatedDetails: this.aggregatedDetails });
460
465
 
461
466
  // Google Card States
462
467
  const googleCard = this.elements.btnLinkGoogleEl;
@@ -464,11 +469,22 @@ export class KeystoneDetailsComponentInstance
464
469
  if (isGoogleLinked) {
465
470
  googleCard.classList.remove('unlinked');
466
471
  googleCard.classList.add('linked');
467
- googleLabel.textContent = "Google Linked ✅";
472
+ const isOrigin = originProvider === 'google';
473
+ if (isOrigin && !caps.isSovereign) {
474
+ googleLabel.textContent = "Google (Primary Account) 🔒";
475
+ googleCard.title = "Cannot unlink primary account without first adding a Master Password below.";
476
+ } else if (isOrigin) {
477
+ googleLabel.textContent = "Google (Primary Account) ✅";
478
+ googleCard.title = "Click to unlink Google provider (requires Master Password).";
479
+ } else {
480
+ googleLabel.textContent = "Google Linked ✅";
481
+ googleCard.title = "Click to unlink Google provider.";
482
+ }
468
483
  } else {
469
484
  googleCard.classList.remove('linked');
470
485
  googleCard.classList.add('unlinked');
471
486
  googleLabel.textContent = "Unlinked";
487
+ googleCard.title = "Click to link Google account.";
472
488
  }
473
489
 
474
490
  // GitHub Card States
@@ -477,15 +493,47 @@ export class KeystoneDetailsComponentInstance
477
493
  if (isGithubLinked) {
478
494
  githubCard.classList.remove('unlinked');
479
495
  githubCard.classList.add('linked');
480
- githubLabel.textContent = "GitHub Linked ✅";
496
+ const isOrigin = originProvider === 'github';
497
+ if (isOrigin && !caps.isSovereign) {
498
+ githubLabel.textContent = "GitHub (Primary Account) 🔒";
499
+ githubCard.title = "Cannot unlink primary account without first adding a Master Password below.";
500
+ } else if (isOrigin) {
501
+ githubLabel.textContent = "GitHub (Primary Account) ✅";
502
+ githubCard.title = "Click to unlink GitHub provider (requires Master Password).";
503
+ } else {
504
+ githubLabel.textContent = "GitHub Linked ✅";
505
+ githubCard.title = "Click to unlink GitHub provider.";
506
+ }
481
507
  } else {
482
508
  githubCard.classList.remove('linked');
483
509
  githubCard.classList.add('unlinked');
484
510
  githubLabel.textContent = "Unlinked";
511
+ githubCard.title = "Click to link GitHub account.";
485
512
  }
486
513
  }
487
514
  }
488
515
 
516
+ private updatePasswordCardView(): void {
517
+ const caps = getKeystoneCapabilities({ keystone: this.ibGib, aggregatedDetails: this.aggregatedDetails });
518
+ const cardTitleEl = this.elements?.changePasswordCardEl?.querySelector('h3');
519
+ const cardDescEl = this.elements?.changePasswordCardEl?.querySelector('.description');
520
+ const currentPasswordGroup = this.elements?.inputCurrentPasswordEl?.closest('.form-group') as HTMLElement | null;
521
+ const btnChangePassword = this.elements?.btnChangePasswordEl;
522
+
523
+ const isCustodialOnly = caps.isCustodial && !caps.isSovereign;
524
+ if (isCustodialOnly) {
525
+ if (cardTitleEl) cardTitleEl.textContent = "Add Master Password";
526
+ if (cardDescEl) cardDescEl.textContent = "Establish sovereign ownership by adding a master password. This enables direct, independent cryptographic management without relying solely on custodian authentication.";
527
+ if (currentPasswordGroup) currentPasswordGroup.style.display = 'none';
528
+ if (btnChangePassword) btnChangePassword.textContent = "Add Master Password";
529
+ } else {
530
+ if (cardTitleEl) cardTitleEl.textContent = "Change Password";
531
+ if (cardDescEl) 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.";
532
+ if (currentPasswordGroup) currentPasswordGroup.style.display = '';
533
+ if (btnChangePassword) btnChangePassword.textContent = "Update Password";
534
+ }
535
+ }
536
+
489
537
  private async updateFrameDetailsView(): Promise<void> {
490
538
  const lc = `${this.lc}[${this.updateFrameDetailsView.name}]`;
491
539
  try {
@@ -549,6 +597,7 @@ export class KeystoneDetailsComponentInstance
549
597
  space
550
598
  });
551
599
 
600
+ this.aggregatedDetails = aggregated;
552
601
  this.elements.identityAggrDetailsEl.textContent = aggregated ? pretty(aggregated) : '{}';
553
602
 
554
603
  // Update Username and Description fields
@@ -556,16 +605,19 @@ export class KeystoneDetailsComponentInstance
556
605
  this.elements.identityDescriptionEl.textContent = aggregated?.description || aggregated?.profile?.description || '-';
557
606
  } catch (err) {
558
607
  console.warn(`${lc} Error aggregating details: ${extractErrorMsg(err)}`);
608
+ this.aggregatedDetails = undefined;
559
609
  this.elements.identityAggrDetailsEl.textContent = '{}';
560
610
  this.elements.identityNameEl.textContent = '-';
561
611
  this.elements.identityDescriptionEl.textContent = '-';
562
612
  }
563
613
  } else {
614
+ this.aggregatedDetails = undefined;
564
615
  this.elements.identityAggrDetailsEl.textContent = '{}';
565
616
  this.elements.identityNameEl.textContent = '-';
566
617
  this.elements.identityDescriptionEl.textContent = '-';
567
618
  }
568
619
  } else {
620
+ this.aggregatedDetails = undefined;
569
621
  this.elements.identityAggrDetailsEl.textContent = '{}';
570
622
  this.elements.identityNameEl.textContent = '-';
571
623
  this.elements.identityDescriptionEl.textContent = '-';
@@ -598,6 +650,7 @@ export class KeystoneDetailsComponentInstance
598
650
  await this.renderDelegates(keystone.data?.delegates);
599
651
  this.elements.ssoLinkCardEl.classList.remove('hidden');
600
652
  this.elements.changePasswordCardEl.classList.remove('hidden');
653
+ this.updatePasswordCardView();
601
654
  } else {
602
655
  this.elements.delegatesCardEl.classList.add('hidden');
603
656
  this.elements.ssoLinkCardEl.classList.add('hidden');
@@ -615,6 +668,7 @@ export class KeystoneDetailsComponentInstance
615
668
 
616
669
  private clearActiveDetails() {
617
670
  if (!this.elements) { return; }
671
+ this.aggregatedDetails = undefined;
618
672
  this.isHistoricalFrame = false;
619
673
  if (this.elements.historicalWarningBannerEl) {
620
674
  this.elements.historicalWarningBannerEl.classList.add('hidden');
@@ -804,43 +858,71 @@ export class KeystoneDetailsComponentInstance
804
858
  }
805
859
 
806
860
  const metaspace = await getGlobalMetaspace_waitIfNeeded();
807
- let localSpaceId: string | undefined;
808
- if (metaspace) {
809
- const space = await metaspace.getLocalUserSpace({ lock: false });
810
- if (space?.ib) {
811
- localSpaceId = parseSpaceIb({ spaceIb: space.ib }).spaceId;
812
- }
861
+ if (!metaspace) {
862
+ throw new Error(`(UNEXPECTED) metaspace falsy? (E: 8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3e)`);
813
863
  }
864
+ const space = await metaspace.getLocalUserSpace({ lock: false });
865
+ if (!space) {
866
+ throw new Error(`(UNEXPECTED) space falsy? (E: 9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4f)`);
867
+ }
868
+ const localSpaceId = space.ib ? parseSpaceIb({ spaceIb: space.ib }).spaceId : undefined;
814
869
 
815
870
  for (const [delegateTjpAddr, info] of Object.entries(delegates)) {
816
871
  const tr = document.createElement('tr');
817
872
 
818
- const deviceName = info.deviceName || info.details?.deviceTag || info.details?.name || 'Device Delegate';
873
+ const deviceName = info.deviceName || (info as any).details?.deviceTag || (info as any).details?.name || 'Device Delegate';
819
874
  const tdName = document.createElement('td');
820
875
 
821
- const delegateSpaceId = info.spaceId || info.details?.spaceId;
822
- let badgeEl: HTMLSpanElement | null = null;
823
-
824
- if (delegateSpaceId) {
825
- 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 (localSpaceId && delegateSpaceId === localSpaceId) {
832
- badgeEl.textContent = 'This Device';
833
- badgeEl.className = 'badge badge-this-device';
834
- badgeEl.style.backgroundColor = '#1b5e20';
835
- badgeEl.style.color = '#a5d6a7';
836
- } else {
837
- badgeEl.textContent = 'Remote Device';
838
- badgeEl.className = 'badge badge-remote-device';
839
- badgeEl.style.backgroundColor = '#0d47a1';
840
- badgeEl.style.color = '#90caf9';
876
+ const delegateSpaceId = info.spaceId || (info as any).details?.spaceId;
877
+
878
+ // Check if delegate is present in local space (via spaceId match or direct presence check)
879
+ let isThisDevice = false;
880
+ if (localSpaceId && delegateSpaceId === localSpaceId) {
881
+ isThisDevice = true;
882
+ } else {
883
+ try {
884
+ const resGet = await metaspace.get({ addrs: [delegateTjpAddr], space });
885
+ isThisDevice = !!(resGet.success && resGet.ibGibs && resGet.ibGibs.length > 0);
886
+ } catch {
887
+ isThisDevice = false;
841
888
  }
842
889
  }
843
890
 
891
+ const badgeEl = document.createElement('span');
892
+ badgeEl.style.marginLeft = '0.5rem';
893
+ badgeEl.style.padding = '0.15rem 0.4rem';
894
+ badgeEl.style.borderRadius = '4px';
895
+ badgeEl.style.fontSize = '0.75rem';
896
+ badgeEl.style.fontWeight = 'bold';
897
+
898
+ if (isThisDevice) {
899
+ badgeEl.textContent = 'This Device';
900
+ badgeEl.className = 'badge badge-this-device';
901
+ badgeEl.style.backgroundColor = '#1b5e20';
902
+ badgeEl.style.color = '#a5d6a7';
903
+ } else {
904
+ badgeEl.textContent = 'Remote Device';
905
+ badgeEl.className = 'badge badge-remote-device';
906
+ badgeEl.style.backgroundColor = '#0d47a1';
907
+ badgeEl.style.color = '#90caf9';
908
+ }
909
+
910
+ // Check expiration
911
+ const isExpired = isDelegateExpired({ delegateInfo: info });
912
+ let expiredBadgeEl: HTMLSpanElement | null = null;
913
+ if (isExpired) {
914
+ expiredBadgeEl = document.createElement('span');
915
+ expiredBadgeEl.textContent = 'Expired';
916
+ expiredBadgeEl.className = 'badge badge-expired';
917
+ expiredBadgeEl.style.marginLeft = '0.3rem';
918
+ expiredBadgeEl.style.padding = '0.15rem 0.4rem';
919
+ expiredBadgeEl.style.borderRadius = '4px';
920
+ expiredBadgeEl.style.fontSize = '0.75rem';
921
+ expiredBadgeEl.style.fontWeight = 'bold';
922
+ expiredBadgeEl.style.backgroundColor = '#b71c1c';
923
+ expiredBadgeEl.style.color = '#ef9a9a';
924
+ }
925
+
844
926
  const applyNameAndBadge = (displayName: string, hoverText?: string) => {
845
927
  tdName.innerHTML = '';
846
928
  const textSpan = document.createElement('span');
@@ -849,6 +931,9 @@ export class KeystoneDetailsComponentInstance
849
931
  if (badgeEl) {
850
932
  tdName.appendChild(badgeEl);
851
933
  }
934
+ if (expiredBadgeEl) {
935
+ tdName.appendChild(expiredBadgeEl);
936
+ }
852
937
  tdName.title = hoverText || displayName;
853
938
  };
854
939
 
@@ -863,7 +948,7 @@ export class KeystoneDetailsComponentInstance
863
948
  tdVerbs.textContent = info.allowedVerbs ? info.allowedVerbs.join(', ') : '-';
864
949
  tdVerbs.title = tdVerbs.textContent;
865
950
 
866
- const isRemote = !!(delegateSpaceId && localSpaceId && delegateSpaceId !== localSpaceId);
951
+ const isRemote = !isThisDevice;
867
952
 
868
953
  const tdActions = document.createElement('td');
869
954
  const btnView = document.createElement('button');
@@ -874,20 +959,20 @@ export class KeystoneDetailsComponentInstance
874
959
  if (isRemote) {
875
960
  this.showRemoteDelegateModal(info);
876
961
  } else {
877
- if (info.delegateAddr) {
962
+ if (info.delegateAddr || delegateTjpAddr) {
878
963
  let addrToLoad = info.delegateAddr;
879
- if (metaspace) {
880
- const space = await metaspace.getLocalUserSpace({ lock: false });
881
- if (space) {
882
- try {
883
- const latest = await metaspace.getLatestAddr({ addr: delegateTjpAddr, space });
884
- if (latest) {
885
- addrToLoad = latest;
886
- }
887
- } catch (err) {
888
- console.warn(`${lc} Error resolving latest delegate address: ${extractErrorMsg(err)}`);
889
- }
964
+ try {
965
+ const keystoneService = new KeystoneService_V1();
966
+ const latestKeystone = await keystoneService.getLatestKeystone({
967
+ addr: delegateTjpAddr,
968
+ metaspace,
969
+ space
970
+ });
971
+ if (latestKeystone) {
972
+ addrToLoad = getIbGibAddr({ ibGib: latestKeystone });
890
973
  }
974
+ } catch (err) {
975
+ console.warn(`${lc} Error resolving latest delegate keystone: ${extractErrorMsg(err)}`);
891
976
  }
892
977
  this.dispatchEvent(new CustomEvent('ibgib-view-keystone-details', {
893
978
  detail: { addr: addrToLoad },
@@ -1187,8 +1272,13 @@ export class KeystoneDetailsComponentInstance
1187
1272
  }
1188
1273
 
1189
1274
  protected async handleSSOClick(providerId: 'google' | 'github') {
1190
- const isLinked = isSsoProviderLinked({ keystone: this.ibGib, providerId });
1275
+ const isLinked = isSsoProviderLinked({ keystone: this.ibGib, aggregatedDetails: this.aggregatedDetails, providerId });
1191
1276
  if (isLinked) {
1277
+ const caps = getKeystoneCapabilities({ keystone: this.ibGib, aggregatedDetails: this.aggregatedDetails });
1278
+ if (!caps.isSovereign) {
1279
+ 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");
1280
+ return;
1281
+ }
1192
1282
  await this.handleSSOUnlink(providerId);
1193
1283
  } else {
1194
1284
  await this.handleSSOLink(providerId);
@@ -1251,7 +1341,7 @@ export class KeystoneDetailsComponentInstance
1251
1341
 
1252
1342
  // 2. Evolve parent keystone to remove the custodian pool
1253
1343
  const targetPoolId = `custodian-manage-${providerId}`;
1254
- const evolvedParent = await keystoneService.removePools({
1344
+ let evolvedParent = await keystoneService.removePools({
1255
1345
  latestKeystone: this.ibGib!,
1256
1346
  masterSecret: masterSecret!,
1257
1347
  poolIds: [targetPoolId],
@@ -1259,6 +1349,22 @@ export class KeystoneDetailsComponentInstance
1259
1349
  space
1260
1350
  });
1261
1351
 
1352
+ // Unregister any corresponding outward delegate for this provider if present
1353
+ const delegatesMap = this.ibGib?.data?.delegates;
1354
+ if (delegatesMap) {
1355
+ for (const [delegateTjpAddr, info] of Object.entries(delegatesMap)) {
1356
+ if (info.deviceName?.includes(providerId)) {
1357
+ evolvedParent = await keystoneService.unregisterDelegate({
1358
+ parentKeystone: evolvedParent,
1359
+ delegateTjpAddr,
1360
+ masterSecret: masterSecret!,
1361
+ metaspace,
1362
+ space,
1363
+ });
1364
+ }
1365
+ }
1366
+ }
1367
+
1262
1368
  // 3. Sync evolved parent keystone back to server
1263
1369
  this.setSSOStatus("Publishing evolved identity keystone to server...", "info");
1264
1370
  const syncRes = await getApiBridge().putEvolveKeystone({
@@ -1299,8 +1405,170 @@ export class KeystoneDetailsComponentInstance
1299
1405
  el.classList.remove('hidden');
1300
1406
  }
1301
1407
 
1408
+ protected async handleAddMasterPassword(): Promise<void> {
1409
+ const lc = `${this.lc}[${this.handleAddMasterPassword.name}]`;
1410
+ this.setChangePasswordStatus("Validating entries...", "info");
1411
+
1412
+ try {
1413
+ const newPassword = this.elements!.inputNewPasswordEl.value;
1414
+ const confirmPassword = this.elements!.inputConfirmPasswordEl.value;
1415
+
1416
+ if (!newPassword) {
1417
+ this.setChangePasswordStatus("Master password is required.", "error");
1418
+ return;
1419
+ }
1420
+ if (newPassword !== confirmPassword) {
1421
+ this.setChangePasswordStatus("Master password and confirmation do not match.", "error");
1422
+ return;
1423
+ }
1424
+
1425
+ const initialManagePool = this.ibGib?.data?.challengePools?.find(p => p.id === POOL_ID_MANAGE);
1426
+ if (!initialManagePool) {
1427
+ this.setChangePasswordStatus("Primary keystone missing active manage challenge pool.", "error");
1428
+ return;
1429
+ }
1430
+
1431
+ this.showBusy({
1432
+ isBusy: true,
1433
+ title: 'Elevating Identity...',
1434
+ msg: 'Generating cryptographic sovereign challenge pool...',
1435
+ animationEmoji: '🔑'
1436
+ });
1437
+ this.setChangePasswordStatus("Generating sovereign challenge pool...", "info");
1438
+ const newChallenges = await generatePoolChallenges({
1439
+ config: initialManagePool.config,
1440
+ masterSecret: newPassword,
1441
+ });
1442
+ const newManagePool = {
1443
+ id: POOL_ID_MANAGE,
1444
+ config: initialManagePool.config,
1445
+ challenges: newChallenges,
1446
+ };
1447
+
1448
+ const primaryTjpAddr = getTjpAddr({ ibGib: this.ibGib! }) || getIbGibAddr({ ibGib: this.ibGib! });
1449
+ const custodianProvider = this.aggregatedDetails?.custodianProvider || 'google';
1450
+
1451
+ let requestBody: any = {
1452
+ primaryTjpAddr,
1453
+ newManagePool,
1454
+ };
1455
+
1456
+ if (custodianProvider === 'google' || custodianProvider === 'github') {
1457
+ this.showBusy({
1458
+ isBusy: true,
1459
+ title: 'Re-authenticating with Provider...',
1460
+ msg: `Please complete authentication in the ${custodianProvider} popup window...`,
1461
+ animationEmoji: '🔒'
1462
+ });
1463
+ this.setChangePasswordStatus(`Please authenticate in the popup with ${custodianProvider} to verify ownership...`, "info");
1464
+ const resPopup = await executeSsoOAuthPopup({
1465
+ providerId: custodianProvider,
1466
+ parentTjpAddr: primaryTjpAddr,
1467
+ });
1468
+
1469
+ if (!resPopup.success || !resPopup.code) {
1470
+ this.showBusy({ isBusy: false });
1471
+ this.setChangePasswordStatus(resPopup.message || "Re-authentication cancelled.", "error");
1472
+ return;
1473
+ }
1474
+
1475
+ requestBody.providerId = custodianProvider;
1476
+ requestBody.code = resPopup.code;
1477
+ requestBody.redirectUri = resPopup.redirectUri;
1478
+ } else {
1479
+ this.showBusy({ isBusy: false });
1480
+ // Email OTP fallback
1481
+ const email = this.aggregatedDetails?.emails?.[0] || this.ibGib?.data?.emails?.[0];
1482
+ const otpCode = await promptForSecret({
1483
+ msg: `Enter 6-digit verification code sent to ${email}:`,
1484
+ confirm: false,
1485
+ });
1486
+ if (!otpCode) {
1487
+ this.setChangePasswordStatus("Verification code required.", "error");
1488
+ return;
1489
+ }
1490
+ requestBody.email = email;
1491
+ requestBody.code = otpCode;
1492
+ }
1493
+
1494
+ this.showBusy({
1495
+ isBusy: true,
1496
+ title: 'Updating Keystone Timeline...',
1497
+ msg: 'Publishing sovereign master password and evolving identity DAG...',
1498
+ animationEmoji: '⏳'
1499
+ });
1500
+ this.setChangePasswordStatus("Elevating identity to sovereign master password on server...", "info");
1501
+ const response = await fetch('/api/identity/custodian/add-master-password', {
1502
+ method: 'POST',
1503
+ headers: { 'Content-Type': 'application/json' },
1504
+ body: JSON.stringify(requestBody),
1505
+ });
1506
+
1507
+ const resJson = await response.json();
1508
+ if (!response.ok || !resJson.success) {
1509
+ throw new Error(resJson.error || resJson.message || `Server error (${response.status})`);
1510
+ }
1511
+
1512
+ const evolvedParent = resJson.primaryKeystone;
1513
+
1514
+ // Save to local user space & metaspace
1515
+ const metaspace = await getGlobalMetaspace_waitIfNeeded();
1516
+ if (!metaspace) { throw new Error("Global metaspace not available"); }
1517
+ const space = await metaspace.getLocalUserSpace({ lock: false });
1518
+ if (!space) { throw new Error("Local user space not available"); }
1519
+
1520
+ await metaspace.put({ ibGibs: [evolvedParent], space });
1521
+ await metaspace.registerNewIbGib({ ibGib: evolvedParent, space });
1522
+ await updateSpecialIndex({
1523
+ type: "keystones",
1524
+ rel8nInfos: [
1525
+ {
1526
+ rel8nName: "keystone",
1527
+ ibGibs: [toDto({ ibGib: evolvedParent })]
1528
+ }
1529
+ ],
1530
+ metaspace,
1531
+ space
1532
+ });
1533
+
1534
+ const evolvedAddr = getIbGibAddr({ ibGib: evolvedParent });
1535
+
1536
+ // Dispatch EVENT_IBGIB_IDENTITY_CHANGED
1537
+ window.dispatchEvent(new CustomEvent(EVENT_IBGIB_IDENTITY_CHANGED, {
1538
+ detail: { activeIdentityAddr: evolvedAddr },
1539
+ bubbles: true,
1540
+ composed: true
1541
+ }));
1542
+
1543
+ this.setChangePasswordStatus("Successfully added Master Password! Identity is now sovereign.", "success");
1544
+
1545
+ // Refresh the view
1546
+ this.ibGibAddr = evolvedAddr;
1547
+ this.staticIbGib = evolvedParent;
1548
+ await this.loadIbGib({ getLatest: true });
1549
+
1550
+ // Clear inputs
1551
+ this.elements!.inputNewPasswordEl.value = '';
1552
+ this.elements!.inputConfirmPasswordEl.value = '';
1553
+
1554
+ } catch (error) {
1555
+ console.error(`${lc} Elevation failed: ${extractErrorMsg(error)}`);
1556
+ this.setChangePasswordStatus(`Failed to add master password: ${extractErrorMsg(error)}`, "error");
1557
+ } finally {
1558
+ this.showBusy({ isBusy: false });
1559
+ }
1560
+ }
1561
+
1302
1562
  protected async handleChangePassword(): Promise<void> {
1303
1563
  const lc = `${this.lc}[${this.handleChangePassword.name}]`;
1564
+
1565
+ const caps = getKeystoneCapabilities({ keystone: this.ibGib, aggregatedDetails: this.aggregatedDetails });
1566
+ const isCustodialOnly = caps.isCustodial && !caps.isSovereign;
1567
+ if (isCustodialOnly) {
1568
+ await this.handleAddMasterPassword();
1569
+ return;
1570
+ }
1571
+
1304
1572
  this.setChangePasswordStatus("Validating entries...", "info");
1305
1573
 
1306
1574
  try {
@@ -107,6 +107,7 @@ export const EVENT_IBGIB_UI_MESSAGE = 'ibgib-ui-message';
107
107
  export const EVENT_IBGIB_IDENTITY_REQUEST_CHANGE = 'ibgib-identity-request-change';
108
108
  export const EVENT_IBGIB_IDENTITY_CHANGED = 'ibgib-identity-changed';
109
109
  export const EVENT_IBGIB_IDENTITY_UPDATE_AVAILABLE = 'ibgib-identity-update-available';
110
+ export const EVENT_IBGIB_IDENTITY_RENEWAL_REQUIRED = 'ibgib-identity-renewal-required';
110
111
  // #endregion Custom Events
111
112
 
112
113
 
@@ -17,3 +17,9 @@ export interface EventIbGibIdentityUpdateAvailableDetail {
17
17
  localN: number;
18
18
  serverLatestAddr: string;
19
19
  }
20
+
21
+ export interface EventIbGibIdentityRenewalRequiredDetail {
22
+ activePrimaryAddr: string;
23
+ primaryTjpAddr: string;
24
+ reason: 'expired' | 'missing_secret';
25
+ }
@@ -1,22 +0,0 @@
1
- /**
2
- * @module identity/custodian-delegate-helper
3
- *
4
- * Client-side helper functions for deriving and storing local cryptographic delegate secrets
5
- * for full-custodian / passwordless identities.
6
- */
7
- /**
8
- * Retrieves or generates a high-entropy local delegate secret for a passwordless custodian identity.
9
- * Stored locally in `localStorage` keyed by `primaryTjpAddr`.
10
- *
11
- * @param primaryTjpAddr The primary identity keystone TJP address (`ib^gib`).
12
- */
13
- export declare function deriveLocalCustodianDelegateSecret({ primaryTjpAddr, }: {
14
- primaryTjpAddr: string;
15
- }): Promise<string>;
16
- /**
17
- * Removes the stored local delegate secret for a custodian identity.
18
- */
19
- export declare function clearLocalCustodianDelegateSecret({ primaryTjpAddr, }: {
20
- primaryTjpAddr: string;
21
- }): void;
22
- //# sourceMappingURL=custodian-delegate-helper.d.mts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"custodian-delegate-helper.d.mts","sourceRoot":"","sources":["../../src/identity/custodian-delegate-helper.mts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAQH;;;;;GAKG;AACH,wBAAsB,kCAAkC,CAAC,EACrD,cAAc,GACjB,EAAE;IACC,cAAc,EAAE,MAAM,CAAC;CAC1B,GAAG,OAAO,CAAC,MAAM,CAAC,CAiDlB;AAED;;GAEG;AACH,wBAAgB,iCAAiC,CAAC,EAC9C,cAAc,GACjB,EAAE;IACC,cAAc,EAAE,MAAM,CAAC;CAC1B,GAAG,IAAI,CAIP"}
@@ -1,67 +0,0 @@
1
- /**
2
- * @module identity/custodian-delegate-helper
3
- *
4
- * Client-side helper functions for deriving and storing local cryptographic delegate secrets
5
- * for full-custodian / passwordless identities.
6
- */
7
- import { getUUID, hash, HashAlgorithm, extractErrorMsg } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
8
- import { storageGet, storagePut } from "../storage/storage-helpers.web.mjs";
9
- import { getGlobalDbName, getGlobalStoreName } from "../helpers.web.mjs";
10
- const LOCAL_STORAGE_PREFIX = 'ibgib_custodian_delegate_secret_';
11
- /**
12
- * Retrieves or generates a high-entropy local delegate secret for a passwordless custodian identity.
13
- * Stored locally in `localStorage` keyed by `primaryTjpAddr`.
14
- *
15
- * @param primaryTjpAddr The primary identity keystone TJP address (`ib^gib`).
16
- */
17
- export async function deriveLocalCustodianDelegateSecret({ primaryTjpAddr, }) {
18
- const lc = `[${deriveLocalCustodianDelegateSecret.name}]`;
19
- try {
20
- if (!primaryTjpAddr) {
21
- throw new Error('primaryTjpAddr is required to derive local custodian delegate secret (E: a1b2c3d4e5f67890123456789a000101)');
22
- }
23
- const key = `${LOCAL_STORAGE_PREFIX}${primaryTjpAddr}`;
24
- const dbName = getGlobalDbName();
25
- const storeName = getGlobalStoreName();
26
- // debugger; // what are the dbName and storeName?
27
- const existingSecret = await storageGet({ dbName, storeName, key });
28
- if (existingSecret) {
29
- return existingSecret;
30
- }
31
- // Generate high-entropy 512-bit SHA hash secret from fresh UUIDs
32
- const rawEntropy = await getUUID(32);
33
- const newSecret = await hash({
34
- s: rawEntropy,
35
- algorithm: HashAlgorithm.sha_512
36
- });
37
- await storagePut({ dbName, storeName, key, value: newSecret });
38
- return newSecret;
39
- // const existingSecret = localStorage.getItem(key);
40
- // if (existingSecret) {
41
- // return existingSecret;
42
- // }
43
- // // Generate high-entropy 512-bit SHA hash secret from fresh UUIDs
44
- // const rawEntropy = `${await getUUID()}-${await getUUID()}-${await getUUID()}-${Date.now()}`;
45
- // const newSecret = await hash({
46
- // s: rawEntropy,
47
- // algorithm: HashAlgorithm.sha_512
48
- // });
49
- // localStorage.setItem(key, newSecret);
50
- // return newSecret;
51
- }
52
- catch (error) {
53
- console.error(`${lc} ${extractErrorMsg(error)}`);
54
- throw error;
55
- }
56
- }
57
- /**
58
- * Removes the stored local delegate secret for a custodian identity.
59
- */
60
- export function clearLocalCustodianDelegateSecret({ primaryTjpAddr, }) {
61
- if (!primaryTjpAddr) {
62
- return;
63
- }
64
- const key = `${LOCAL_STORAGE_PREFIX}${primaryTjpAddr}`;
65
- localStorage.removeItem(key);
66
- }
67
- //# sourceMappingURL=custodian-delegate-helper.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"custodian-delegate-helper.mjs","sourceRoot":"","sources":["../../src/identity/custodian-delegate-helper.mts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,iDAAiD,CAAC;AAChH,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,oCAAoC,CAAC;AAC5E,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAEzE,MAAM,oBAAoB,GAAG,kCAAkC,CAAC;AAEhE;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,kCAAkC,CAAC,EACrD,cAAc,GAGjB;IACG,MAAM,EAAE,GAAG,IAAI,kCAAkC,CAAC,IAAI,GAAG,CAAC;IAC1D,IAAI,CAAC;QACD,IAAI,CAAC,cAAc,EAAE,CAAC;YAClB,MAAM,IAAI,KAAK,CAAC,4GAA4G,CAAC,CAAC;QAClI,CAAC;QAED,MAAM,GAAG,GAAG,GAAG,oBAAoB,GAAG,cAAc,EAAE,CAAC;QAEvD,MAAM,MAAM,GAAG,eAAe,EAAE,CAAC;QACjC,MAAM,SAAS,GAAG,kBAAkB,EAAE,CAAC;QAEvC,kDAAkD;QAGlD,MAAM,cAAc,GAAG,MAAM,UAAU,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC;QACpE,IAAI,cAAc,EAAE,CAAC;YACjB,OAAO,cAAc,CAAC;QAC1B,CAAC;QAED,iEAAiE;QACjE,MAAM,UAAU,GAAG,MAAM,OAAO,CAAC,EAAE,CAAC,CAAC;QACrC,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC;YACzB,CAAC,EAAE,UAAU;YACb,SAAS,EAAE,aAAa,CAAC,OAAO;SACnC,CAAC,CAAC;QACH,MAAM,UAAU,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;QAE/D,OAAO,SAAS,CAAC;QAGjB,oDAAoD;QACpD,wBAAwB;QACxB,6BAA6B;QAC7B,IAAI;QAEJ,oEAAoE;QACpE,+FAA+F;QAC/F,iCAAiC;QACjC,qBAAqB;QACrB,uCAAuC;QACvC,MAAM;QAEN,wCAAwC;QACxC,oBAAoB;IACxB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE,IAAI,eAAe,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACjD,MAAM,KAAK,CAAC;IAChB,CAAC;AACL,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iCAAiC,CAAC,EAC9C,cAAc,GAGjB;IACG,IAAI,CAAC,cAAc,EAAE,CAAC;QAAC,OAAO;IAAC,CAAC;IAChC,MAAM,GAAG,GAAG,GAAG,oBAAoB,GAAG,cAAc,EAAE,CAAC;IACvD,YAAY,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;AACjC,CAAC"}