@ibgib/space-gib 0.0.9 → 0.0.13

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 (82) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +1 -0
  3. package/dist/client/bootstrap.mjs +33 -33
  4. package/dist/client/bootstrap.mjs.map +3 -3
  5. package/dist/client/chunk-ROJZUPPE.mjs +19 -0
  6. package/dist/client/chunk-ROJZUPPE.mjs.map +7 -0
  7. package/dist/client/chunk-WJHRARK6.mjs +2329 -0
  8. package/dist/client/chunk-WJHRARK6.mjs.map +7 -0
  9. package/dist/client/index.mjs +22 -1
  10. package/dist/client/index.mjs.map +4 -4
  11. package/dist/client/script.mjs +1 -1
  12. package/dist/server/server.mjs +19147 -18210
  13. package/dist/server/server.mjs.map +4 -4
  14. package/package.json +4 -4
  15. package/space-gib.localhost-1785337158142.log +75 -0
  16. package/src/client/AUTO-GENERATED-version.mts +1 -1
  17. package/src/client/README.md +1 -1
  18. package/src/client/api/space-gib-api-bridge.mts +8 -224
  19. package/src/client/ui/shell/space-gib-shell-service.mts +68 -18
  20. package/src/common/keystone-policies.mts +1 -1
  21. package/src/server/README.md +3 -2
  22. package/src/server/server.mts +13 -32
  23. package/src/server/test.json +0 -0
  24. package/dist/client/chunk-ACIGPCQW.mjs +0 -2407
  25. package/dist/client/chunk-ACIGPCQW.mjs.map +0 -7
  26. package/dist/client/chunk-QRMTJE6M.mjs +0 -42
  27. package/dist/client/chunk-QRMTJE6M.mjs.map +0 -7
  28. package/space-gib.localhost-1783713259760.log +0 -45
  29. package/src/client/components/identity-header/IMPLEMENTATION.md +0 -45
  30. package/src/client/components/identity-header/identity-header.css +0 -112
  31. package/src/client/components/identity-header/identity-header.html +0 -11
  32. package/src/client/components/identity-header/identity-header.mts +0 -439
  33. package/src/client/components/identity-manager/IMPLEMENTATION.md +0 -100
  34. package/src/client/components/identity-manager/identity-manager.css +0 -171
  35. package/src/client/components/identity-manager/identity-manager.html +0 -19
  36. package/src/client/components/identity-manager/identity-manager.mts +0 -639
  37. package/src/client/components/keystone-creator/keystone-creator.css +0 -65
  38. package/src/client/components/keystone-creator/keystone-creator.html +0 -41
  39. package/src/client/components/keystone-creator/keystone-creator.mts +0 -443
  40. package/src/client/components/keystone-details/keystone-details.css +0 -611
  41. package/src/client/components/keystone-details/keystone-details.html +0 -151
  42. package/src/client/components/keystone-details/keystone-details.mts +0 -1213
  43. package/src/client/components/keystone-scrubber/SCRUBBER_IMPLEMENTATION.md +0 -33
  44. package/src/client/components/keystone-scrubber/keystone-scrubber.css +0 -46
  45. package/src/client/components/keystone-scrubber/keystone-scrubber.html +0 -15
  46. package/src/client/components/keystone-scrubber/keystone-scrubber.mts +0 -356
  47. package/src/server/bootstrap-helper.mts +0 -141
  48. package/src/server/bootstrap-helper.respec.mts +0 -100
  49. package/src/server/metaspace-nodeindexedspace/metaspace-nodeindexedspace.mts +0 -85
  50. package/src/server/path-constants.mts +0 -101
  51. package/src/server/path-helper.mts +0 -101
  52. package/src/server/path-helper.respec.mts +0 -99
  53. package/src/server/serve-gib/CHANGELOG.md +0 -29
  54. package/src/server/serve-gib/README.md +0 -43
  55. package/src/server/serve-gib/constants.mts +0 -1
  56. package/src/server/serve-gib/handlers/api/debug/ws-echo.handler.mts +0 -105
  57. package/src/server/serve-gib/handlers/api/health.handler.mts +0 -23
  58. package/src/server/serve-gib/handlers/api/health.respec.mts +0 -51
  59. package/src/server/serve-gib/handlers/api/ibgib/ibgib-handler-types.mts +0 -49
  60. package/src/server/serve-gib/handlers/api/ibgib/ibgib.handler.mts +0 -176
  61. package/src/server/serve-gib/handlers/api/keystone/keystone-evolve.handler.mts +0 -150
  62. package/src/server/serve-gib/handlers/api/keystone/keystone-genesis.handler.mts +0 -146
  63. package/src/server/serve-gib/handlers/api/keystone/keystone-get.handler.mts +0 -198
  64. package/src/server/serve-gib/handlers/api/keystone/keystone-get.respec.mts +0 -107
  65. package/src/server/serve-gib/handlers/api/keystone/keystone-handler-types.mts +0 -29
  66. package/src/server/serve-gib/handlers/api/keystone/keystone-post.handler.mts +0 -55
  67. package/src/server/serve-gib/handlers/api/keystone/keystone-post.respec.mts +0 -130
  68. package/src/server/serve-gib/handlers/api/keystone/sso-config.handler.mts +0 -66
  69. package/src/server/serve-gib/handlers/api/keystone/sso-link.handler.mts +0 -140
  70. package/src/server/serve-gib/handlers/api/keystone/sso-login.handler.mts +0 -190
  71. package/src/server/serve-gib/handlers/error-handler.mts +0 -36
  72. package/src/server/serve-gib/handlers/handler-base.mts +0 -383
  73. package/src/server/serve-gib/handlers/static-handler.mts +0 -82
  74. package/src/server/serve-gib/handlers/ws/sync-upgrade-handler-base.mts +0 -233
  75. package/src/server/serve-gib/handlers/ws/sync-upgrade.handler.mts +0 -24
  76. package/src/server/serve-gib/handlers/ws/ws-helper.mts +0 -216
  77. package/src/server/serve-gib/handlers/ws/ws-types.mts +0 -53
  78. package/src/server/serve-gib/serve-gib-helpers.mts +0 -32
  79. package/src/server/serve-gib/serve-gib-v1.mts +0 -172
  80. package/src/server/serve-gib/serve-gib.respec.mts +0 -90
  81. package/src/server/serve-gib/types.mts +0 -102
  82. package/src/server/server-constants.mts +0 -2
