@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/dist/client/bootstrap.mjs +1 -1
- package/dist/client/{chunk-DBHMHCGD.mjs → chunk-ACIGPCQW.mjs} +95 -29
- package/dist/client/{chunk-DBHMHCGD.mjs.map → chunk-ACIGPCQW.mjs.map} +3 -3
- package/dist/client/{chunk-SUQ5QJH4.mjs → chunk-QRMTJE6M.mjs} +23 -23
- package/dist/client/{chunk-SUQ5QJH4.mjs.map → chunk-QRMTJE6M.mjs.map} +3 -3
- package/dist/client/index.mjs +1 -1
- package/dist/client/script.mjs +1 -1
- package/dist/server/server.mjs +247 -141
- package/dist/server/server.mjs.map +2 -2
- package/package.json +4 -4
- package/src/client/AUTO-GENERATED-version.mts +1 -1
- package/src/client/api/space-gib-api-bridge.mts +37 -8
- package/src/client/components/keystone-creator/keystone-creator.mts +5 -5
- package/src/client/components/keystone-details/keystone-details.css +42 -0
- package/src/client/components/keystone-details/keystone-details.html +24 -0
- package/src/client/components/keystone-details/keystone-details.mts +217 -17
- package/src/client/dev-tools/common.mts +4 -4
- package/src/server/serve-gib/handlers/api/keystone/keystone-evolve.handler.mts +12 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ibgib/space-gib",
|
|
3
|
-
"version": "0.0.
|
|
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.
|
|
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.
|
|
19
|
-
"@ibgib/node-gib": "^0.0.
|
|
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"
|
|
@@ -51,14 +51,39 @@ export class SpaceGibApiBridge {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
/**
|
|
54
|
-
* PUTs an evolved domain keystone and any related
|
|
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
|
|
58
|
-
keystoneIbGib
|
|
59
|
-
|
|
60
|
-
revokeTokens
|
|
61
|
-
|
|
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({
|
|
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
|
-
|
|
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 ${
|
|
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
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
104
|
+
relatedKeystoneIbGibs,
|
|
96
105
|
metaspace: reqCtx.metaspace,
|
|
97
106
|
space
|
|
98
107
|
});
|