@ibgib/web-gib 0.0.57 → 0.0.59
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/AUTO-GENERATED-version.d.mts +1 -1
- package/dist/AUTO-GENERATED-version.mjs +1 -1
- package/dist/api/ibgib-api-bridge.d.mts +63 -1
- package/dist/api/ibgib-api-bridge.d.mts.map +1 -1
- package/dist/api/ibgib-api-bridge.mjs +135 -0
- package/dist/api/ibgib-api-bridge.mjs.map +1 -1
- package/dist/identity/custodian-delegate-helper.d.mts +22 -0
- package/dist/identity/custodian-delegate-helper.d.mts.map +1 -0
- package/dist/identity/custodian-delegate-helper.mjs +50 -0
- package/dist/identity/custodian-delegate-helper.mjs.map +1 -0
- package/dist/identity/sso-popup-helper.d.mts +33 -0
- package/dist/identity/sso-popup-helper.d.mts.map +1 -0
- package/dist/identity/sso-popup-helper.mjs +124 -0
- package/dist/identity/sso-popup-helper.mjs.map +1 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +3 -0
- package/dist/index.mjs.map +1 -1
- package/dist/ui/component/identity/add-device/add-device.d.mts.map +1 -1
- package/dist/ui/component/identity/add-device/add-device.mjs +5 -1
- package/dist/ui/component/identity/add-device/add-device.mjs.map +1 -1
- package/dist/ui/component/identity/identity-header/identity-header.d.mts.map +1 -1
- package/dist/ui/component/identity/identity-header/identity-header.mjs +2 -1
- package/dist/ui/component/identity/identity-header/identity-header.mjs.map +1 -1
- package/dist/ui/component/identity/keystone-creator/keystone-creator.css +109 -23
- package/dist/ui/component/identity/keystone-creator/keystone-creator.d.mts +22 -0
- package/dist/ui/component/identity/keystone-creator/keystone-creator.d.mts.map +1 -1
- package/dist/ui/component/identity/keystone-creator/keystone-creator.html +122 -60
- package/dist/ui/component/identity/keystone-creator/keystone-creator.mjs +445 -73
- package/dist/ui/component/identity/keystone-creator/keystone-creator.mjs.map +1 -1
- package/dist/ui/component/identity/keystone-details/keystone-details.d.mts.map +1 -1
- package/dist/ui/component/identity/keystone-details/keystone-details.mjs +10 -53
- package/dist/ui/component/identity/keystone-details/keystone-details.mjs.map +1 -1
- package/package.json +4 -4
- package/src/AUTO-GENERATED-version.mts +1 -1
- package/src/api/ibgib-api-bridge.mts +195 -1
- package/src/identity/custodian-delegate-helper.mts +61 -0
- package/src/identity/sso-popup-helper.mts +155 -0
- package/src/index.mts +3 -0
- package/src/ui/component/identity/add-device/add-device.mts +5 -1
- package/src/ui/component/identity/identity-header/identity-header.mts +2 -1
- package/src/ui/component/identity/keystone-creator/keystone-creator.css +109 -23
- package/src/ui/component/identity/keystone-creator/keystone-creator.html +122 -60
- package/src/ui/component/identity/keystone-creator/keystone-creator.mts +507 -77
- package/src/ui/component/identity/keystone-details/keystone-details.mts +10 -62
- package/tools/auto-generated-agent-skills/skills/ibgib-implementation-plan/SKILL.md +3 -0
|
@@ -11,6 +11,7 @@ import { KeystoneService_V1 } from "@ibgib/core-gib/dist/keystone/keystone-servi
|
|
|
11
11
|
import { KeystoneProfileBuilder } from "@ibgib/core-gib/dist/keystone/policy/keystone-profile-builder.mjs";
|
|
12
12
|
import { updateSpecialIndex, } from "@ibgib/core-gib/dist/timeline/timeline-api.mjs";
|
|
13
13
|
import { toDto, getTjpAddr } from "@ibgib/core-gib/dist/common/other/ibgib-helper.mjs";
|
|
14
|
+
import { FlatIbGibGraph } from "@ibgib/core-gib/dist/common/other/graph-types.mjs";
|
|
14
15
|
import { validateEmail } from "@ibgib/core-gib/dist/keystone/keystone-helpers.mjs";
|
|
15
16
|
import { KEYSTONE_USERNAME_REGEXP, KEYSTONE_DESCRIPTION_REGEXP, KEYSTONE_VERB_SYNC, KEYSTONE_VERB_CONNECT } from "@ibgib/core-gib/dist/keystone/keystone-constants.mjs";
|
|
16
17
|
import { deriveDelegateSecret } from "@ibgib/core-gib/dist/keystone/strategy/hash-reveal-v1/hash-reveal-v1.mjs";
|
|
@@ -23,8 +24,10 @@ import {
|
|
|
23
24
|
} from "../../component-types.mjs";
|
|
24
25
|
import { getComponentCtorArg } from "../../../../app-bootstrap/init-orchestration.mjs";
|
|
25
26
|
import { getApiBridge } from "../../../../api/ibgib-api-bridge.mjs";
|
|
27
|
+
import { deriveLocalCustodianDelegateSecret } from "../../../../identity/custodian-delegate-helper.mjs";
|
|
28
|
+
import { executeSsoOAuthPopup } from "../../../../identity/sso-popup-helper.mjs";
|
|
26
29
|
import { EVENT_IBGIB_IDENTITY_REQUEST_CHANGE, EVENT_IBGIB_UI_MESSAGE } from "../../../ui-constants.mjs";
|
|
27
|
-
import { shadowRoot_getElementById } from "../../../../helpers.web.mjs";
|
|
30
|
+
import { alertUser, shadowRoot_getElementById } from "../../../../helpers.web.mjs";
|
|
28
31
|
|
|
29
32
|
export const KEYSTONE_CREATOR_COMPONENT_NAME = 'ibgib-keystone-creator';
|
|
30
33
|
|
|
@@ -164,6 +167,59 @@ export class KeystoneCreatorComponentInstance
|
|
|
164
167
|
}
|
|
165
168
|
|
|
166
169
|
private initHandlers() {
|
|
170
|
+
// Mode Tab Switching (Quick vs Password-based)
|
|
171
|
+
const tabQuick = this.shadowRoot?.getElementById('tab-quick') as HTMLButtonElement | null;
|
|
172
|
+
const tabPassword = this.shadowRoot?.getElementById('tab-password') as HTMLButtonElement | null;
|
|
173
|
+
const panelQuick = this.shadowRoot?.getElementById('panel-quick') as HTMLDivElement | null;
|
|
174
|
+
const panelPassword = this.shadowRoot?.getElementById('panel-password') as HTMLDivElement | null;
|
|
175
|
+
const subtitleQuick = this.shadowRoot?.getElementById('subtitle-quick') as HTMLParagraphElement | null;
|
|
176
|
+
const subtitlePassword = this.shadowRoot?.getElementById('subtitle-password') as HTMLParagraphElement | null;
|
|
177
|
+
|
|
178
|
+
tabQuick?.addEventListener('click', () => {
|
|
179
|
+
tabQuick.classList.add('active');
|
|
180
|
+
tabPassword?.classList.remove('active');
|
|
181
|
+
panelQuick?.classList.remove('hidden');
|
|
182
|
+
panelPassword?.classList.add('hidden');
|
|
183
|
+
subtitleQuick?.classList.remove('hidden');
|
|
184
|
+
subtitlePassword?.classList.add('hidden');
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
tabPassword?.addEventListener('click', () => {
|
|
188
|
+
tabPassword.classList.add('active');
|
|
189
|
+
tabQuick?.classList.remove('active');
|
|
190
|
+
panelPassword?.classList.remove('hidden');
|
|
191
|
+
panelQuick?.classList.add('hidden');
|
|
192
|
+
subtitlePassword?.classList.remove('hidden');
|
|
193
|
+
subtitleQuick?.classList.add('hidden');
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
const btnGoogleSso = this.shadowRoot?.getElementById('btn-google-sso') as HTMLButtonElement | null;
|
|
197
|
+
btnGoogleSso?.addEventListener('click', () => this.handleSsoClick('google'));
|
|
198
|
+
|
|
199
|
+
const btnGithubSso = this.shadowRoot?.getElementById('btn-github-sso') as HTMLButtonElement | null;
|
|
200
|
+
btnGithubSso?.addEventListener('click', () => this.handleSsoClick('github'));
|
|
201
|
+
|
|
202
|
+
// Email Sign-In Info Button
|
|
203
|
+
const btnEmailInfo = this.shadowRoot?.getElementById('btn-email-info') as HTMLButtonElement | null;
|
|
204
|
+
btnEmailInfo?.addEventListener('click', (e) => {
|
|
205
|
+
e.preventDefault();
|
|
206
|
+
alertUser({
|
|
207
|
+
title: 'Custom Email Sign-In',
|
|
208
|
+
msg: 'When you enter your email address, we dispatch a 6-digit one-time verification code to your inbox.\n\nEntering the code creates your cryptographic identity delegate on your device and signs you in seamlessly without needing a password!'
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
// Quick Email Verification Code Handlers
|
|
213
|
+
const btnQuickSendCode = this.shadowRoot?.getElementById('btn-quick-send-code') as HTMLButtonElement | null;
|
|
214
|
+
const inputQuickEmail = this.shadowRoot?.getElementById('input-quick-email') as HTMLInputElement | null;
|
|
215
|
+
const secQuickOtp = this.shadowRoot?.getElementById('section-quick-otp') as HTMLDivElement | null;
|
|
216
|
+
const inputQuickOtp = this.shadowRoot?.getElementById('input-quick-otp') as HTMLInputElement | null;
|
|
217
|
+
const btnQuickVerifyCode = this.shadowRoot?.getElementById('btn-quick-verify-code') as HTMLButtonElement | null;
|
|
218
|
+
|
|
219
|
+
btnQuickSendCode?.addEventListener('click', () => this.handleQuickSendCode());
|
|
220
|
+
|
|
221
|
+
btnQuickVerifyCode?.addEventListener('click', () => this.handleQuickVerifyCode());
|
|
222
|
+
|
|
167
223
|
this.elements!.btnGenerateEl.addEventListener('click', () => this.handleGenerate());
|
|
168
224
|
|
|
169
225
|
this.shadowRoot!.getElementById('btn-switch-sign-in')?.addEventListener('click', (e) => {
|
|
@@ -176,16 +232,16 @@ export class KeystoneCreatorComponentInstance
|
|
|
176
232
|
});
|
|
177
233
|
|
|
178
234
|
// Clear invalid styling when user inputs text
|
|
179
|
-
this.elements!.inputUsername
|
|
235
|
+
this.elements!.inputUsername?.addEventListener('input', () => {
|
|
180
236
|
this.elements!.inputUsername.classList.remove('invalid');
|
|
181
237
|
});
|
|
182
|
-
this.elements!.inputDescription
|
|
238
|
+
this.elements!.inputDescription?.addEventListener('input', () => {
|
|
183
239
|
this.elements!.inputDescription.classList.remove('invalid');
|
|
184
240
|
});
|
|
185
|
-
this.elements!.inputEmail
|
|
241
|
+
this.elements!.inputEmail?.addEventListener('input', () => {
|
|
186
242
|
this.elements!.inputEmail.classList.remove('invalid');
|
|
187
243
|
});
|
|
188
|
-
this.elements!.inputSecret
|
|
244
|
+
this.elements!.inputSecret?.addEventListener('input', () => {
|
|
189
245
|
this.elements!.inputSecret.classList.remove('invalid');
|
|
190
246
|
});
|
|
191
247
|
}
|
|
@@ -198,6 +254,292 @@ export class KeystoneCreatorComponentInstance
|
|
|
198
254
|
}
|
|
199
255
|
}
|
|
200
256
|
|
|
257
|
+
/**
|
|
258
|
+
* Triggered by Google / GitHub SSO buttons.
|
|
259
|
+
* Opens OAuth popup, exchanges authorization code, builds local delegate keystone,
|
|
260
|
+
* registers delegate on server, and finalizes onboarding.
|
|
261
|
+
*/
|
|
262
|
+
protected async handleSsoClick(providerId: 'google' | 'github'): Promise<void> {
|
|
263
|
+
const lc = `${this.lc}[${this.handleSsoClick.name}]`;
|
|
264
|
+
const providerName = providerId === 'google' ? 'Google' : providerId === 'github' ? 'GitHub' : providerId;
|
|
265
|
+
|
|
266
|
+
this.setStatus(`Opening ${providerName} SSO authorization window...`, 'info');
|
|
267
|
+
this.showBusy({
|
|
268
|
+
isBusy: true,
|
|
269
|
+
title: `Connecting to ${providerName}...`,
|
|
270
|
+
msg: `Opening ${providerName} SSO authorization window...\nPlease complete authorization in the popup window.`,
|
|
271
|
+
animationEmoji: '🌐'
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
try {
|
|
275
|
+
const resPopup = await executeSsoOAuthPopup({ providerId });
|
|
276
|
+
if (!resPopup.success || !resPopup.code) {
|
|
277
|
+
this.setStatus(resPopup.message || `${providerName} sign-in cancelled.`, 'error');
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
this.setStatus(`Exchanging ${providerName} authorization code...`, 'info');
|
|
282
|
+
this.showBusy({
|
|
283
|
+
isBusy: true,
|
|
284
|
+
title: 'Exchanging SSO Code...',
|
|
285
|
+
msg: `Validating ${providerName} authorization code with custodian server...`,
|
|
286
|
+
animationEmoji: '🔐'
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
const apiBridge = getApiBridge();
|
|
290
|
+
const resLogin = await apiBridge.postSsoLogin({
|
|
291
|
+
code: resPopup.code,
|
|
292
|
+
providerId,
|
|
293
|
+
redirectUri: resPopup.redirectUri!
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
if (!resLogin.success || !resLogin.primaryTjpAddr || !resLogin.custodianReqs) {
|
|
297
|
+
this.setStatus(resLogin.message || `${providerName} login failed.`, 'error');
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const { primaryTjpAddr, custodianReqs } = resLogin;
|
|
302
|
+
this.setStatus('Deriving local device delegate keys...', 'info');
|
|
303
|
+
this.showBusy({
|
|
304
|
+
isBusy: true,
|
|
305
|
+
title: 'Creating Sync Delegate...',
|
|
306
|
+
msg: 'Generating cryptographic sync delegate keys...',
|
|
307
|
+
animationEmoji: '🔑'
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
const delegateSecret = await deriveLocalCustodianDelegateSecret({ primaryTjpAddr });
|
|
311
|
+
const email = resLogin.userInfo?.email || '';
|
|
312
|
+
const username = email ? email.split('@')[0] : 'user';
|
|
313
|
+
const custodianSyncProfileName = process.env.KEYSTONE_PROFILE_CUSTODIAN_SYNC;
|
|
314
|
+
if (!custodianSyncProfileName || !custodianSyncProfileName.trim()) {
|
|
315
|
+
throw new Error('KEYSTONE_PROFILE_CUSTODIAN_SYNC environment variable is missing. Expected valid profile name like "sync-profile.dev" or "sync-profile.prod". (E: 4d5e6f7890123456789abcdef4)');
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const delegateBuilder = KeystoneProfileBuilder.buildKeystone(custodianSyncProfileName.trim() as any)
|
|
319
|
+
.withUsername(`${username}-sync`)
|
|
320
|
+
.withDescription(`Sync Delegate for ${username}`)
|
|
321
|
+
.withDetails({
|
|
322
|
+
client: 'space-gib-web',
|
|
323
|
+
role: 'sync-delegate',
|
|
324
|
+
primaryTjpAddr,
|
|
325
|
+
custodianReqs,
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
const configs = await delegateBuilder.compileConfigs();
|
|
329
|
+
const metaspace = await getGlobalMetaspace_waitIfNeeded();
|
|
330
|
+
const space = await metaspace.getLocalUserSpace({ lock: false });
|
|
331
|
+
if (!space) {
|
|
332
|
+
this.setStatus('Could not access local user space.', 'error');
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const keystoneService = new KeystoneService_V1();
|
|
337
|
+
const delegateKeystone = await keystoneService.genesis({
|
|
338
|
+
masterSecret: delegateSecret,
|
|
339
|
+
configs,
|
|
340
|
+
metaspace,
|
|
341
|
+
space,
|
|
342
|
+
frameDetails: delegateBuilder.getFrameDetails(),
|
|
343
|
+
isPrimary: false
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
this.setStatus('Registering device delegate with custodian server...', 'info');
|
|
347
|
+
this.showBusy({
|
|
348
|
+
isBusy: true,
|
|
349
|
+
title: 'Registering Delegate...',
|
|
350
|
+
msg: 'Registering local delegate with custodian server...',
|
|
351
|
+
animationEmoji: '📝'
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
const resRegister = await apiBridge.postCustodianRegisterDelegate({ delegateKeystone });
|
|
355
|
+
if (!resRegister.success) {
|
|
356
|
+
this.setStatus(resRegister.message || 'Custodian delegate registration failed.', 'error');
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Finalize onboarding: update keystones index, activate identity, and navigate to identity manager
|
|
361
|
+
await this.finalizeIdentityOnboarding({
|
|
362
|
+
primaryKeystone: resRegister.evolvedPrimaryKeystone || resLogin.primaryKeystone,
|
|
363
|
+
primaryGraph: resRegister.primaryGraph || resLogin.primaryGraph,
|
|
364
|
+
delegateKeystone,
|
|
365
|
+
primaryTjpAddr,
|
|
366
|
+
delegateSecret,
|
|
367
|
+
makeActive: true
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
} catch (error) {
|
|
371
|
+
const emsg = extractErrorMsg(error);
|
|
372
|
+
console.error(`${lc} ${emsg}`, error);
|
|
373
|
+
this.setStatus(`SSO sign-in error: ${emsg}`, 'error');
|
|
374
|
+
} finally {
|
|
375
|
+
this.showBusy({ isBusy: false });
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Triggered by the "Send 6-Digit Verification Code" button.
|
|
381
|
+
* Validates quick email input and dispatches a 6-digit OTP code to the user's inbox.
|
|
382
|
+
*/
|
|
383
|
+
protected async handleQuickSendCode(): Promise<void> {
|
|
384
|
+
const lc = `${this.lc}[${this.handleQuickSendCode.name}]`;
|
|
385
|
+
const btnQuickSendCode = this.shadowRoot?.getElementById('btn-quick-send-code') as HTMLButtonElement | null;
|
|
386
|
+
const inputQuickEmail = this.shadowRoot?.getElementById('input-quick-email') as HTMLInputElement | null;
|
|
387
|
+
const secQuickOtp = this.shadowRoot?.getElementById('section-quick-otp') as HTMLDivElement | null;
|
|
388
|
+
|
|
389
|
+
const email = (inputQuickEmail?.value || '').trim();
|
|
390
|
+
if (!email || !validateEmail(email)) {
|
|
391
|
+
this.setStatus("Please enter a valid email address.", "error");
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (btnQuickSendCode) {
|
|
396
|
+
btnQuickSendCode.disabled = true;
|
|
397
|
+
btnQuickSendCode.textContent = "Sending Verification Code...";
|
|
398
|
+
}
|
|
399
|
+
this.setStatus("Requesting verification code...", "info");
|
|
400
|
+
|
|
401
|
+
try {
|
|
402
|
+
const resCode = await getApiBridge().postVerificationCode({ email });
|
|
403
|
+
if (resCode.success) {
|
|
404
|
+
secQuickOtp?.classList.remove('hidden');
|
|
405
|
+
this.setStatus("✉️ 6-digit verification code sent to your email!", "success");
|
|
406
|
+
} else {
|
|
407
|
+
this.setStatus(resCode.message || "Failed to dispatch verification code.", "error");
|
|
408
|
+
}
|
|
409
|
+
} catch (error) {
|
|
410
|
+
const emsg = extractErrorMsg(error);
|
|
411
|
+
console.error(`${lc} ${emsg}`);
|
|
412
|
+
this.setStatus(`Failed to dispatch verification code: ${emsg}`, "error");
|
|
413
|
+
} finally {
|
|
414
|
+
if (btnQuickSendCode) {
|
|
415
|
+
btnQuickSendCode.disabled = false;
|
|
416
|
+
btnQuickSendCode.textContent = "Send 6-Digit Verification Code";
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Triggered by the "Verify Code" button.
|
|
423
|
+
* Submits 6-digit OTP code to custodian server, builds local sync delegate keystone,
|
|
424
|
+
* registers delegate on server, and finalizes onboarding.
|
|
425
|
+
*/
|
|
426
|
+
protected async handleQuickVerifyCode(): Promise<void> {
|
|
427
|
+
const lc = `${this.lc}[${this.handleQuickVerifyCode.name}]`;
|
|
428
|
+
const inputQuickEmail = this.shadowRoot?.getElementById('input-quick-email') as HTMLInputElement | null;
|
|
429
|
+
const inputQuickOtp = this.shadowRoot?.getElementById('input-quick-otp') as HTMLInputElement | null;
|
|
430
|
+
const btnQuickVerifyCode = this.shadowRoot?.getElementById('btn-quick-verify-code') as HTMLButtonElement | null;
|
|
431
|
+
|
|
432
|
+
const email = (inputQuickEmail?.value || '').trim();
|
|
433
|
+
const code = (inputQuickOtp?.value || '').trim();
|
|
434
|
+
if (!code || code.length !== 6 || !/^\d{6}$/.test(code)) {
|
|
435
|
+
this.setStatus("Please enter a valid 6-digit verification code.", "error");
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (btnQuickVerifyCode) {
|
|
440
|
+
btnQuickVerifyCode.disabled = true;
|
|
441
|
+
btnQuickVerifyCode.textContent = "Verifying...";
|
|
442
|
+
}
|
|
443
|
+
this.setStatus("Verifying code & completing onboarding...", "info");
|
|
444
|
+
this.showBusy({
|
|
445
|
+
isBusy: true,
|
|
446
|
+
title: 'Verifying Code...',
|
|
447
|
+
msg: 'Great! Juust a moment...\n(Validating code, promoting server domain space, other security stuff...)',
|
|
448
|
+
animationEmoji: '🔐'
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
try {
|
|
452
|
+
const resGenesis = await getApiBridge().postCustodianGenesis({ email, code });
|
|
453
|
+
if (!resGenesis.success || !resGenesis.primaryTjpAddr || !resGenesis.custodianReqs) {
|
|
454
|
+
this.setStatus(resGenesis.message || "Custodian verification failed.", "error");
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const primaryTjpAddr = resGenesis.primaryTjpAddr;
|
|
459
|
+
const custodianReqs = resGenesis.custodianReqs;
|
|
460
|
+
|
|
461
|
+
// Construct local delegate keystone
|
|
462
|
+
this.setStatus("Constructing local sync delegate...", "info");
|
|
463
|
+
this.showBusy({
|
|
464
|
+
isBusy: true,
|
|
465
|
+
title: 'Creating Sync Delegate...',
|
|
466
|
+
msg: 'Generating cryptographic sync delegate keys...',
|
|
467
|
+
animationEmoji: '🔑'
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
const delegateSecret = await deriveLocalCustodianDelegateSecret({ primaryTjpAddr });
|
|
471
|
+
const username = email ? email.split('@')[0] : 'user';
|
|
472
|
+
const custodianSyncProfileName = process.env.KEYSTONE_PROFILE_CUSTODIAN_SYNC;
|
|
473
|
+
if (!custodianSyncProfileName || !custodianSyncProfileName.trim()) {
|
|
474
|
+
throw new Error('KEYSTONE_PROFILE_CUSTODIAN_SYNC environment variable is missing. Expected valid profile name like "sync-profile.dev" or "sync-profile.prod". (E: 4d5e6f7890123456789abcdef4)');
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const delegateBuilder = KeystoneProfileBuilder.buildKeystone(custodianSyncProfileName.trim() as any)
|
|
478
|
+
.withUsername(`${username}-sync`)
|
|
479
|
+
.withDescription(`Sync Delegate for ${username}`)
|
|
480
|
+
.withDetails({
|
|
481
|
+
client: 'space-gib-web',
|
|
482
|
+
role: 'sync-delegate',
|
|
483
|
+
primaryTjpAddr,
|
|
484
|
+
custodianReqs,
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
const configs = await delegateBuilder.compileConfigs();
|
|
488
|
+
const metaspace = await getGlobalMetaspace_waitIfNeeded();
|
|
489
|
+
const space = await metaspace.getLocalUserSpace({ lock: false });
|
|
490
|
+
if (!space) {
|
|
491
|
+
this.setStatus("Failed to access local user space.", "error");
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
const keystoneService = new KeystoneService_V1();
|
|
496
|
+
const delegateKeystone = await keystoneService.genesis({
|
|
497
|
+
masterSecret: delegateSecret,
|
|
498
|
+
configs,
|
|
499
|
+
metaspace,
|
|
500
|
+
space,
|
|
501
|
+
frameDetails: delegateBuilder.getFrameDetails(),
|
|
502
|
+
isPrimary: false
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
// Register delegate with server
|
|
506
|
+
this.setStatus("Registering device delegate with custodian server...", "info");
|
|
507
|
+
this.showBusy({
|
|
508
|
+
isBusy: true,
|
|
509
|
+
title: 'Registering Delegate...',
|
|
510
|
+
msg: 'Registering local delegate with custodian server...',
|
|
511
|
+
animationEmoji: '📝'
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
const resRegister = await getApiBridge().postCustodianRegisterDelegate({ delegateKeystone });
|
|
515
|
+
if (!resRegister.success) {
|
|
516
|
+
this.setStatus(resRegister.message || "Failed to register delegate with custodian server.", "error");
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Finalize onboarding: update keystones index, activate identity, and navigate to identity manager
|
|
521
|
+
await this.finalizeIdentityOnboarding({
|
|
522
|
+
primaryKeystone: resRegister.evolvedPrimaryKeystone || resGenesis.primaryKeystone,
|
|
523
|
+
primaryGraph: resRegister.primaryGraph || resGenesis.primaryGraph,
|
|
524
|
+
delegateKeystone,
|
|
525
|
+
primaryTjpAddr,
|
|
526
|
+
delegateSecret,
|
|
527
|
+
makeActive: true
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
} catch (error) {
|
|
531
|
+
const emsg = extractErrorMsg(error);
|
|
532
|
+
console.error(`${lc} ${emsg}`, error);
|
|
533
|
+
this.setStatus(`Error: ${emsg}`, "error");
|
|
534
|
+
} finally {
|
|
535
|
+
this.showBusy({ isBusy: false });
|
|
536
|
+
if (btnQuickVerifyCode) {
|
|
537
|
+
btnQuickVerifyCode.disabled = false;
|
|
538
|
+
btnQuickVerifyCode.textContent = "Verify Code";
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
201
543
|
/**
|
|
202
544
|
* Triggered by the "Generate" button.
|
|
203
545
|
* Orchestrates: Secret -> Local Genesis -> Graph Collection -> Server Sync.
|
|
@@ -282,13 +624,14 @@ export class KeystoneCreatorComponentInstance
|
|
|
282
624
|
animationEmoji: '⚙️'
|
|
283
625
|
});
|
|
284
626
|
|
|
285
|
-
// 1. Prepare Primary
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
627
|
+
// 1. Prepare Primary Keystone Configs
|
|
628
|
+
const sovereignPrimaryProfileName = process.env.KEYSTONE_PROFILE_SOVEREIGN_PRIMARY;
|
|
629
|
+
if (!sovereignPrimaryProfileName || !sovereignPrimaryProfileName.trim()) {
|
|
630
|
+
throw new Error('KEYSTONE_PROFILE_SOVEREIGN_PRIMARY environment variable is missing. Expected valid profile name like "primary-profile.dev" or "primary-profile.prod". (E: 3c4d5e6f7890123456789abcdef3)');
|
|
631
|
+
}
|
|
632
|
+
console.log(`${lc} Using sovereign primary keystone policy profile '${sovereignPrimaryProfileName}' (I: 90123456789abcdef0123456789abcde)`);
|
|
290
633
|
|
|
291
|
-
const builder = KeystoneProfileBuilder.buildKeystone(
|
|
634
|
+
const builder = KeystoneProfileBuilder.buildKeystone(sovereignPrimaryProfileName.trim() as any)
|
|
292
635
|
.withUsername(username)
|
|
293
636
|
.withDescription(description);
|
|
294
637
|
if (email) {
|
|
@@ -300,10 +643,13 @@ export class KeystoneCreatorComponentInstance
|
|
|
300
643
|
});
|
|
301
644
|
const configs = await builder.compileConfigs();
|
|
302
645
|
|
|
303
|
-
// 2.
|
|
646
|
+
// 2. Prepare Metaspace/Space
|
|
304
647
|
const metaspace = await getGlobalMetaspace_waitIfNeeded();
|
|
305
|
-
const space = await metaspace.getLocalUserSpace({});
|
|
306
|
-
if (!space) {
|
|
648
|
+
const space = await metaspace.getLocalUserSpace({ lock: false });
|
|
649
|
+
if (!space) {
|
|
650
|
+
this.setStatus("Failed to access local user space.", "error");
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
307
653
|
|
|
308
654
|
// 3. Perform Genesis (Local)
|
|
309
655
|
this.showBusy({
|
|
@@ -338,7 +684,12 @@ export class KeystoneCreatorComponentInstance
|
|
|
338
684
|
});
|
|
339
685
|
|
|
340
686
|
const delegateSecret = await deriveDelegateSecret({ masterSecret });
|
|
341
|
-
const
|
|
687
|
+
const sovereignSyncProfileName = process.env.KEYSTONE_PROFILE_SOVEREIGN_SYNC;
|
|
688
|
+
if (!sovereignSyncProfileName || !sovereignSyncProfileName.trim()) {
|
|
689
|
+
throw new Error('KEYSTONE_PROFILE_SOVEREIGN_SYNC environment variable is missing. Expected valid profile name like "sync-profile.dev" or "sync-profile.prod". (E: 5e6f7890123456789abcdef5)');
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
const delegateBuilder = KeystoneProfileBuilder.buildKeystone(sovereignSyncProfileName.trim() as any)
|
|
342
693
|
.withUsername(`${username}-sync`)
|
|
343
694
|
.withDescription(`Sync Delegate for ${username}`)
|
|
344
695
|
.withDetails({
|
|
@@ -385,7 +736,7 @@ export class KeystoneCreatorComponentInstance
|
|
|
385
736
|
this.showBusy({
|
|
386
737
|
isBusy: true,
|
|
387
738
|
title: 'Syncing to Server...',
|
|
388
|
-
msg: '
|
|
739
|
+
msg: 'Staging pending genesis keystone on server...',
|
|
389
740
|
animationEmoji: '🔄'
|
|
390
741
|
});
|
|
391
742
|
const resSync = await getApiBridge().postGenesisKeystone(keystoneIbGib);
|
|
@@ -510,69 +861,16 @@ export class KeystoneCreatorComponentInstance
|
|
|
510
861
|
}
|
|
511
862
|
}
|
|
512
863
|
|
|
513
|
-
// 5.
|
|
514
|
-
this.
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
await updateSpecialIndex({
|
|
522
|
-
type: "keystones",
|
|
523
|
-
rel8nInfos: [
|
|
524
|
-
{
|
|
525
|
-
rel8nName: "keystone",
|
|
526
|
-
ibGibs: [
|
|
527
|
-
toDto({ ibGib: evolvedParentKeystoneIbGib }),
|
|
528
|
-
toDto({ ibGib: delegateKeystoneIbGib })
|
|
529
|
-
],
|
|
530
|
-
},
|
|
531
|
-
],
|
|
532
|
-
metaspace,
|
|
533
|
-
space,
|
|
864
|
+
// 5. Finalize onboarding: update keystones index, activate identity, and navigate to identity manager
|
|
865
|
+
await this.finalizeIdentityOnboarding({
|
|
866
|
+
primaryKeystone: evolvedParentKeystoneIbGib,
|
|
867
|
+
delegateKeystone: delegateKeystoneIbGib,
|
|
868
|
+
primaryTjpAddr: evolvedParentAddr,
|
|
869
|
+
masterSecret,
|
|
870
|
+
delegateSecret,
|
|
871
|
+
makeActive: this.elements!.checkboxMakeActive?.checked ?? true
|
|
534
872
|
});
|
|
535
873
|
|
|
536
|
-
// Expose for dev tools
|
|
537
|
-
(window as any).dev_domainI = evolvedParentKeystoneIbGib;
|
|
538
|
-
(window as any).dev_domainIMasterSecret = masterSecret;
|
|
539
|
-
(window as any).dev_syncDelegate = delegateKeystoneIbGib;
|
|
540
|
-
(window as any).dev_syncDelegateSecret = delegateSecret;
|
|
541
|
-
|
|
542
|
-
// 6. Request to make the new identity active if the checkbox is checked
|
|
543
|
-
const makeActive = this.elements!.checkboxMakeActive?.checked;
|
|
544
|
-
if (makeActive) {
|
|
545
|
-
window.dispatchEvent(new CustomEvent(EVENT_IBGIB_IDENTITY_REQUEST_CHANGE, {
|
|
546
|
-
detail: { activeIdentityAddr: evolvedParentAddr },
|
|
547
|
-
bubbles: true,
|
|
548
|
-
composed: true
|
|
549
|
-
}));
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
// 7. Success
|
|
553
|
-
const successMsg = "Identity successfully created and activated! Just a sec...";
|
|
554
|
-
this.showBusy({
|
|
555
|
-
isBusy: true,
|
|
556
|
-
title: 'Success!',
|
|
557
|
-
msg: successMsg,
|
|
558
|
-
animationEmoji: '✅'
|
|
559
|
-
});
|
|
560
|
-
await delay(humanEyeDelay); // for human eyes, not needed for functionality, because we'll redirect
|
|
561
|
-
await delay(humanEyeDelay); // for human eyes, not needed for functionality, because we'll redirect
|
|
562
|
-
await delay(humanEyeDelay); // for human eyes, not needed for functionality, because we'll redirect
|
|
563
|
-
await delay(humanEyeDelay); // for human eyes, not needed for functionality, because we'll redirect
|
|
564
|
-
this.elements!.keystoneAddrEl.textContent = evolvedParentAddr;
|
|
565
|
-
this.elements!.statusArea.classList.remove('hidden');
|
|
566
|
-
this.setStatus(successMsg, "success");
|
|
567
|
-
console.log(`${lc} ✓ ${successMsg} (I: 3396d8261978d7027895b71e27e9ab26)`);
|
|
568
|
-
|
|
569
|
-
// Dispatch UI message to close keystone-creator and show identity-manager
|
|
570
|
-
window.dispatchEvent(new CustomEvent(EVENT_IBGIB_UI_MESSAGE, {
|
|
571
|
-
detail: { action: 'show-identity-manager', addr: evolvedParentAddr },
|
|
572
|
-
bubbles: true,
|
|
573
|
-
composed: true
|
|
574
|
-
}));
|
|
575
|
-
|
|
576
874
|
} catch (error) {
|
|
577
875
|
const emsg = extractErrorMsg(error);
|
|
578
876
|
console.error(`${lc} ${emsg}`, error);
|
|
@@ -586,6 +884,138 @@ export class KeystoneCreatorComponentInstance
|
|
|
586
884
|
}
|
|
587
885
|
}
|
|
588
886
|
|
|
887
|
+
/**
|
|
888
|
+
* DRY helper to register keystones in the local keystones special index,
|
|
889
|
+
* set the active identity, and navigate to the Identity Manager component.
|
|
890
|
+
*/
|
|
891
|
+
private async finalizeIdentityOnboarding({
|
|
892
|
+
primaryKeystone,
|
|
893
|
+
primaryGraph,
|
|
894
|
+
delegateKeystone,
|
|
895
|
+
primaryTjpAddr,
|
|
896
|
+
masterSecret,
|
|
897
|
+
delegateSecret,
|
|
898
|
+
makeActive = true,
|
|
899
|
+
}: {
|
|
900
|
+
primaryKeystone?: IbGib_V1;
|
|
901
|
+
primaryGraph?: FlatIbGibGraph;
|
|
902
|
+
delegateKeystone: IbGib_V1;
|
|
903
|
+
primaryTjpAddr: string;
|
|
904
|
+
masterSecret?: string;
|
|
905
|
+
delegateSecret?: string;
|
|
906
|
+
makeActive?: boolean;
|
|
907
|
+
}): Promise<void> {
|
|
908
|
+
const lc = `${this.lc}[${this.finalizeIdentityOnboarding.name}]`;
|
|
909
|
+
const humanEyeDelay = 500;
|
|
910
|
+
|
|
911
|
+
const metaspace = await getGlobalMetaspace_waitIfNeeded();
|
|
912
|
+
if (!metaspace) {
|
|
913
|
+
throw new Error(`(UNEXPECTED) metaspace falsy? Expected active global metaspace instance. (E: 2e37a8b6fe987449c5d8693a7d4a2827)`);
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
const space = await metaspace.getLocalUserSpace({});
|
|
917
|
+
if (!space) {
|
|
918
|
+
throw new Error(`(UNEXPECTED) space falsy? Expected valid default local user space from metaspace. (E: 1e37a8b6fe987449c5d8693a7d4a2826)`);
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
// 0. Persist all frames of primary identity graph into local browser space
|
|
922
|
+
if (primaryGraph) {
|
|
923
|
+
const graphIbGibs = Object.values(primaryGraph) as IbGib_V1[];
|
|
924
|
+
for (const frame of graphIbGibs) {
|
|
925
|
+
await metaspace.put({ ibGibs: [frame], space });
|
|
926
|
+
await metaspace.registerNewIbGib({ ibGib: frame, space });
|
|
927
|
+
}
|
|
928
|
+
} else if (primaryKeystone) {
|
|
929
|
+
await metaspace.put({ ibGibs: [primaryKeystone], space });
|
|
930
|
+
await metaspace.registerNewIbGib({ ibGib: primaryKeystone, space });
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
// 0.5 Verify primary keystone exists in local browser space via getLatestKeystone
|
|
934
|
+
const keystoneService = new KeystoneService_V1();
|
|
935
|
+
try {
|
|
936
|
+
await keystoneService.getLatestKeystone({
|
|
937
|
+
addr: primaryTjpAddr,
|
|
938
|
+
metaspace,
|
|
939
|
+
space
|
|
940
|
+
});
|
|
941
|
+
} catch (error) {
|
|
942
|
+
const emsg = `Primary identity keystone ${primaryTjpAddr} not found in local browser space! Error: ${extractErrorMsg(error)}`;
|
|
943
|
+
console.error(`${lc} ${emsg} (E: a123456789abcdef0123456789c0001)`);
|
|
944
|
+
throw new Error(emsg);
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
// 1. Register with the local keystones special index
|
|
948
|
+
this.showBusy({
|
|
949
|
+
isBusy: true,
|
|
950
|
+
title: 'Registering Identity...',
|
|
951
|
+
msg: 'Updating local identity indexes and scope settings...',
|
|
952
|
+
animationEmoji: '💾'
|
|
953
|
+
});
|
|
954
|
+
const ibGibsToRegister: IbGib_V1[] = [toDto({ ibGib: delegateKeystone })];
|
|
955
|
+
|
|
956
|
+
if (!primaryKeystone && !primaryGraph) { throw new Error(`(UNEXPECTED) primaryKeystone and primaryGraph both falsy? We should have gotten this back from the server. (E: e34cd882ac66633c75c53318a3b28826)`); }
|
|
957
|
+
if (!delegateKeystone) { throw new Error(`(UNEXPECTED) delegateKeystone falsy? We should have made this locally and even registered it with the server. (E: 56b1c396269e15f008b7e298c71bc826)`); }
|
|
958
|
+
|
|
959
|
+
if (primaryKeystone) {
|
|
960
|
+
ibGibsToRegister.unshift(toDto({ ibGib: primaryKeystone }));
|
|
961
|
+
}
|
|
962
|
+
await updateSpecialIndex({
|
|
963
|
+
type: "keystones",
|
|
964
|
+
rel8nInfos: [
|
|
965
|
+
{
|
|
966
|
+
rel8nName: "keystone",
|
|
967
|
+
ibGibs: ibGibsToRegister,
|
|
968
|
+
},
|
|
969
|
+
],
|
|
970
|
+
metaspace,
|
|
971
|
+
space,
|
|
972
|
+
});
|
|
973
|
+
|
|
974
|
+
// Expose for dev tools debugging if available
|
|
975
|
+
if (masterSecret) {
|
|
976
|
+
(window as any).dev_domainI = primaryKeystone;
|
|
977
|
+
(window as any).dev_domainIMasterSecret = masterSecret;
|
|
978
|
+
(window as any).dev_syncDelegate = delegateKeystone;
|
|
979
|
+
(window as any).dev_syncDelegateSecret = delegateSecret;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
// 2. Request to make the new identity active
|
|
983
|
+
if (makeActive) {
|
|
984
|
+
window.dispatchEvent(new CustomEvent(EVENT_IBGIB_IDENTITY_REQUEST_CHANGE, {
|
|
985
|
+
detail: { activeIdentityAddr: primaryTjpAddr },
|
|
986
|
+
bubbles: true,
|
|
987
|
+
composed: true
|
|
988
|
+
}));
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
// 3. Display success status
|
|
992
|
+
const successMsg = "Identity successfully created and activated!";
|
|
993
|
+
this.showBusy({
|
|
994
|
+
isBusy: true,
|
|
995
|
+
title: 'Success!',
|
|
996
|
+
msg: successMsg,
|
|
997
|
+
animationEmoji: '✅'
|
|
998
|
+
});
|
|
999
|
+
await delay(humanEyeDelay);
|
|
1000
|
+
if (this.elements?.keystoneAddrEl) {
|
|
1001
|
+
this.elements.keystoneAddrEl.textContent = primaryTjpAddr;
|
|
1002
|
+
}
|
|
1003
|
+
if (this.elements?.statusArea) {
|
|
1004
|
+
this.elements.statusArea.classList.remove('hidden');
|
|
1005
|
+
}
|
|
1006
|
+
this.setStatus(successMsg, "success");
|
|
1007
|
+
console.log(`${lc} ✓ ${successMsg}`);
|
|
1008
|
+
|
|
1009
|
+
// 4. Dispatch UI navigation message to close creator and open identity manager
|
|
1010
|
+
window.dispatchEvent(new CustomEvent(EVENT_IBGIB_UI_MESSAGE, {
|
|
1011
|
+
detail: { action: 'show-identity-manager', addr: primaryTjpAddr },
|
|
1012
|
+
bubbles: true,
|
|
1013
|
+
composed: true
|
|
1014
|
+
}));
|
|
1015
|
+
|
|
1016
|
+
this.showBusy({ isBusy: false });
|
|
1017
|
+
}
|
|
1018
|
+
|
|
589
1019
|
private setFormDisabled(disabled: boolean): void {
|
|
590
1020
|
const root = this.shadowRoot!;
|
|
591
1021
|
const inputIds = [
|