@@ -1,1213 +0,0 @@
1
- import styleCss from "../../style.css";
2
- import thisCss from "./keystone-details.css";
3
- import thisHtml from "./keystone-details.html";
4
-
5
- import { extractErrorMsg, pretty } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
6
- import { IbGibAddr } from "@ibgib/ts-gib/dist/types.mjs";
7
- import { IbGib_V1 } from "@ibgib/ts-gib/dist/V1/types.mjs";
8
- import { getIbAndGib, getIbGibAddr } from "@ibgib/ts-gib/dist/helper.mjs";
9
- import { getGibInfo } from "@ibgib/ts-gib/dist/V1/transforms/transform-helper.mjs";
10
- import { KeystoneService_V1 } from "@ibgib/core-gib/dist/keystone/keystone-service-v1.mjs";
11
- import { KeystoneIbGib_V1 } from "@ibgib/core-gib/dist/keystone/keystone-types.mjs";
12
- import {
13
- IbGibDynamicComponentMetaBase, IbGibDynamicComponentInstanceBase,
14
- } from "@ibgib/web-gib/dist/ui/component/ibgib-dynamic-component-bases.mjs";
15
- import {
16
- ElementsBase, IbGibDynamicComponentInstance,
17
- IbGibDynamicComponentInstanceInitOpts,
18
- } from "@ibgib/web-gib/dist/ui/component/component-types.mjs";
19
- import { EVENT_IBGIB_IDENTITY_REQUEST_CHANGE, EVENT_IBGIB_IDENTITY_CHANGED } from "@ibgib/web-gib/dist/ui/ui-constants.mjs";
20
- import { spaceGibApiBridge } from "../../api/space-gib-api-bridge.mjs";
21
- import { updateSpecialIndex } from "@ibgib/core-gib/dist/timeline/timeline-api.mjs";
22
- import { getTjpAddr, toDto } from "@ibgib/core-gib/dist/common/other/ibgib-helper.mjs";
23
-
24
- import { GLOBAL_LOG_A_LOT, APP_CONFIG, } from "../../constants.mjs";
25
- import { getComponentCtorArg, getIbGibGlobalThis_SpaceGib } from "../../helpers.web.mjs";
26
- import { devLog } from "../../dev-tools.mjs";
27
- import { promptForSecret } from "@ibgib/web-gib/dist/helpers.web.mjs";
28
- import { isSsoProviderLinked } from "@ibgib/web-gib/dist/identity/sso/sso-helpers.mjs";
29
-
30
- const logalot = GLOBAL_LOG_A_LOT || true;
31
-
32
- export const KEYSTONE_DETAILS_COMPONENT_NAME = 'ibgib-keystone-details';
33
-
34
- export class KeystoneDetailsComponentMeta extends IbGibDynamicComponentMetaBase {
35
- protected override lc: string = `[KeystoneDetailsComponentMeta]`;
36
-
37
- routeRegExp?: RegExp = new RegExp(`^${KEYSTONE_DETAILS_COMPONENT_NAME}$`);
38
- componentName = KEYSTONE_DETAILS_COMPONENT_NAME;
39
-
40
- constructor() {
41
- super(getComponentCtorArg());
42
- if (!customElements.get(this.componentName)) {
43
- customElements.define(this.componentName, KeystoneDetailsComponentInstance);
44
- }
45
- }
46
-
47
- async createInstance({
48
- path,
49
- ibGibAddr
50
- }: {
51
- path: string;
52
- ibGibAddr: IbGibAddr;
53
- }): Promise<IbGibDynamicComponentInstance> {
54
- const lc = `${this.lc}[${this.createInstance.name}]`;
55
- const component = document.createElement(this.componentName) as KeystoneDetailsComponentInstance;
56
- await component.initialize({
57
- ibGibAddr,
58
- meta: this,
59
- html: thisHtml,
60
- css: [styleCss, thisCss],
61
- });
62
- return component;
63
- }
64
- }
65
-
66
- interface KeystoneDetailsElements extends ElementsBase {
67
- identityDetailsViewEl: HTMLDivElement;
68
- identityAddrEl: HTMLElement;
69
- identityGenEl: HTMLElement;
70
- identityTimestampEl: HTMLElement;
71
- identityUuidEl: HTMLElement;
72
- identityNameEl: HTMLElement;
73
- identityDescriptionEl: HTMLElement;
74
- identityFrameDetailsEl: HTMLPreElement;
75
- identityAggrDetailsEl: HTMLPreElement;
76
-
77
- poolsContainerEl: HTMLDivElement;
78
- btnVerifyChainEl: HTMLButtonElement;
79
- btnSetActiveEl: HTMLButtonElement;
80
- verificationStatusEl: HTMLDivElement;
81
- rawAccordionHeaderEl: HTMLDivElement;
82
- rawAccordionContentEl: HTMLDivElement;
83
- rawKeystoneJsonEl: HTMLPreElement;
84
- delegatesCardEl: HTMLDivElement;
85
- delegatesListEl: HTMLTableSectionElement;
86
- historicalWarningBannerEl: HTMLDivElement;
87
- containerEl: HTMLDivElement;
88
- ssoLinkCardEl: HTMLDivElement;
89
- btnLinkGoogleEl: HTMLDivElement;
90
- btnLinkGithubEl: HTMLDivElement;
91
- ssoStatusMsgEl: HTMLDivElement;
92
- inputCurrentPassphraseEl: HTMLInputElement;
93
- inputNewPassphraseEl: HTMLInputElement;
94
- inputConfirmPassphraseEl: HTMLInputElement;
95
- btnChangePassphraseEl: HTMLButtonElement;
96
- changePassphraseStatusEl: HTMLDivElement;
97
- changePassphraseCardEl: HTMLDivElement;
98
- }
99
-
100
- export class KeystoneDetailsComponentInstance
101
- extends IbGibDynamicComponentInstanceBase<KeystoneIbGib_V1, KeystoneDetailsElements>
102
- implements IbGibDynamicComponentInstance<KeystoneIbGib_V1, KeystoneDetailsElements> {
103
-
104
- protected override lc: string = `[KeystoneDetailsComponentInstance]`;
105
-
106
- private staticIbGib: KeystoneIbGib_V1 | undefined;
107
-
108
- override get ibGib(): KeystoneIbGib_V1 | undefined {
109
- return this.staticIbGib;
110
- }
111
-
112
- private globalActiveAddr: string | null = null;
113
- private isHistoricalFrame: boolean = false;
114
- private _onIdentityChanged: ((e: any) => void) | null = null;
115
- private keystoneCache: Map<string, { username: string, description: string, isPrimary: boolean, n: number }> = new Map();
116
-
117
- constructor() {
118
- super();
119
- }
120
-
121
- override async initialize(opts: IbGibDynamicComponentInstanceInitOpts): Promise<void> {
122
- const lc = `${this.lc}[${this.initialize.name}]`;
123
- try {
124
- if (logalot) { console.log(`${lc} starting... (I: 4c9328643a18e56b56e57292c87bd226)`); }
125
-
126
- await super.initialize(opts);
127
- // await this.loadIbGib({ getLatest: false });
128
- await this.loadIbGib({ getLatest: false });
129
-
130
- } catch (error) {
131
- console.error(`${lc} ${extractErrorMsg(error)}`);
132
- throw error;
133
- } finally {
134
- if (logalot) { console.log(`${lc} complete.`); }
135
- }
136
- }
137
-
138
- private async getKeystoneDetails(addr: string): Promise<{ username: string, description: string, isPrimary: boolean, n: number }> {
139
- const cached = this.keystoneCache.get(addr);
140
- if (cached) return cached;
141
-
142
- const metaspace = getIbGibGlobalThis_SpaceGib(APP_CONFIG).metaspace;
143
- if (!metaspace) return { username: '', description: '', isPrimary: false, n: 0 };
144
- const space = await metaspace.getLocalUserSpace({ lock: false });
145
- if (!space) return { username: '', description: '', isPrimary: false, n: 0 };
146
-
147
- try {
148
- const keystoneSvc = new KeystoneService_V1();
149
- const info = await keystoneSvc.getKeystoneCommonInfo({
150
- addr,
151
- metaspace,
152
- space
153
- });
154
- if (info) {
155
- const details = {
156
- username: info.name,
157
- description: info.description,
158
- isPrimary: info.isPrimary,
159
- n: info.n
160
- };
161
- this.keystoneCache.set(addr, details);
162
- return details;
163
- }
164
- } catch (err) {
165
- console.warn(`Error getting details for keystone ${addr}: ${extractErrorMsg(err)}`);
166
- }
167
-
168
- return { username: '', description: '', isPrimary: false, n: 0 };
169
- }
170
-
171
- override async created(): Promise<void> {
172
- const lc = `${this.lc}[${this.created.name}]`;
173
- try {
174
- if (logalot) { console.log(`${lc} starting... (I: 0b2c6debcdac9db9b7928aa8695ed826)`); }
175
-
176
- this.elements = {} as KeystoneDetailsElements;
177
-
178
- const shadow = this.shadowRoot!;
179
- this.elements.identityDetailsViewEl = shadow.getElementById('identity-details-view') as HTMLDivElement;
180
- this.elements.identityAddrEl = shadow.getElementById('identity-addr') as HTMLElement;
181
- this.elements.identityGenEl = shadow.getElementById('identity-gen') as HTMLSpanElement;
182
- this.elements.identityTimestampEl = shadow.getElementById('identity-timestamp') as HTMLSpanElement;
183
- this.elements.identityUuidEl = shadow.getElementById('identity-uuid') as HTMLSpanElement;
184
- this.elements.identityNameEl = shadow.getElementById('identity-name') as HTMLElement;
185
- this.elements.identityDescriptionEl = shadow.getElementById('identity-description') as HTMLElement;
186
- this.elements.identityFrameDetailsEl = shadow.getElementById('identity-frame-details') as HTMLPreElement;
187
- this.elements.identityAggrDetailsEl = shadow.getElementById('identity-aggr-details') as HTMLPreElement;
188
-
189
- this.elements.poolsContainerEl = shadow.getElementById('pools-container') as HTMLDivElement;
190
- this.elements.btnVerifyChainEl = shadow.getElementById('btn-verify-chain') as HTMLButtonElement;
191
- this.elements.btnSetActiveEl = shadow.getElementById('btn-set-active') as HTMLButtonElement;
192
- this.elements.verificationStatusEl = shadow.getElementById('verification-status') as HTMLDivElement;
193
- this.elements.rawAccordionHeaderEl = shadow.getElementById('raw-accordion-header') as HTMLDivElement;
194
- this.elements.rawAccordionContentEl = shadow.getElementById('raw-accordion-content') as HTMLDivElement;
195
- this.elements.rawKeystoneJsonEl = shadow.getElementById('raw-keystone-json') as HTMLPreElement;
196
- this.elements.delegatesCardEl = shadow.getElementById('delegates-card') as HTMLDivElement;
197
- this.elements.delegatesListEl = shadow.getElementById('delegates-list') as HTMLTableSectionElement;
198
- this.elements.containerEl = shadow.getElementById('container') as HTMLDivElement;
199
- this.elements.historicalWarningBannerEl = shadow.getElementById('historical-warning-banner') as HTMLDivElement;
200
- this.elements.ssoLinkCardEl = shadow.getElementById('sso-link-card') as HTMLDivElement;
201
- this.elements.btnLinkGoogleEl = shadow.getElementById('btn-link-google') as HTMLDivElement;
202
- this.elements.btnLinkGithubEl = shadow.getElementById('btn-link-github') as HTMLDivElement;
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;
210
-
211
- // ElementBase requires contentEl
212
- this.elements.contentEl = shadow.getElementById('identity-details-view') as HTMLDivElement;
213
-
214
- this.initHandlers();
215
-
216
- // Subscribe to global active identity changes
217
- this._onIdentityChanged = (e: any) => {
218
- this.globalActiveAddr = e.detail?.activeIdentityAddr || null;
219
- this.updateActiveButtonState();
220
- };
221
- window.addEventListener(EVENT_IBGIB_IDENTITY_CHANGED, this._onIdentityChanged);
222
-
223
- await this.resolveActiveIdentity();
224
- await this.loadIbGib({ getLatest: false });
225
- } catch (error) {
226
- console.error(`${lc} ${extractErrorMsg(error)}`);
227
- throw error;
228
- } finally {
229
- if (logalot) { console.log(`${lc} complete.`); }
230
- }
231
- }
232
-
233
- override async disconnected(): Promise<void> {
234
- if (this._onIdentityChanged) {
235
- window.removeEventListener(EVENT_IBGIB_IDENTITY_CHANGED, this._onIdentityChanged);
236
- }
237
- }
238
-
239
- private initHandlers() {
240
- // Setup raw JSON accordion toggle
241
- this.elements!.rawAccordionHeaderEl.addEventListener('click', () => this.toggleAccordion());
242
-
243
- // Buttons
244
- this.elements!.btnVerifyChainEl.addEventListener('click', () => this.handleVerifyIdentityChain());
245
- this.elements!.btnSetActiveEl.addEventListener('click', () => this.handleSetActiveIdentity());
246
- this.elements!.btnLinkGoogleEl.addEventListener('click', () => this.handleSSOClick('google'));
247
- this.elements!.btnLinkGithubEl.addEventListener('click', () => this.handleSSOClick('github'));
248
- this.elements!.btnChangePassphraseEl.addEventListener('click', () => this.handleChangePassphrase());
249
- }
250
-
251
- private toggleAccordion() {
252
- const header = this.elements!.rawAccordionHeaderEl;
253
- const content = this.elements!.rawAccordionContentEl;
254
- header.classList.toggle('expanded');
255
- content.classList.toggle('expanded');
256
- }
257
-
258
- protected async handleVerifyIdentityChain() {
259
- const lc = `${this.lc}[${this.handleVerifyIdentityChain.name}]`;
260
- devLog(`${lc} Verifying identity keystone timeline...`);
261
- this.setVerificationStatus("Verification request sent...", "info");
262
- try {
263
- this.setVerificationStatus("Verification complete. Root Keystone is verified (Genesis phase 1).", "success");
264
- } catch (error) {
265
- this.setVerificationStatus(`Verification failed: ${extractErrorMsg(error)}`, "error");
266
- }
267
- }
268
-
269
- protected async handleSetActiveIdentity() {
270
- if (!this.ibGibAddr) { return; }
271
- window.dispatchEvent(new CustomEvent(EVENT_IBGIB_IDENTITY_REQUEST_CHANGE, {
272
- detail: { activeIdentityAddr: this.ibGibAddr },
273
- bubbles: true,
274
- composed: true
275
- }));
276
- }
277
-
278
- private setVerificationStatus(msg: string, type: 'info' | 'success' | 'error') {
279
- const statusEl = this.elements!.verificationStatusEl;
280
- statusEl.textContent = msg;
281
- statusEl.className = `status-msg ${type}`;
282
- statusEl.classList.remove('hidden');
283
- }
284
-
285
- private async resolveActiveIdentity() {
286
- const lc = `${this.lc}[resolveActiveIdentity]`;
287
- try {
288
- const domainIdentity = getIbGibGlobalThis_SpaceGib(APP_CONFIG).identity?.domainIdentity;
289
- this.globalActiveAddr = domainIdentity ? getIbGibAddr({ ibGib: domainIdentity }) : null;
290
- } catch (error) {
291
- console.error(`${lc} ${extractErrorMsg(error)}`);
292
- this.globalActiveAddr = null;
293
- }
294
- }
295
-
296
- override async loadIbGib(opts?: { getLatest?: boolean }): Promise<void> {
297
- const lc = `${this.lc}[${this.loadIbGib.name}]`;
298
- try {
299
- if (logalot) { console.log(`${lc} starting... (I: f0f7e8d52cbdc37bec844b2875996e26)`); }
300
-
301
- if (!this.ibGibAddr) { return; }
302
-
303
- const metaspace = getIbGibGlobalThis_SpaceGib(APP_CONFIG).metaspace;
304
- if (!metaspace) {
305
- throw new Error(`(UNEXPECTED) metaspace falsy? (E: a9864dbbaefc1082ce7bd228ebd67c26)`);
306
- }
307
- let space = await metaspace.getLocalUserSpace({ lock: false });
308
- if (!space) {
309
- throw new Error(`(UNEXPECTED) space falsy and we couldn't get default local user space from metaspace? (E: fa298edcecbda889cfd7b228fbd57c26)`);
310
- }
311
-
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 });
322
-
323
- if (resGet.success && resGet.ibGibs && resGet.ibGibs.length === 1) {
324
- this.staticIbGib = resGet.ibGibs[0] as KeystoneIbGib_V1;
325
- } else {
326
- const resIbGib = resGet.rawResultIbGib as any;
327
- const addrsNotFound = resIbGib?.data?.addrsNotFound ?? 'unknown';
328
- throw new Error(`couldn't find address ${loadAddr}. addrsNotFound: ${addrsNotFound}? resGet.errorMsg: ${resGet.errorMsg} (E: e0e5c9b7de2837bc902fa88390cd9f26)`);
329
- }
330
-
331
- await this.updateFrameDetailsView();
332
- } catch (error) {
333
- console.error(`${lc} ${extractErrorMsg(error)}`);
334
- throw error;
335
- } finally {
336
- if (logalot) { console.log(`${lc} complete.`); }
337
- }
338
- }
339
- protected override async renderUI(): Promise<void> {
340
- const lc = `${this.lc}[${this.renderUI.name}]`;
341
- try {
342
- if (logalot) { console.log(`${lc} starting... (I: c8aa086352d8079708e00f78b6ac0826)`); }
343
- await super.renderUI();
344
- // await this.updateFrameDetailsView();
345
- } catch (error) {
346
- console.error(`${lc} ${extractErrorMsg(error)}`);
347
- throw error;
348
- } finally {
349
- if (logalot) { console.log(`${lc} complete.`); }
350
- }
351
- }
352
-
353
- private updateActiveButtonState() {
354
- if (!this.elements) { return; }
355
-
356
- const tjpGib = getGibInfo({ ibGibAddr: this.ibGibAddr }).tjpGib ?? getIbAndGib({ ibGibAddr: this.ibGibAddr }).gib;
357
- const activeTjpGib = this.globalActiveAddr
358
- ? (getGibInfo({ ibGibAddr: this.globalActiveAddr }).tjpGib ?? getIbAndGib({ ibGibAddr: this.globalActiveAddr }).gib)
359
- : null;
360
-
361
- const isTimelineActive = !!(this.globalActiveAddr && tjpGib === activeTjpGib);
362
-
363
- if (isTimelineActive) {
364
- this.elements.btnSetActiveEl.textContent = "Active Identity";
365
- this.elements.btnSetActiveEl.classList.add('active-primary');
366
- } else {
367
- this.elements.btnSetActiveEl.textContent = "Set as Active Identity";
368
- this.elements.btnSetActiveEl.classList.remove('active-primary');
369
- }
370
-
371
- // Disable buttons if historical
372
- if (this.isHistoricalFrame) {
373
- this.elements.btnSetActiveEl.disabled = true;
374
- this.elements.btnVerifyChainEl.disabled = true;
375
- this.elements.btnLinkGoogleEl.classList.add('disabled');
376
- this.elements.btnLinkGithubEl.classList.add('disabled');
377
- } else {
378
- this.elements.btnVerifyChainEl.disabled = false;
379
- this.elements.btnSetActiveEl.disabled = isTimelineActive;
380
- this.elements.btnLinkGoogleEl.classList.remove('disabled');
381
- this.elements.btnLinkGithubEl.classList.remove('disabled');
382
-
383
- // Check if already linked
384
- const isGoogleLinked = isSsoProviderLinked({ keystone: this.ibGib, providerId: 'google' });
385
- const isGithubLinked = isSsoProviderLinked({ keystone: this.ibGib, providerId: 'github' });
386
-
387
- // Google Card States
388
- const googleCard = this.elements.btnLinkGoogleEl;
389
- const googleLabel = googleCard.querySelector('.toggle-state-text')!;
390
- if (isGoogleLinked) {
391
- googleCard.classList.remove('unlinked');
392
- googleCard.classList.add('linked');
393
- googleLabel.textContent = "Google Linked ✅";
394
- } else {
395
- googleCard.classList.remove('linked');
396
- googleCard.classList.add('unlinked');
397
- googleLabel.textContent = "Unlinked";
398
- }
399
-
400
- // GitHub Card States
401
- const githubCard = this.elements.btnLinkGithubEl;
402
- const githubLabel = githubCard.querySelector('.toggle-state-text')!;
403
- if (isGithubLinked) {
404
- githubCard.classList.remove('unlinked');
405
- githubCard.classList.add('linked');
406
- githubLabel.textContent = "GitHub Linked ✅";
407
- } else {
408
- githubCard.classList.remove('linked');
409
- githubCard.classList.add('unlinked');
410
- githubLabel.textContent = "Unlinked";
411
- }
412
- }
413
- }
414
-
415
- private async updateFrameDetailsView(): Promise<void> {
416
- const lc = `${this.lc}[${this.updateFrameDetailsView.name}]`;
417
- try {
418
- if (logalot) { console.log(`${lc} starting... (I: 1fa9e66d2e78fb100874573e2db71d26)`); }
419
-
420
- if (!this.elements) { return; }
421
-
422
- const keystone = this.ibGib;
423
- if (!keystone) {
424
- this.clearActiveDetails();
425
- return;
426
- }
427
- const keystoneAddr = this.ibGibAddr;
428
-
429
- // Update Verifiable Address
430
- this.elements.identityAddrEl.textContent = keystoneAddr;
431
-
432
- // Update Generation
433
- this.elements.identityGenEl.textContent = keystone.data?.n !== undefined ? String(keystone.data.n) : '0 (Genesis)';
434
-
435
- // Update Timestamp
436
- const timestampStr = keystone.data?.timestamp;
437
- let formattedTimestamp = 'N/A';
438
- if (timestampStr) {
439
- const parsed = parseInt(timestampStr);
440
- if (!isNaN(parsed)) {
441
- formattedTimestamp = new Date(parsed).toLocaleString();
442
- } else {
443
- formattedTimestamp = new Date(timestampStr).toLocaleString();
444
- }
445
- }
446
- this.elements.identityTimestampEl.textContent = formattedTimestamp;
447
-
448
- // Update UUID
449
- this.elements.identityUuidEl.textContent = keystone.data?.uuid || 'N/A';
450
-
451
- // Show Frame Details
452
- const frameDetails = keystone.data?.frameDetails;
453
- this.elements.identityFrameDetailsEl.textContent = frameDetails ? pretty(frameDetails) : '{}';
454
-
455
- // Get and Show Aggregated details up to the scrolled-to frame
456
- let isHistorical = false;
457
- const metaspace = getIbGibGlobalThis_SpaceGib(APP_CONFIG).metaspace;
458
- if (metaspace) {
459
- const space = await metaspace.getLocalUserSpace({ lock: false });
460
- if (space) {
461
- try {
462
- const latestAddr = await metaspace.getLatestAddr({ addr: this.ibGibAddr, space });
463
- if (latestAddr && latestAddr !== this.ibGibAddr) {
464
- isHistorical = true;
465
- }
466
- } catch (err) {
467
- console.warn(`${lc} Error checking latest address: ${extractErrorMsg(err)}`);
468
- }
469
-
470
- try {
471
- const keystoneSvc = new KeystoneService_V1();
472
- const aggregated = await keystoneSvc.getAggregateDetails({
473
- latestKeystone: keystone,
474
- metaspace,
475
- space
476
- });
477
-
478
- this.elements.identityAggrDetailsEl.textContent = aggregated ? pretty(aggregated) : '{}';
479
-
480
- // Update Username and Description fields
481
- this.elements.identityNameEl.textContent = aggregated?.username || aggregated?.profile?.name || aggregated?.name || '-';
482
- this.elements.identityDescriptionEl.textContent = aggregated?.description || aggregated?.profile?.description || '-';
483
- } catch (err) {
484
- console.warn(`${lc} Error aggregating details: ${extractErrorMsg(err)}`);
485
- this.elements.identityAggrDetailsEl.textContent = '{}';
486
- this.elements.identityNameEl.textContent = '-';
487
- this.elements.identityDescriptionEl.textContent = '-';
488
- }
489
- } else {
490
- this.elements.identityAggrDetailsEl.textContent = '{}';
491
- this.elements.identityNameEl.textContent = '-';
492
- this.elements.identityDescriptionEl.textContent = '-';
493
- }
494
- } else {
495
- this.elements.identityAggrDetailsEl.textContent = '{}';
496
- this.elements.identityNameEl.textContent = '-';
497
- this.elements.identityDescriptionEl.textContent = '-';
498
- }
499
-
500
- // Update historical warning state
501
- this.isHistoricalFrame = isHistorical;
502
- const banner = this.elements.historicalWarningBannerEl;
503
- const container = this.elements.containerEl;
504
- if (banner && container) {
505
- if (isHistorical) {
506
- banner.classList.remove('hidden');
507
- container.classList.add('is-historical');
508
- } else {
509
- banner.classList.add('hidden');
510
- container.classList.remove('is-historical');
511
- }
512
- }
513
-
514
- // Render Raw Timeline JSON
515
- this.elements.rawKeystoneJsonEl.textContent = pretty(keystone);
516
-
517
- // Render Challenge Pools
518
- this.renderChallengePools(keystone.data?.challengePools || []);
519
-
520
- // Render Delegates, SSO link, and Passphrase Change cards (only if this is a primary keystone, hide them for delegate/session keystones)
521
- const details = await this.getKeystoneDetails(this.ibGibAddr);
522
- if (details.isPrimary) {
523
- this.elements.delegatesCardEl.classList.remove('hidden');
524
- await this.renderDelegates(keystone.data?.delegates);
525
- this.elements.ssoLinkCardEl.classList.remove('hidden');
526
- this.elements.changePassphraseCardEl.classList.remove('hidden');
527
- } else {
528
- this.elements.delegatesCardEl.classList.add('hidden');
529
- this.elements.ssoLinkCardEl.classList.add('hidden');
530
- this.elements.changePassphraseCardEl.classList.add('hidden');
531
- }
532
-
533
- this.updateActiveButtonState();
534
- } catch (error) {
535
- console.error(`${lc} ${extractErrorMsg(error)}`);
536
- throw error;
537
- } finally {
538
- if (logalot) { console.log(`${lc} complete.`); }
539
- }
540
- }
541
-
542
- private clearActiveDetails() {
543
- if (!this.elements) { return; }
544
- this.isHistoricalFrame = false;
545
- if (this.elements.historicalWarningBannerEl) {
546
- this.elements.historicalWarningBannerEl.classList.add('hidden');
547
- }
548
- if (this.elements.containerEl) {
549
- this.elements.containerEl.classList.remove('is-historical');
550
- }
551
- this.elements.identityGenEl.textContent = '-';
552
- this.elements.identityTimestampEl.textContent = '-';
553
- this.elements.identityUuidEl.textContent = '-';
554
- this.elements.identityNameEl.textContent = '-';
555
- this.elements.identityDescriptionEl.textContent = '-';
556
- this.elements.identityFrameDetailsEl.textContent = '{}';
557
- this.elements.identityAggrDetailsEl.textContent = '{}';
558
- this.elements.poolsContainerEl.innerHTML = '';
559
- if (this.elements.delegatesListEl) {
560
- this.elements.delegatesListEl.innerHTML = '';
561
- }
562
- }
563
-
564
- private renderChallengePools(pools: any[]) {
565
- if (!this.elements) { return; }
566
- const container = this.elements.poolsContainerEl;
567
- container.innerHTML = '';
568
-
569
- if (!pools || pools.length === 0) {
570
- container.innerHTML = `<p class="description">No challenge pools found in this keystone.</p>`;
571
- return;
572
- }
573
-
574
- for (const pool of pools) {
575
- const card = document.createElement('div');
576
- card.className = 'pool-card';
577
-
578
- const config = pool.config || {};
579
- const behavior = config.behavior || {};
580
- const allowedVerbs = config.allowedVerbs || [];
581
- const activeCount = pool.challenges ? Object.keys(pool.challenges).length : 0;
582
- const isForeign = !!pool.isForeign;
583
-
584
- // Generate HTML for the challenges list
585
- const challenges = pool.challenges || {};
586
- const challengeListHtml = Object.entries(challenges).map(([id, challenge]: [string, any]) => {
587
- const truncatedHash = challenge.hash && challenge.hash.length > 20
588
- ? challenge.hash.substring(0, 10) + '...' + challenge.hash.substring(challenge.hash.length - 8)
589
- : challenge.hash || 'N/A';
590
- return `
591
- <div class="pool-challenge-item">
592
- <span class="challenge-id">${id}</span>
593
- <span class="challenge-hash" title="${challenge.hash || ''}">${truncatedHash}</span>
594
- </div>
595
- `;
596
- }).join('');
597
-
598
- card.innerHTML = `
599
- <div class="pool-header">
600
- <span class="pool-title">${pool.id || 'Unnamed Pool'}</span>
601
- <span class="pool-tag ${isForeign ? 'foreign' : 'native'}">${isForeign ? 'Foreign' : 'Native'}</span>
602
- </div>
603
- <div class="pool-grid">
604
- <div class="pool-field">
605
- <label>Verbs Allowed</label>
606
- <span>${allowedVerbs.length > 0 ? allowedVerbs.join(', ') : 'Any'}</span>
607
- </div>
608
- <div class="pool-field">
609
- <label>FIFO / Random cost</label>
610
- <span>FIFO: ${behavior.selectSequentially ?? 0}, Rand: ${behavior.selectRandomly ?? 0}</span>
611
- </div>
612
- </div>
613
-
614
- <!-- Collapsible Challenges List -->
615
- <details class="pool-details">
616
- <summary class="pool-details-summary">Challenges (${activeCount})</summary>
617
- <div class="pool-challenges-list">
618
- ${challengeListHtml || '<p class="placeholder-text" style="padding: 0.5rem; text-align: center;">No challenges active.</p>'}
619
- </div>
620
- </details>
621
- `;
622
- container.appendChild(card);
623
- }
624
- }
625
-
626
- private async renderDelegates(delegates: Record<string, any> | undefined) {
627
- if (!this.elements) { return; }
628
- const listEl = this.elements.delegatesListEl;
629
- listEl.innerHTML = '';
630
-
631
- if (!delegates || Object.keys(delegates).length === 0) {
632
- listEl.innerHTML = `
633
- <tr>
634
- <td colspan="4" style="text-align: center; color: var(--clr-text-secondary, #b3b3b3);">
635
- No registered delegates.
636
- </td>
637
- </tr>
638
- `;
639
- return;
640
- }
641
-
642
- for (const [delegateTjpAddr, info] of Object.entries(delegates)) {
643
- const tr = document.createElement('tr');
644
-
645
- const tdName = document.createElement('td');
646
- tdName.textContent = 'Loading...';
647
-
648
- const tdTjp = document.createElement('td');
649
- tdTjp.textContent = delegateTjpAddr;
650
- tdTjp.title = delegateTjpAddr;
651
-
652
- const tdVerbs = document.createElement('td');
653
- tdVerbs.textContent = info.allowedVerbs ? info.allowedVerbs.join(', ') : '-';
654
- tdVerbs.title = tdVerbs.textContent;
655
-
656
- const tdActions = document.createElement('td');
657
- const btnView = document.createElement('button');
658
- btnView.className = 'action-btn secondary small';
659
- btnView.textContent = 'View Details';
660
- btnView.addEventListener('click', async () => {
661
- const lc = `${this.lc}[renderDelegates.click]`;
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
- }
678
- this.dispatchEvent(new CustomEvent('ibgib-view-keystone-details', {
679
- detail: { addr: addrToLoad },
680
- bubbles: true,
681
- composed: true
682
- }));
683
- }
684
- });
685
- tdActions.appendChild(btnView);
686
-
687
- tr.appendChild(tdName);
688
- tr.appendChild(tdTjp);
689
- tr.appendChild(tdVerbs);
690
- tr.appendChild(tdActions);
691
- listEl.appendChild(tr);
692
-
693
- if (info.delegateAddr) {
694
- this.getKeystoneDetails(info.delegateAddr).then(details => {
695
- const textParts: string[] = [];
696
- if (details.username) textParts.push(details.username);
697
- if (details.description) textParts.push(details.description);
698
- tdName.textContent = textParts.join(' / ') || 'Unnamed Delegate';
699
- tdName.title = `Addr: ${info.delegateAddr}\n` + textParts.join('\n');
700
- }).catch(err => {
701
- tdName.textContent = 'Unknown Delegate';
702
- });
703
- } else {
704
- tdName.textContent = 'No Address';
705
- }
706
- }
707
- }
708
-
709
- override async handleContextUpdated(): Promise<void> {
710
- const lc = `${this.lc}[${this.handleContextUpdated.name}]`;
711
- try {
712
- if (logalot) { console.log(`${lc} starting... (I: 243a88c1d4689c779bbbf2a4abd5da26)`); }
713
-
714
- // don't do anything when the context is updated?
715
-
716
- // is this hit? no. leave this as a no-op
717
- console.warn(`${lc} this component currently designed expecting that this does not hit (W: 95dc018b87027287e860de0826146826)`);
718
-
719
- await super.handleContextUpdated();
720
- } catch (error) {
721
- console.error(`${lc} ${extractErrorMsg(error)}`);
722
- throw error;
723
- } finally {
724
- if (logalot) { console.log(`${lc} complete.`); }
725
- }
726
- }
727
-
728
- protected override async handleNewContextChild({ childIbGib }: { childIbGib: IbGib_V1; }): Promise<void> {
729
- const lc = `${this.lc}[${this.handleNewContextChild.name}]`;
730
- try {
731
- if (logalot) { console.log(`${lc} starting... (I: b04dc63842f871658840233485659726)`); }
732
- // is this hit? no
733
- await super.handleNewContextChild({ childIbGib });
734
-
735
- console.warn(`${lc} this component currently designed expecting that this does not hit (W: 95dc018b87027287e860de0826146826)`);
736
- } catch (error) {
737
- console.error(`${lc} ${extractErrorMsg(error)}`);
738
- throw error;
739
- } finally {
740
- if (logalot) { console.log(`${lc} complete.`); }
741
- }
742
- }
743
-
744
- protected async handleSSOLink(providerId: 'google' | 'github') {
745
- const lc = `${this.lc}[${this.handleSSOLink.name}]`;
746
- this.setSSOStatus(`Linking ${providerId}...`, "info");
747
-
748
- try {
749
- // 1. Fetch public SSO configs from server
750
- const res = await fetch('/api/identity/sso/config');
751
- if (!res.ok) {
752
- throw new Error(`Failed to fetch SSO config from server: ${res.statusText}`);
753
- }
754
- const data = await res.json();
755
- if (!data.success || !data.providers) {
756
- throw new Error(`SSO configuration fetch failed: ${data.message || 'unknown error'}`);
757
- }
758
-
759
- const providerConfig = data.providers.find((p: any) => p.providerId === providerId);
760
- if (!providerConfig) {
761
- throw new Error(`SSO provider '${providerId}' is not configured on the server.`);
762
- }
763
-
764
- // 2. Generate a secure, high-entropy user nonce
765
- const userNonce = (Math.random().toString(36).substring(2) + Math.random().toString(36).substring(2)).substring(0, 32);
766
-
767
- // 3. Construct state payload
768
- const stateObj = {
769
- providerId,
770
- parentTjpAddr: this.ibGibAddr,
771
- userNonce
772
- };
773
- const state = btoa(JSON.stringify(stateObj));
774
-
775
- // 4. Construct Auth URL
776
- const authUrl = `${providerConfig.authUrl}?client_id=${providerConfig.clientId}&redirect_uri=${encodeURIComponent(providerConfig.redirectUri)}&response_type=code&scope=${encodeURIComponent(providerConfig.scope)}&state=${encodeURIComponent(state)}`;
777
-
778
- // 5. Open popup window
779
- const popup = window.open(
780
- authUrl,
781
- 'sso-oauth-popup',
782
- 'width=600,height=700,status=yes,toolbar=no,menubar=no,location=yes'
783
- );
784
- if (!popup) {
785
- throw new Error("Popup blocked by browser. Please enable popups for this site.");
786
- }
787
-
788
- // 6. Listen for callback from pop-up
789
- let checkClosedInterval: any;
790
- const messageListener = async (event: MessageEvent) => {
791
- if (event.origin !== window.location.origin) return;
792
- const callbackData = event.data;
793
- if (callbackData && callbackData.type === 'sso-oauth-callback') {
794
- window.removeEventListener('message', messageListener);
795
- clearInterval(checkClosedInterval);
796
- await this.handleSSOLinkCallback(callbackData.code, stateObj, providerConfig.redirectUri);
797
- }
798
- };
799
- window.addEventListener('message', messageListener);
800
-
801
- // 7. Poll for manual closure of the popup window
802
- checkClosedInterval = setInterval(() => {
803
- if (popup.closed) {
804
- clearInterval(checkClosedInterval);
805
- window.removeEventListener('message', messageListener);
806
- const statusEl = this.elements!.ssoStatusMsgEl;
807
- if (statusEl.textContent && statusEl.textContent.startsWith('Linking')) {
808
- this.setSSOStatus('Linking cancelled (window closed).', 'error');
809
- }
810
- }
811
- }, 500);
812
-
813
- } catch (error) {
814
- console.error(`${lc} SSO link initiation failed: ${extractErrorMsg(error)}`);
815
- this.setSSOStatus(`Link failed: ${extractErrorMsg(error)}`, "error");
816
- }
817
- }
818
-
819
- private setSSOStatus(msg: string, type: 'info' | 'success' | 'error') {
820
- const statusEl = this.elements!.ssoStatusMsgEl;
821
- statusEl.textContent = msg;
822
- statusEl.className = `status-msg ${type}`;
823
- statusEl.classList.remove('hidden');
824
- }
825
-
826
- protected async handleSSOLinkCallback(code: string, stateObj: any, redirectUri: string) {
827
- const lc = `${this.lc}[${this.handleSSOLinkCallback.name}]`;
828
- this.setSSOStatus("Retrieving challenge pool configuration from server...", "info");
829
-
830
- try {
831
- // 1. Exchange OAuth code for custodian public pool config
832
- const res = await fetch('/api/identity/sso/link', {
833
- method: 'POST',
834
- headers: { 'Content-Type': 'application/json' },
835
- body: JSON.stringify({
836
- code,
837
- providerId: stateObj.providerId,
838
- parentTjpAddr: stateObj.parentTjpAddr,
839
- userNonce: stateObj.userNonce,
840
- redirectUri
841
- })
842
- });
843
- if (!res.ok) {
844
- const errBody = await res.json().catch(() => ({}));
845
- throw new Error(`Server link exchange failed: ${errBody.message || res.statusText}`);
846
- }
847
-
848
- const linkData = await res.json();
849
- if (!linkData.success || !linkData.custodianPool) {
850
- throw new Error(`Failed to retrieve custodian challenge pool: ${linkData.message || 'unknown error'}`);
851
- }
852
-
853
- const custodianPool = linkData.custodianPool;
854
-
855
- // 2. Prompt user for master secret and verify it
856
- const keystoneService = new KeystoneService_V1();
857
- let masterSecret: string | undefined;
858
- let isCorrect = false;
859
- let tries = 0;
860
- const maxTries = 8;
861
-
862
- while (tries < maxTries && !isCorrect) {
863
- try {
864
- const promptMsg = tries === 0
865
- ? "Link successful! Enter your local Master Secret (Passphrase) to sign and register the custodian pool"
866
- : `Incorrect passphrase. Please try again (${maxTries - tries} tries remaining):`;
867
- masterSecret = await promptForSecret({
868
- msg: promptMsg,
869
- confirm: false
870
- });
871
- } catch (error) {
872
- // cancelled
873
- if (logalot) { console.log(`${lc}[promptForSecret] user cancelled (I: 911ff1fb0234949228481968e9087626)`); }
874
- masterSecret = undefined;
875
- break;
876
- }
877
-
878
- if (!masterSecret) {
879
- break;
880
- }
881
-
882
- // Verify secret using verifySigningSecret (requires manage poolId)
883
- isCorrect = await keystoneService.verifySigningSecret({
884
- keystoneIbGib: this.ibGib!,
885
- signingSecret: masterSecret,
886
- poolId: 'manage'
887
- });
888
-
889
- if (!isCorrect) {
890
- tries++;
891
- }
892
- }
893
-
894
- if (!isCorrect) {
895
- this.setSSOStatus("Link cancelled or failed (incorrect passphrase).", "error");
896
- return;
897
- }
898
-
899
- this.setSSOStatus("Evolving identity locally and generating cryptographic signatures...", "info");
900
-
901
- const metaspace = getIbGibGlobalThis_SpaceGib(APP_CONFIG).metaspace;
902
- if (!metaspace) throw new Error("Metaspace not found");
903
- const space = await metaspace.getLocalUserSpace({ lock: false });
904
- if (!space) { throw new Error("Local space not found"); }
905
-
906
- // 3. Evolve parent keystone to include the custodian pool
907
- const evolvedParent = await keystoneService.addPools({
908
- latestKeystone: this.ibGib!,
909
- masterSecret: masterSecret!,
910
- newPools: [custodianPool],
911
- metaspace,
912
- space
913
- });
914
-
915
- // 4. Sync evolved parent keystone back to server
916
- this.setSSOStatus("Publishing evolved identity keystone to server...", "info");
917
- const syncRes = await spaceGibApiBridge.putEvolveKeystone({
918
- domainAddr: stateObj.parentTjpAddr,
919
- keystoneIbGib: evolvedParent,
920
- relatedKeystoneIbGibs: [] // No outward delegate timelines
921
- });
922
- if (!syncRes.success) {
923
- throw new Error(`Server synchronization rejected: ${syncRes.message}`);
924
- }
925
-
926
- // 5. Update index locally
927
- await updateSpecialIndex({
928
- type: "keystones",
929
- rel8nInfos: [
930
- {
931
- rel8nName: "keystone",
932
- ibGibs: [toDto({ ibGib: evolvedParent })]
933
- }
934
- ],
935
- metaspace,
936
- space
937
- });
938
-
939
- this.setSSOStatus(`Successfully linked with ${stateObj.providerId}!`, "success");
940
- await this.loadIbGib({ getLatest: false });
941
-
942
- } catch (error) {
943
- console.error(`${lc} SSO callback processing failed: ${extractErrorMsg(error)}`);
944
- this.setSSOStatus(`Link failed: ${extractErrorMsg(error)}`, "error");
945
- }
946
- }
947
-
948
- protected async handleSSOClick(providerId: 'google' | 'github') {
949
- const isLinked = isSsoProviderLinked({ keystone: this.ibGib, providerId });
950
- if (isLinked) {
951
- await this.handleSSOUnlink(providerId);
952
- } else {
953
- await this.handleSSOLink(providerId);
954
- }
955
- }
956
-
957
- protected async handleSSOUnlink(providerId: 'google' | 'github') {
958
- const lc = `${this.lc}[${this.handleSSOUnlink.name}]`;
959
- this.setSSOStatus(`Unlinking ${providerId}...`, "info");
960
-
961
- try {
962
- // 1. Prompt user for passphrase
963
- const keystoneService = new KeystoneService_V1();
964
- let masterSecret: string | undefined;
965
- let isCorrect = false;
966
- let tries = 0;
967
- const maxTries = 8;
968
-
969
- while (tries < maxTries && !isCorrect) {
970
- try {
971
- const promptMsg = tries === 0
972
- ? `Are you sure you want to unlink your ${providerId} account? If so, enter your local Master Secret (Passphrase) to authorize. Otherwise, click Cancel.`
973
- : `Incorrect passphrase. Please try again (${maxTries - tries} tries remaining):`;
974
- masterSecret = await promptForSecret({
975
- msg: promptMsg,
976
- confirm: false
977
- });
978
- } catch (error) {
979
- masterSecret = undefined;
980
- break;
981
- }
982
-
983
- if (!masterSecret) {
984
- break;
985
- }
986
-
987
- // Verify secret using verifySigningSecret (requires manage poolId)
988
- isCorrect = await keystoneService.verifySigningSecret({
989
- keystoneIbGib: this.ibGib!,
990
- signingSecret: masterSecret,
991
- poolId: 'manage'
992
- });
993
-
994
- if (!isCorrect) {
995
- tries++;
996
- }
997
- }
998
-
999
- if (!isCorrect) {
1000
- this.setSSOStatus("Unlink cancelled or failed (incorrect passphrase).", "error");
1001
- return;
1002
- }
1003
-
1004
- this.setSSOStatus("Evolving identity locally and removing custodian pool...", "info");
1005
-
1006
- const metaspace = getIbGibGlobalThis_SpaceGib(APP_CONFIG).metaspace;
1007
- if (!metaspace) throw new Error("Metaspace not found");
1008
- const space = await metaspace.getLocalUserSpace({ lock: false });
1009
- if (!space) { throw new Error("Local space not found"); }
1010
-
1011
- // 2. Evolve parent keystone to remove the custodian pool
1012
- const targetPoolId = `custodian-manage-${providerId}`;
1013
- const evolvedParent = await keystoneService.removePools({
1014
- latestKeystone: this.ibGib!,
1015
- masterSecret: masterSecret!,
1016
- poolIds: [targetPoolId],
1017
- metaspace,
1018
- space
1019
- });
1020
-
1021
- // 3. Sync evolved parent keystone back to server
1022
- this.setSSOStatus("Publishing evolved identity keystone to server...", "info");
1023
- const syncRes = await spaceGibApiBridge.putEvolveKeystone({
1024
- domainAddr: getIbGibAddr({ ibGib: this.ibGib! }),
1025
- keystoneIbGib: evolvedParent,
1026
- relatedKeystoneIbGibs: [] // No outward delegate timelines
1027
- });
1028
- if (!syncRes.success) {
1029
- throw new Error(`Server synchronization rejected: ${syncRes.message}`);
1030
- }
1031
-
1032
- // 4. Update index locally
1033
- await updateSpecialIndex({
1034
- type: "keystones",
1035
- rel8nInfos: [
1036
- {
1037
- rel8nName: "keystone",
1038
- ibGibs: [toDto({ ibGib: evolvedParent })]
1039
- }
1040
- ],
1041
- metaspace,
1042
- space
1043
- });
1044
-
1045
- this.setSSOStatus(`Successfully unlinked ${providerId}!`, "success");
1046
- await this.loadIbGib({ getLatest: true });
1047
-
1048
- } catch (error) {
1049
- console.error(`${lc} SSO unlink failed: ${extractErrorMsg(error)}`);
1050
- this.setSSOStatus(`Unlink failed: ${extractErrorMsg(error)}`, "error");
1051
- }
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
- }
1213
- }