@ibgib/space-gib 0.0.8 → 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ibgib/space-gib",
3
- "version": "0.0.8",
3
+ "version": "0.0.9",
4
4
  "type": "module",
5
5
  "description": "ibgib storage and provider service — SaaS at ibgib.space",
6
6
  "scripts": {
@@ -12,11 +12,11 @@
12
12
  "build:test": "tsc -p tsconfig.test.json"
13
13
  },
14
14
  "dependencies": {
15
- "@ibgib/core-gib": "^0.1.65",
15
+ "@ibgib/core-gib": "^0.1.66",
16
16
  "@ibgib/helper-gib": "^0.0.36",
17
17
  "@ibgib/ts-gib": "^0.5.33",
18
- "@ibgib/web-gib": "^0.0.51",
19
- "@ibgib/node-gib": "^0.0.9"
18
+ "@ibgib/web-gib": "^0.0.52",
19
+ "@ibgib/node-gib": "^0.0.10"
20
20
  },
21
21
  "engines": {
22
22
  "node": ">=20.0.0"
@@ -8,4 +8,4 @@
8
8
  /**
9
9
  * Version of space-gib, auto-updated by the build process.
10
10
  */
11
- export const AUTO_GENERATED_VERSION = '0.0.8';
11
+ export const AUTO_GENERATED_VERSION = '0.0.9';
@@ -51,14 +51,39 @@ export class SpaceGibApiBridge {
51
51
  }
52
52
 
53
53
  /**
54
- * PUTs an evolved domain keystone and any related ibgibs (e.g. S^Stjp) to the server.
54
+ * PUTs an evolved domain keystone and any related keystones (e.g., session
55
+ * keystones or delegate keystones) to the server.
55
56
  */
56
- public async putEvolveKeystone(
57
- domainAddr: string,
58
- keystoneIbGib: IbGib_V1,
59
- relatedIbGibs: IbGib_V1[] = [],
60
- revokeTokens?: Record<string, string>
61
- ): Promise<{ success: boolean; message?: string }> {
57
+ public async putEvolveKeystone({
58
+ domainAddr,
59
+ keystoneIbGib,
60
+ relatedKeystoneIbGibs = [],
61
+ revokeTokens
62
+ }: {
63
+ /**
64
+ * Address of the root parent domain identity keystone.
65
+ */
66
+ domainAddr: string;
67
+ /**
68
+ * The main evolved keystone frame being synchronized.
69
+ */
70
+ keystoneIbGib: IbGib_V1;
71
+ /**
72
+ * Array of other keystone frames related to this evolution.
73
+ * Distinction in intent:
74
+ * 1. Ephemeral Session Keystones: Temporary keystones generated to
75
+ * authorize a single sync session (under 'sync' claims, targeted via
76
+ * claim.target = sAddr).
77
+ * 2. Persistent Delegate Keystones: Long-lived sub-keystones registered
78
+ * in the parent's `delegates` dictionary that are evolved
79
+ * concurrently (e.g., during 'manage' password changes).
80
+ */
81
+ relatedKeystoneIbGibs?: IbGib_V1[];
82
+ /**
83
+ * Optional map of providerId to token to revoke during unlinking.
84
+ */
85
+ revokeTokens?: Record<string, string>;
86
+ }): Promise<{ success: boolean; message?: string }> {
62
87
  const lc = `${this.lc}[${this.putEvolveKeystone.name}]`;
63
88
  try {
64
89
  console.log(`${lc} posting evolved keystone...`);
@@ -70,7 +95,11 @@ export class SpaceGibApiBridge {
70
95
  headers: {
71
96
  'Content-Type': 'application/json',
72
97
  },
73
- body: JSON.stringify({ keystoneIbGib, relatedIbGibs, revokeTokens }),
98
+ body: JSON.stringify({
99
+ keystoneIbGib,
100
+ relatedKeystoneIbGibs,
101
+ revokeTokens
102
+ }),
74
103
  });
75
104
 
76
105
  const data = await response.json();
@@ -363,11 +363,11 @@ export class KeystoneCreatorComponentInstance
363
363
  msg: 'Posting evolved domain identity and sync delegate to server...',
364
364
  animationEmoji: '🔄'
365
365
  });
366
- const resEvolve = await spaceGibApiBridge.putEvolveKeystone(
367
- addr,
368
- evolvedParentKeystoneIbGib,
369
- [delegateKeystoneIbGib]
370
- );
366
+ const resEvolve = await spaceGibApiBridge.putEvolveKeystone({
367
+ domainAddr: addr,
368
+ keystoneIbGib: evolvedParentKeystoneIbGib,
369
+ relatedKeystoneIbGibs: [delegateKeystoneIbGib]
370
+ });
371
371
  if (!resEvolve.success) {
372
372
  this.setStatus(`Delegate registration sync failed: ${resEvolve.message}`, "error");
373
373
  devLog(`✗ Server rejected delegate registration: ${resEvolve.message}`);
@@ -567,3 +567,45 @@
567
567
  pointer-events: none;
568
568
  opacity: 0.4;
569
569
  }
570
+
571
+ /* Form Layout for Passphrase Rotation */
572
+ .form-row {
573
+ display: flex;
574
+ gap: 1rem;
575
+ flex-wrap: wrap;
576
+ margin-top: 0.5rem;
577
+ }
578
+
579
+ .form-group {
580
+ flex: 1;
581
+ min-width: 200px;
582
+ display: flex;
583
+ flex-direction: column;
584
+ gap: 0.4rem;
585
+ }
586
+
587
+ .form-group label {
588
+ font-size: 0.75rem;
589
+ font-weight: 600;
590
+ text-transform: uppercase;
591
+ letter-spacing: 0.03em;
592
+ color: var(--clr-text-secondary, #8fa08f);
593
+ }
594
+
595
+ .form-input {
596
+ padding: 0.65rem 0.8rem;
597
+ background: rgba(0, 0, 0, 0.35);
598
+ border: 1px solid var(--border-color-base, rgba(255, 255, 255, 0.08));
599
+ border-radius: var(--radius-sm, 6px);
600
+ color: #ffffff;
601
+ font-family: var(--font-family-base, inherit);
602
+ font-size: 0.9rem;
603
+ transition: all 0.2s ease-in-out;
604
+ }
605
+
606
+ .form-input:focus {
607
+ outline: none;
608
+ border-color: var(--clr-accent, #50fa7b);
609
+ background: rgba(0, 0, 0, 0.5);
610
+ box-shadow: 0 0 8px rgba(80, 250, 123, 0.15);
611
+ }
@@ -113,6 +113,30 @@
113
113
  <div id="sso-status-msg" class="status-msg hidden"></div>
114
114
  </div>
115
115
 
116
+ <!-- Change Passphrase Card -->
117
+ <div id="change-passphrase-card" class="card">
118
+ <h3>Change Master Passphrase</h3>
119
+ <p class="description">Rotate your local master passphrase. This will cryptographically evolve your primary identity and all registered user-owned delegate keystones to the new passphrase.</p>
120
+ <div class="form-row">
121
+ <div class="form-group">
122
+ <label for="input-current-passphrase">Current Passphrase</label>
123
+ <input type="password" id="input-current-passphrase" placeholder="••••••••" class="form-input">
124
+ </div>
125
+ <div class="form-group">
126
+ <label for="input-new-passphrase">New Passphrase</label>
127
+ <input type="password" id="input-new-passphrase" placeholder="••••••••" class="form-input">
128
+ </div>
129
+ <div class="form-group">
130
+ <label for="input-confirm-passphrase">Confirm New Passphrase</label>
131
+ <input type="password" id="input-confirm-passphrase" placeholder="••••••••" class="form-input">
132
+ </div>
133
+ </div>
134
+ <div class="action-row">
135
+ <button id="btn-change-passphrase" class="action-btn">Update Passphrase</button>
136
+ </div>
137
+ <div id="change-passphrase-status" class="status-msg hidden"></div>
138
+ </div>
139
+
116
140
  <!-- Raw Keystone Timeline Explorer -->
117
141
  <div class="card accordion">
118
142
  <div id="raw-accordion-header" class="accordion-header">
@@ -19,7 +19,7 @@ import {
19
19
  import { EVENT_IBGIB_IDENTITY_REQUEST_CHANGE, EVENT_IBGIB_IDENTITY_CHANGED } from "@ibgib/web-gib/dist/ui/ui-constants.mjs";
20
20
  import { spaceGibApiBridge } from "../../api/space-gib-api-bridge.mjs";
21
21
  import { updateSpecialIndex } from "@ibgib/core-gib/dist/timeline/timeline-api.mjs";
22
- import { toDto } from "@ibgib/core-gib/dist/common/other/ibgib-helper.mjs";
22
+ import { getTjpAddr, toDto } from "@ibgib/core-gib/dist/common/other/ibgib-helper.mjs";
23
23
 
24
24
  import { GLOBAL_LOG_A_LOT, APP_CONFIG, } from "../../constants.mjs";
25
25
  import { getComponentCtorArg, getIbGibGlobalThis_SpaceGib } from "../../helpers.web.mjs";
@@ -89,6 +89,12 @@ interface KeystoneDetailsElements extends ElementsBase {
89
89
  btnLinkGoogleEl: HTMLDivElement;
90
90
  btnLinkGithubEl: HTMLDivElement;
91
91
  ssoStatusMsgEl: HTMLDivElement;
92
+ inputCurrentPassphraseEl: HTMLInputElement;
93
+ inputNewPassphraseEl: HTMLInputElement;
94
+ inputConfirmPassphraseEl: HTMLInputElement;
95
+ btnChangePassphraseEl: HTMLButtonElement;
96
+ changePassphraseStatusEl: HTMLDivElement;
97
+ changePassphraseCardEl: HTMLDivElement;
92
98
  }
93
99
 
94
100
  export class KeystoneDetailsComponentInstance
@@ -195,6 +201,12 @@ export class KeystoneDetailsComponentInstance
195
201
  this.elements.btnLinkGoogleEl = shadow.getElementById('btn-link-google') as HTMLDivElement;
196
202
  this.elements.btnLinkGithubEl = shadow.getElementById('btn-link-github') as HTMLDivElement;
197
203
  this.elements.ssoStatusMsgEl = shadow.getElementById('sso-status-msg') as HTMLDivElement;
204
+ this.elements.inputCurrentPassphraseEl = shadow.getElementById('input-current-passphrase') as HTMLInputElement;
205
+ this.elements.inputNewPassphraseEl = shadow.getElementById('input-new-passphrase') as HTMLInputElement;
206
+ this.elements.inputConfirmPassphraseEl = shadow.getElementById('input-confirm-passphrase') as HTMLInputElement;
207
+ this.elements.btnChangePassphraseEl = shadow.getElementById('btn-change-passphrase') as HTMLButtonElement;
208
+ this.elements.changePassphraseStatusEl = shadow.getElementById('change-passphrase-status') as HTMLDivElement;
209
+ this.elements.changePassphraseCardEl = shadow.getElementById('change-passphrase-card') as HTMLDivElement;
198
210
 
199
211
  // ElementBase requires contentEl
200
212
  this.elements.contentEl = shadow.getElementById('identity-details-view') as HTMLDivElement;
@@ -233,6 +245,7 @@ export class KeystoneDetailsComponentInstance
233
245
  this.elements!.btnSetActiveEl.addEventListener('click', () => this.handleSetActiveIdentity());
234
246
  this.elements!.btnLinkGoogleEl.addEventListener('click', () => this.handleSSOClick('google'));
235
247
  this.elements!.btnLinkGithubEl.addEventListener('click', () => this.handleSSOClick('github'));
248
+ this.elements!.btnChangePassphraseEl.addEventListener('click', () => this.handleChangePassphrase());
236
249
  }
237
250
 
238
251
  private toggleAccordion() {
@@ -296,14 +309,23 @@ export class KeystoneDetailsComponentInstance
296
309
  throw new Error(`(UNEXPECTED) space falsy and we couldn't get default local user space from metaspace? (E: fa298edcecbda889cfd7b228fbd57c26)`);
297
310
  }
298
311
 
299
- const resGet = await metaspace.get({ addrs: [this.ibGibAddr], space });
312
+ let loadAddr = this.ibGibAddr;
313
+ if (opts?.getLatest) {
314
+ const latest = await metaspace.getLatestAddr({ addr: this.ibGibAddr, space });
315
+ if (latest) {
316
+ loadAddr = latest;
317
+ this.ibGibAddr = latest;
318
+ }
319
+ }
320
+
321
+ const resGet = await metaspace.get({ addrs: [loadAddr], space });
300
322
 
301
323
  if (resGet.success && resGet.ibGibs && resGet.ibGibs.length === 1) {
302
324
  this.staticIbGib = resGet.ibGibs[0] as KeystoneIbGib_V1;
303
325
  } else {
304
326
  const resIbGib = resGet.rawResultIbGib as any;
305
327
  const addrsNotFound = resIbGib?.data?.addrsNotFound ?? 'unknown';
306
- throw new Error(`couldn't find address ${this.ibGibAddr}. addrsNotFound: ${addrsNotFound}? resGet.errorMsg: ${resGet.errorMsg} (E: e0e5c9b7de2837bc902fa88390cd9f26)`);
328
+ throw new Error(`couldn't find address ${loadAddr}. addrsNotFound: ${addrsNotFound}? resGet.errorMsg: ${resGet.errorMsg} (E: e0e5c9b7de2837bc902fa88390cd9f26)`);
307
329
  }
308
330
 
309
331
  await this.updateFrameDetailsView();
@@ -495,15 +517,17 @@ export class KeystoneDetailsComponentInstance
495
517
  // Render Challenge Pools
496
518
  this.renderChallengePools(keystone.data?.challengePools || []);
497
519
 
498
- // Render Delegates & SSO link card (only if this is a primary keystone, hide delegates table & SSO card for delegate/session keystones)
520
+ // Render Delegates, SSO link, and Passphrase Change cards (only if this is a primary keystone, hide them for delegate/session keystones)
499
521
  const details = await this.getKeystoneDetails(this.ibGibAddr);
500
522
  if (details.isPrimary) {
501
523
  this.elements.delegatesCardEl.classList.remove('hidden');
502
524
  await this.renderDelegates(keystone.data?.delegates);
503
525
  this.elements.ssoLinkCardEl.classList.remove('hidden');
526
+ this.elements.changePassphraseCardEl.classList.remove('hidden');
504
527
  } else {
505
528
  this.elements.delegatesCardEl.classList.add('hidden');
506
529
  this.elements.ssoLinkCardEl.classList.add('hidden');
530
+ this.elements.changePassphraseCardEl.classList.add('hidden');
507
531
  }
508
532
 
509
533
  this.updateActiveButtonState();
@@ -633,10 +657,26 @@ export class KeystoneDetailsComponentInstance
633
657
  const btnView = document.createElement('button');
634
658
  btnView.className = 'action-btn secondary small';
635
659
  btnView.textContent = 'View Details';
636
- btnView.addEventListener('click', () => {
660
+ btnView.addEventListener('click', async () => {
661
+ const lc = `${this.lc}[renderDelegates.click]`;
637
662
  if (info.delegateAddr) {
663
+ let addrToLoad = info.delegateAddr;
664
+ const metaspace = getIbGibGlobalThis_SpaceGib(APP_CONFIG).metaspace;
665
+ if (metaspace) {
666
+ const space = await metaspace.getLocalUserSpace({ lock: false });
667
+ if (space) {
668
+ try {
669
+ const latest = await metaspace.getLatestAddr({ addr: delegateTjpAddr, space });
670
+ if (latest) {
671
+ addrToLoad = latest;
672
+ }
673
+ } catch (err) {
674
+ console.warn(`${lc} Error resolving latest delegate address: ${extractErrorMsg(err)}`);
675
+ }
676
+ }
677
+ }
638
678
  this.dispatchEvent(new CustomEvent('ibgib-view-keystone-details', {
639
- detail: { addr: info.delegateAddr },
679
+ detail: { addr: addrToLoad },
640
680
  bubbles: true,
641
681
  composed: true
642
682
  }));
@@ -874,11 +914,11 @@ export class KeystoneDetailsComponentInstance
874
914
 
875
915
  // 4. Sync evolved parent keystone back to server
876
916
  this.setSSOStatus("Publishing evolved identity keystone to server...", "info");
877
- const syncRes = await spaceGibApiBridge.putEvolveKeystone(
878
- stateObj.parentTjpAddr,
879
- evolvedParent,
880
- [] // No outward delegate timelines
881
- );
917
+ const syncRes = await spaceGibApiBridge.putEvolveKeystone({
918
+ domainAddr: stateObj.parentTjpAddr,
919
+ keystoneIbGib: evolvedParent,
920
+ relatedKeystoneIbGibs: [] // No outward delegate timelines
921
+ });
882
922
  if (!syncRes.success) {
883
923
  throw new Error(`Server synchronization rejected: ${syncRes.message}`);
884
924
  }
@@ -980,11 +1020,11 @@ export class KeystoneDetailsComponentInstance
980
1020
 
981
1021
  // 3. Sync evolved parent keystone back to server
982
1022
  this.setSSOStatus("Publishing evolved identity keystone to server...", "info");
983
- const syncRes = await spaceGibApiBridge.putEvolveKeystone(
984
- getIbGibAddr({ ibGib: this.ibGib! }),
985
- evolvedParent,
986
- [] // No outward delegate timelines
987
- );
1023
+ const syncRes = await spaceGibApiBridge.putEvolveKeystone({
1024
+ domainAddr: getIbGibAddr({ ibGib: this.ibGib! }),
1025
+ keystoneIbGib: evolvedParent,
1026
+ relatedKeystoneIbGibs: [] // No outward delegate timelines
1027
+ });
988
1028
  if (!syncRes.success) {
989
1029
  throw new Error(`Server synchronization rejected: ${syncRes.message}`);
990
1030
  }
@@ -1003,11 +1043,171 @@ export class KeystoneDetailsComponentInstance
1003
1043
  });
1004
1044
 
1005
1045
  this.setSSOStatus(`Successfully unlinked ${providerId}!`, "success");
1006
- await this.loadIbGib({ getLatest: false });
1046
+ await this.loadIbGib({ getLatest: true });
1007
1047
 
1008
1048
  } catch (error) {
1009
1049
  console.error(`${lc} SSO unlink failed: ${extractErrorMsg(error)}`);
1010
1050
  this.setSSOStatus(`Unlink failed: ${extractErrorMsg(error)}`, "error");
1011
1051
  }
1012
1052
  }
1053
+
1054
+ private setChangePassphraseStatus(msg: string, type: 'info' | 'success' | 'error') {
1055
+ const el = this.elements!.changePassphraseStatusEl;
1056
+ el.textContent = msg;
1057
+ el.className = `status-msg ${type}`;
1058
+ el.classList.remove('hidden');
1059
+ }
1060
+
1061
+ protected async handleChangePassphrase(): Promise<void> {
1062
+ const lc = `${this.lc}[${this.handleChangePassphrase.name}]`;
1063
+ this.setChangePassphraseStatus("Validating entries...", "info");
1064
+
1065
+ try {
1066
+ const currentPassphrase = this.elements!.inputCurrentPassphraseEl.value;
1067
+ const newPassphrase = this.elements!.inputNewPassphraseEl.value;
1068
+ const confirmPassphrase = this.elements!.inputConfirmPassphraseEl.value;
1069
+
1070
+ if (!currentPassphrase) {
1071
+ this.setChangePassphraseStatus("Current passphrase is required.", "error");
1072
+ return;
1073
+ }
1074
+ if (!newPassphrase) {
1075
+ this.setChangePassphraseStatus("New passphrase is required.", "error");
1076
+ return;
1077
+ }
1078
+ if (newPassphrase !== confirmPassphrase) {
1079
+ this.setChangePassphraseStatus("New passphrase and confirmation do not match.", "error");
1080
+ return;
1081
+ }
1082
+
1083
+ const keystoneService = new KeystoneService_V1();
1084
+
1085
+ // 1. Verify current passphrase against parent manage pool
1086
+ const isMatch = await keystoneService.verifySigningSecret({
1087
+ keystoneIbGib: this.ibGib!,
1088
+ signingSecret: currentPassphrase,
1089
+ poolId: 'manage'
1090
+ });
1091
+ if (!isMatch) {
1092
+ this.setChangePassphraseStatus("Incorrect current passphrase.", "error");
1093
+ return;
1094
+ }
1095
+
1096
+ this.setChangePassphraseStatus("Evolving primary identity keystone...", "info");
1097
+
1098
+ const metaspace = getIbGibGlobalThis_SpaceGib(APP_CONFIG).metaspace;
1099
+ if (!metaspace) throw new Error("Metaspace not found");
1100
+ const space = await metaspace.getLocalUserSpace({ lock: false });
1101
+ if (!space) { throw new Error("Local space not found"); }
1102
+
1103
+ // 2. Rotate parent identity
1104
+ debugger; // walk through this...looking for delegate failure
1105
+ const { newKeystone: evolvedParent, newDelegates: evolvedDelegates } = await keystoneService.changePassword({
1106
+ keystoneIbGib: this.ibGib!,
1107
+ newMasterSecret: newPassphrase,
1108
+ signingSecret: currentPassphrase,
1109
+ signingPoolId: 'manage',
1110
+ metaspace,
1111
+ space
1112
+ });
1113
+ debugger; // look at evolvedDelegates
1114
+
1115
+ this.setChangePassphraseStatus("Resolving and rotating delegate keystones...", "info");
1116
+
1117
+ // // 3. Evolve registered delegate keystones (preserving custodian/foreign delegate secrets)
1118
+ // const delegates = this.ibGib!.data?.delegates || {};
1119
+ // const evolvedDelegates: KeystoneIbGib_V1[] = [];
1120
+
1121
+ // for (const delegateInfo of Object.values(delegates)) {
1122
+ // const delegateAddr = delegateInfo.delegateAddr;
1123
+ // const delegateKeystone = await keystoneService.getLatestKeystone({
1124
+ // addr: delegateAddr,
1125
+ // metaspace,
1126
+ // space,
1127
+ // });
1128
+
1129
+ // // Identify user-owned challenge pools in the delegate that match the old passphrase
1130
+ // const poolsToRotate: string[] = [];
1131
+ // for (const pool of delegateKeystone.data?.challengePools ?? []) {
1132
+ // if (!pool.isForeign) {
1133
+ // /**
1134
+ // * WARNING: There is an extreme edge case that if two pools
1135
+ // * happen to be owned by different parties but share the
1136
+ // * same secret, this will change the wrong pool's secret
1137
+ // * also. For V1, this shouldn't matter. But we need a
1138
+ // * better way of tracking pool ownership for V2 (or even
1139
+ // * V1 if we have multi-party support). (DO NOT REMOVE
1140
+ // * THIS COMMENT UNTIL THIS IS ADDRESSED)
1141
+ // */
1142
+ // const isPoolMatch = await keystoneService.verifySigningSecret({
1143
+ // keystoneIbGib: delegateKeystone,
1144
+ // signingSecret: currentPassphrase,
1145
+ // poolId: pool.id
1146
+ // });
1147
+ // if (isPoolMatch) { poolsToRotate.push(pool.id); }
1148
+ // }
1149
+ // }
1150
+
1151
+ // if (poolsToRotate.length > 0) {
1152
+ // console.log(`${lc} Rotating pools in delegate ${delegateInfo.delegateTjpAddr}: ${poolsToRotate.join(', ')}`);
1153
+ // const {newKeystone: evolvedDelegate} = await keystoneService.changePassword({
1154
+ // keystoneIbGib: delegateKeystone,
1155
+ // newMasterSecret: newPassphrase,
1156
+ // signingSecret: currentPassphrase,
1157
+ // signingPoolId: 'manage',
1158
+ // poolIds: poolsToRotate,
1159
+ // metaspace,
1160
+ // space
1161
+ // });
1162
+ // evolvedDelegates.push(evolvedDelegate);
1163
+ // }
1164
+ // }
1165
+
1166
+ // 4. Sync evolved parent and all evolved delegate keystones atomically
1167
+ this.setChangePassphraseStatus("Syncing evolved keystones to server...", "info");
1168
+ const parentTjp = getIbGibAddr({ ibGib: this.ibGib! }); // is this.ibGib the tjp always? or is it the latest in the keystone line? Does it matter?
1169
+
1170
+ const syncRes = await spaceGibApiBridge.putEvolveKeystone({
1171
+ domainAddr: parentTjp,
1172
+ keystoneIbGib: evolvedParent,
1173
+ relatedKeystoneIbGibs: evolvedDelegates,
1174
+ });
1175
+ if (!syncRes.success) {
1176
+ throw new Error(`Server synchronization rejected: ${syncRes.message}`);
1177
+ }
1178
+
1179
+ // 5. Update index locally
1180
+ this.setChangePassphraseStatus("Updating local indexes...", "info");
1181
+ const newKeystoneIbGibs = [toDto({ ibGib: evolvedParent })];
1182
+ if (evolvedDelegates) {
1183
+ evolvedDelegates.forEach(d => {
1184
+ newKeystoneIbGibs.push(toDto({ ibGib: d }));
1185
+ });
1186
+ }
1187
+ const rel8nInfos = [
1188
+ {
1189
+ rel8nName: "keystone",
1190
+ ibGibs: newKeystoneIbGibs,
1191
+ }
1192
+ ];
1193
+ await updateSpecialIndex({
1194
+ type: "keystones",
1195
+ rel8nInfos,
1196
+ metaspace,
1197
+ space
1198
+ });
1199
+
1200
+ // Clear inputs
1201
+ this.elements!.inputCurrentPassphraseEl.value = "";
1202
+ this.elements!.inputNewPassphraseEl.value = "";
1203
+ this.elements!.inputConfirmPassphraseEl.value = "";
1204
+
1205
+ this.setChangePassphraseStatus("Passphrase updated successfully!", "success");
1206
+ await this.loadIbGib({ getLatest: true });
1207
+
1208
+ } catch (error) {
1209
+ console.error(`${lc} Passphrase rotation failed: ${extractErrorMsg(error)}`);
1210
+ this.setChangePassphraseStatus(`Update failed: ${extractErrorMsg(error)}`, "error");
1211
+ }
1212
+ }
1013
1213
  }
@@ -102,11 +102,11 @@ export async function createAndRegisterSyncDelegate({
102
102
  const domainAddr = getIbGibAddr({ ibGib: domainI });
103
103
 
104
104
  // 4. Sync evolved parent and delegate to server
105
- const resEvolve = await apiBridge.putEvolveKeystone(
105
+ const resEvolve = await apiBridge.putEvolveKeystone({
106
106
  domainAddr,
107
- evolvedDomainI,
108
- [delegateKeystone]
109
- );
107
+ keystoneIbGib: evolvedDomainI,
108
+ relatedKeystoneIbGibs: [delegateKeystone]
109
+ });
110
110
  if (!resEvolve.success) {
111
111
  throw new Error(`Server rejected delegate evolution: ${resEvolve.message}`);
112
112
  }
@@ -29,9 +29,18 @@ interface EvolveParams extends ParamsWithDomain {
29
29
  domainAddr: string;
30
30
  }
31
31
 
32
+ /**
33
+ * Body payload for the Keystone Evolve REST API request.
34
+ */
32
35
  interface EvolveBody {
36
+ /** The main evolved keystone frame. */
33
37
  keystoneIbGib: KeystoneIbGib_V1;
34
- relatedIbGibs?: IbGib_V1[];
38
+ /**
39
+ * Array of other keystone frames related to this evolution.
40
+ * Ephemeral session keystones for sync, or persistent delegate keystones for manage.
41
+ */
42
+ relatedKeystoneIbGibs?: IbGib_V1[];
43
+ /** Optional map of providerId to token to revoke during unlinking. */
35
44
  revokeTokens?: Record<string, string>;
36
45
  }
37
46
 
@@ -78,7 +87,7 @@ export class KeystoneEvolveHandler extends ServeGibHandlerWithMetaspaceBase<Evol
78
87
  return this.error(400, 'Invalid request JSON body: bodyStr does NOT parse. (E: 2f81c8c80a6884ff32b877671e92f326)');
79
88
  }
80
89
 
81
- const { keystoneIbGib, relatedIbGibs = [] } = parsed;
90
+ const { keystoneIbGib, relatedKeystoneIbGibs = [] } = parsed;
82
91
  if (!keystoneIbGib) {
83
92
  return this.error(400, 'Invalid request. Body must contain keystoneIbGib (E: e234647f79282982c8012fe8015f5826)');
84
93
  }
@@ -92,7 +101,7 @@ export class KeystoneEvolveHandler extends ServeGibHandlerWithMetaspaceBase<Evol
92
101
  await validateAndRegisterEvolveKeystone({
93
102
  domainAddr,
94
103
  keystoneIbGib,
95
- relatedIbGibs,
104
+ relatedKeystoneIbGibs,
96
105
  metaspace: reqCtx.metaspace,
97
106
  space
98
107
  });