@ibgib/web-gib 0.0.57 → 0.0.58
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/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 +386 -63
- 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/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 +441 -67
- package/src/ui/component/identity/keystone-details/keystone-details.mts +10 -62
|
@@ -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,245 @@ 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
|
+
this.setStatus(`Opening ${providerId.toUpperCase()} SSO authorization window...`, 'info');
|
|
265
|
+
const resPopup = await executeSsoOAuthPopup({ providerId });
|
|
266
|
+
if (!resPopup.success || !resPopup.code) {
|
|
267
|
+
this.setStatus(resPopup.message || `${providerId.toUpperCase()} sign-in cancelled.`, 'error');
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
this.setStatus(`Exchanging ${providerId.toUpperCase()} authorization code...`, 'info');
|
|
272
|
+
const apiBridge = getApiBridge();
|
|
273
|
+
const resLogin = await apiBridge.postSsoLogin({
|
|
274
|
+
code: resPopup.code,
|
|
275
|
+
providerId,
|
|
276
|
+
redirectUri: resPopup.redirectUri!
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
if (!resLogin.success || !resLogin.primaryTjpAddr || !resLogin.custodianReqs) {
|
|
280
|
+
this.setStatus(resLogin.message || `${providerId.toUpperCase()} login failed.`, 'error');
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const { primaryTjpAddr, custodianReqs } = resLogin;
|
|
285
|
+
this.setStatus('Deriving local device delegate keys...', 'info');
|
|
286
|
+
const delegateSecret = await deriveLocalCustodianDelegateSecret({ primaryTjpAddr });
|
|
287
|
+
const email = resLogin.userInfo?.email || '';
|
|
288
|
+
const username = email ? email.split('@')[0] : 'user';
|
|
289
|
+
|
|
290
|
+
const delegateBuilder = KeystoneProfileBuilder.buildKeystone('sync')
|
|
291
|
+
.withUsername(`${username}-sync`)
|
|
292
|
+
.withDescription(`Sync Delegate for ${username}`)
|
|
293
|
+
.withDetails({
|
|
294
|
+
client: 'space-gib-web',
|
|
295
|
+
role: 'sync-delegate',
|
|
296
|
+
primaryTjpAddr,
|
|
297
|
+
custodianReqs,
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
const configs = await delegateBuilder.compileConfigs();
|
|
301
|
+
const metaspace = await getGlobalMetaspace_waitIfNeeded();
|
|
302
|
+
const space = await metaspace.getLocalUserSpace({ lock: false });
|
|
303
|
+
if (!space) {
|
|
304
|
+
this.setStatus('Could not access local user space.', 'error');
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const keystoneService = new KeystoneService_V1();
|
|
309
|
+
const delegateKeystone = await keystoneService.genesis({
|
|
310
|
+
masterSecret: delegateSecret,
|
|
311
|
+
configs,
|
|
312
|
+
metaspace,
|
|
313
|
+
space,
|
|
314
|
+
frameDetails: delegateBuilder.getFrameDetails(),
|
|
315
|
+
isPrimary: false
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
this.setStatus('Registering device delegate with custodian server...', 'info');
|
|
319
|
+
const resRegister = await apiBridge.postCustodianRegisterDelegate({ delegateKeystone });
|
|
320
|
+
|
|
321
|
+
if (!resRegister.success) {
|
|
322
|
+
this.setStatus(resRegister.message || 'Custodian delegate registration failed.', 'error');
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Finalize onboarding: update keystones index, activate identity, and navigate to identity manager
|
|
327
|
+
await this.finalizeIdentityOnboarding({
|
|
328
|
+
primaryKeystone: resRegister.evolvedPrimaryKeystone || resLogin.primaryKeystone,
|
|
329
|
+
primaryGraph: resRegister.primaryGraph || resLogin.primaryGraph,
|
|
330
|
+
delegateKeystone,
|
|
331
|
+
primaryTjpAddr,
|
|
332
|
+
delegateSecret,
|
|
333
|
+
makeActive: true
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Triggered by the "Send 6-Digit Verification Code" button.
|
|
339
|
+
* Validates quick email input and dispatches a 6-digit OTP code to the user's inbox.
|
|
340
|
+
*/
|
|
341
|
+
protected async handleQuickSendCode(): Promise<void> {
|
|
342
|
+
const lc = `${this.lc}[${this.handleQuickSendCode.name}]`;
|
|
343
|
+
const btnQuickSendCode = this.shadowRoot?.getElementById('btn-quick-send-code') as HTMLButtonElement | null;
|
|
344
|
+
const inputQuickEmail = this.shadowRoot?.getElementById('input-quick-email') as HTMLInputElement | null;
|
|
345
|
+
const secQuickOtp = this.shadowRoot?.getElementById('section-quick-otp') as HTMLDivElement | null;
|
|
346
|
+
|
|
347
|
+
const email = (inputQuickEmail?.value || '').trim();
|
|
348
|
+
if (!email || !validateEmail(email)) {
|
|
349
|
+
this.setStatus("Please enter a valid email address.", "error");
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (btnQuickSendCode) {
|
|
354
|
+
btnQuickSendCode.disabled = true;
|
|
355
|
+
btnQuickSendCode.textContent = "Sending Verification Code...";
|
|
356
|
+
}
|
|
357
|
+
this.setStatus("Requesting verification code...", "info");
|
|
358
|
+
|
|
359
|
+
try {
|
|
360
|
+
const resCode = await getApiBridge().postVerificationCode({ email });
|
|
361
|
+
if (resCode.success) {
|
|
362
|
+
secQuickOtp?.classList.remove('hidden');
|
|
363
|
+
this.setStatus("✉️ 6-digit verification code sent to your email!", "success");
|
|
364
|
+
} else {
|
|
365
|
+
this.setStatus(resCode.message || "Failed to dispatch verification code.", "error");
|
|
366
|
+
}
|
|
367
|
+
} catch (error) {
|
|
368
|
+
const emsg = extractErrorMsg(error);
|
|
369
|
+
console.error(`${lc} ${emsg}`);
|
|
370
|
+
this.setStatus(`Failed to dispatch verification code: ${emsg}`, "error");
|
|
371
|
+
} finally {
|
|
372
|
+
if (btnQuickSendCode) {
|
|
373
|
+
btnQuickSendCode.disabled = false;
|
|
374
|
+
btnQuickSendCode.textContent = "Send 6-Digit Verification Code";
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Triggered by the "Verify Code" button.
|
|
381
|
+
* Submits 6-digit OTP code to custodian server, builds local sync delegate keystone,
|
|
382
|
+
* registers delegate on server, and finalizes onboarding.
|
|
383
|
+
*/
|
|
384
|
+
protected async handleQuickVerifyCode(): Promise<void> {
|
|
385
|
+
const lc = `${this.lc}[${this.handleQuickVerifyCode.name}]`;
|
|
386
|
+
const inputQuickEmail = this.shadowRoot?.getElementById('input-quick-email') as HTMLInputElement | null;
|
|
387
|
+
const inputQuickOtp = this.shadowRoot?.getElementById('input-quick-otp') as HTMLInputElement | null;
|
|
388
|
+
const btnQuickVerifyCode = this.shadowRoot?.getElementById('btn-quick-verify-code') as HTMLButtonElement | null;
|
|
389
|
+
|
|
390
|
+
const email = (inputQuickEmail?.value || '').trim();
|
|
391
|
+
const code = (inputQuickOtp?.value || '').trim();
|
|
392
|
+
if (!code || code.length !== 6 || !/^\d{6}$/.test(code)) {
|
|
393
|
+
this.setStatus("Please enter a valid 6-digit verification code.", "error");
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (btnQuickVerifyCode) {
|
|
398
|
+
btnQuickVerifyCode.disabled = true;
|
|
399
|
+
btnQuickVerifyCode.textContent = "Verifying...";
|
|
400
|
+
}
|
|
401
|
+
this.setStatus("Verifying code & completing onboarding...", "info");
|
|
402
|
+
this.showBusy({
|
|
403
|
+
isBusy: true,
|
|
404
|
+
title: 'Verifying Code...',
|
|
405
|
+
msg: 'Great! Juust a moment...\n(Validating code, promoting server domain space, other security stuff...)',
|
|
406
|
+
animationEmoji: '🔐'
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
try {
|
|
410
|
+
const resGenesis = await getApiBridge().postCustodianGenesis({ email, code });
|
|
411
|
+
if (!resGenesis.success || !resGenesis.primaryTjpAddr || !resGenesis.custodianReqs) {
|
|
412
|
+
this.setStatus(resGenesis.message || "Custodian verification failed.", "error");
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const primaryTjpAddr = resGenesis.primaryTjpAddr;
|
|
417
|
+
const custodianReqs = resGenesis.custodianReqs;
|
|
418
|
+
|
|
419
|
+
// Construct local delegate keystone
|
|
420
|
+
this.setStatus("Constructing local sync delegate...", "info");
|
|
421
|
+
this.showBusy({
|
|
422
|
+
isBusy: true,
|
|
423
|
+
title: 'Creating Sync Delegate...',
|
|
424
|
+
msg: 'Generating cryptographic sync delegate keys...',
|
|
425
|
+
animationEmoji: '🔑'
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
const delegateSecret = await deriveLocalCustodianDelegateSecret({ primaryTjpAddr });
|
|
429
|
+
const username = email.split('@')[0];
|
|
430
|
+
const delegateBuilder = KeystoneProfileBuilder.buildKeystone('sync')
|
|
431
|
+
.withUsername(`${username}-sync`)
|
|
432
|
+
.withDescription(`Sync Delegate for ${username}`)
|
|
433
|
+
.withDetails({
|
|
434
|
+
client: 'space-gib-web',
|
|
435
|
+
role: 'sync-delegate',
|
|
436
|
+
primaryTjpAddr,
|
|
437
|
+
custodianReqs,
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
const configs = await delegateBuilder.compileConfigs();
|
|
441
|
+
const metaspace = await getGlobalMetaspace_waitIfNeeded();
|
|
442
|
+
const space = await metaspace.getLocalUserSpace({ lock: false });
|
|
443
|
+
if (!space) {
|
|
444
|
+
this.setStatus("Failed to access local user space.", "error");
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const keystoneService = new KeystoneService_V1();
|
|
449
|
+
const delegateKeystone = await keystoneService.genesis({
|
|
450
|
+
masterSecret: delegateSecret,
|
|
451
|
+
configs,
|
|
452
|
+
metaspace,
|
|
453
|
+
space,
|
|
454
|
+
frameDetails: delegateBuilder.getFrameDetails(),
|
|
455
|
+
isPrimary: false
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
// Register delegate with server
|
|
459
|
+
this.setStatus("Registering device delegate with custodian server...", "info");
|
|
460
|
+
this.showBusy({
|
|
461
|
+
isBusy: true,
|
|
462
|
+
title: 'Registering Delegate...',
|
|
463
|
+
msg: 'Registering local delegate with custodian server...',
|
|
464
|
+
animationEmoji: '📝'
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
const resRegister = await getApiBridge().postCustodianRegisterDelegate({ delegateKeystone });
|
|
468
|
+
if (!resRegister.success) {
|
|
469
|
+
this.setStatus(resRegister.message || "Failed to register delegate with custodian server.", "error");
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Finalize onboarding: update keystones index, activate identity, and navigate to identity manager
|
|
474
|
+
await this.finalizeIdentityOnboarding({
|
|
475
|
+
primaryKeystone: resRegister.evolvedPrimaryKeystone || resGenesis.primaryKeystone,
|
|
476
|
+
primaryGraph: resRegister.primaryGraph || resGenesis.primaryGraph,
|
|
477
|
+
delegateKeystone,
|
|
478
|
+
primaryTjpAddr,
|
|
479
|
+
delegateSecret,
|
|
480
|
+
makeActive: true
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
} catch (error) {
|
|
484
|
+
const emsg = extractErrorMsg(error);
|
|
485
|
+
console.error(`${lc} ${emsg}`, error);
|
|
486
|
+
this.setStatus(`Error: ${emsg}`, "error");
|
|
487
|
+
} finally {
|
|
488
|
+
this.showBusy({ isBusy: false });
|
|
489
|
+
if (btnQuickVerifyCode) {
|
|
490
|
+
btnQuickVerifyCode.disabled = false;
|
|
491
|
+
btnQuickVerifyCode.textContent = "Verify Code";
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
201
496
|
/**
|
|
202
497
|
* Triggered by the "Generate" button.
|
|
203
498
|
* Orchestrates: Secret -> Local Genesis -> Graph Collection -> Server Sync.
|
|
@@ -385,7 +680,7 @@ export class KeystoneCreatorComponentInstance
|
|
|
385
680
|
this.showBusy({
|
|
386
681
|
isBusy: true,
|
|
387
682
|
title: 'Syncing to Server...',
|
|
388
|
-
msg: '
|
|
683
|
+
msg: 'Staging pending genesis keystone on server...',
|
|
389
684
|
animationEmoji: '🔄'
|
|
390
685
|
});
|
|
391
686
|
const resSync = await getApiBridge().postGenesisKeystone(keystoneIbGib);
|
|
@@ -510,69 +805,16 @@ export class KeystoneCreatorComponentInstance
|
|
|
510
805
|
}
|
|
511
806
|
}
|
|
512
807
|
|
|
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,
|
|
808
|
+
// 5. Finalize onboarding: update keystones index, activate identity, and navigate to identity manager
|
|
809
|
+
await this.finalizeIdentityOnboarding({
|
|
810
|
+
primaryKeystone: evolvedParentKeystoneIbGib,
|
|
811
|
+
delegateKeystone: delegateKeystoneIbGib,
|
|
812
|
+
primaryTjpAddr: evolvedParentAddr,
|
|
813
|
+
masterSecret,
|
|
814
|
+
delegateSecret,
|
|
815
|
+
makeActive: this.elements!.checkboxMakeActive?.checked ?? true
|
|
534
816
|
});
|
|
535
817
|
|
|
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
818
|
} catch (error) {
|
|
577
819
|
const emsg = extractErrorMsg(error);
|
|
578
820
|
console.error(`${lc} ${emsg}`, error);
|
|
@@ -586,6 +828,138 @@ export class KeystoneCreatorComponentInstance
|
|
|
586
828
|
}
|
|
587
829
|
}
|
|
588
830
|
|
|
831
|
+
/**
|
|
832
|
+
* DRY helper to register keystones in the local keystones special index,
|
|
833
|
+
* set the active identity, and navigate to the Identity Manager component.
|
|
834
|
+
*/
|
|
835
|
+
private async finalizeIdentityOnboarding({
|
|
836
|
+
primaryKeystone,
|
|
837
|
+
primaryGraph,
|
|
838
|
+
delegateKeystone,
|
|
839
|
+
primaryTjpAddr,
|
|
840
|
+
masterSecret,
|
|
841
|
+
delegateSecret,
|
|
842
|
+
makeActive = true,
|
|
843
|
+
}: {
|
|
844
|
+
primaryKeystone?: IbGib_V1;
|
|
845
|
+
primaryGraph?: FlatIbGibGraph;
|
|
846
|
+
delegateKeystone: IbGib_V1;
|
|
847
|
+
primaryTjpAddr: string;
|
|
848
|
+
masterSecret?: string;
|
|
849
|
+
delegateSecret?: string;
|
|
850
|
+
makeActive?: boolean;
|
|
851
|
+
}): Promise<void> {
|
|
852
|
+
const lc = `${this.lc}[${this.finalizeIdentityOnboarding.name}]`;
|
|
853
|
+
const humanEyeDelay = 500;
|
|
854
|
+
|
|
855
|
+
const metaspace = await getGlobalMetaspace_waitIfNeeded();
|
|
856
|
+
if (!metaspace) {
|
|
857
|
+
throw new Error(`(UNEXPECTED) metaspace falsy? Expected active global metaspace instance. (E: 2e37a8b6fe987449c5d8693a7d4a2827)`);
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
const space = await metaspace.getLocalUserSpace({});
|
|
861
|
+
if (!space) {
|
|
862
|
+
throw new Error(`(UNEXPECTED) space falsy? Expected valid default local user space from metaspace. (E: 1e37a8b6fe987449c5d8693a7d4a2826)`);
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
// 0. Persist all frames of primary identity graph into local browser space
|
|
866
|
+
if (primaryGraph) {
|
|
867
|
+
const graphIbGibs = Object.values(primaryGraph) as IbGib_V1[];
|
|
868
|
+
for (const frame of graphIbGibs) {
|
|
869
|
+
await metaspace.put({ ibGibs: [frame], space });
|
|
870
|
+
await metaspace.registerNewIbGib({ ibGib: frame, space });
|
|
871
|
+
}
|
|
872
|
+
} else if (primaryKeystone) {
|
|
873
|
+
await metaspace.put({ ibGibs: [primaryKeystone], space });
|
|
874
|
+
await metaspace.registerNewIbGib({ ibGib: primaryKeystone, space });
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
// 0.5 Verify primary keystone exists in local browser space via getLatestKeystone
|
|
878
|
+
const keystoneService = new KeystoneService_V1();
|
|
879
|
+
try {
|
|
880
|
+
await keystoneService.getLatestKeystone({
|
|
881
|
+
addr: primaryTjpAddr,
|
|
882
|
+
metaspace,
|
|
883
|
+
space
|
|
884
|
+
});
|
|
885
|
+
} catch (error) {
|
|
886
|
+
const emsg = `Primary identity keystone ${primaryTjpAddr} not found in local browser space! Error: ${extractErrorMsg(error)}`;
|
|
887
|
+
console.error(`${lc} ${emsg} (E: a123456789abcdef0123456789c0001)`);
|
|
888
|
+
throw new Error(emsg);
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
// 1. Register with the local keystones special index
|
|
892
|
+
this.showBusy({
|
|
893
|
+
isBusy: true,
|
|
894
|
+
title: 'Registering Identity...',
|
|
895
|
+
msg: 'Updating local identity indexes and scope settings...',
|
|
896
|
+
animationEmoji: '💾'
|
|
897
|
+
});
|
|
898
|
+
const ibGibsToRegister: IbGib_V1[] = [toDto({ ibGib: delegateKeystone })];
|
|
899
|
+
|
|
900
|
+
if (!primaryKeystone && !primaryGraph) { throw new Error(`(UNEXPECTED) primaryKeystone and primaryGraph both falsy? We should have gotten this back from the server. (E: e34cd882ac66633c75c53318a3b28826)`); }
|
|
901
|
+
if (!delegateKeystone) { throw new Error(`(UNEXPECTED) delegateKeystone falsy? We should have made this locally and even registered it with the server. (E: 56b1c396269e15f008b7e298c71bc826)`); }
|
|
902
|
+
|
|
903
|
+
if (primaryKeystone) {
|
|
904
|
+
ibGibsToRegister.unshift(toDto({ ibGib: primaryKeystone }));
|
|
905
|
+
}
|
|
906
|
+
await updateSpecialIndex({
|
|
907
|
+
type: "keystones",
|
|
908
|
+
rel8nInfos: [
|
|
909
|
+
{
|
|
910
|
+
rel8nName: "keystone",
|
|
911
|
+
ibGibs: ibGibsToRegister,
|
|
912
|
+
},
|
|
913
|
+
],
|
|
914
|
+
metaspace,
|
|
915
|
+
space,
|
|
916
|
+
});
|
|
917
|
+
|
|
918
|
+
// Expose for dev tools debugging if available
|
|
919
|
+
if (masterSecret) {
|
|
920
|
+
(window as any).dev_domainI = primaryKeystone;
|
|
921
|
+
(window as any).dev_domainIMasterSecret = masterSecret;
|
|
922
|
+
(window as any).dev_syncDelegate = delegateKeystone;
|
|
923
|
+
(window as any).dev_syncDelegateSecret = delegateSecret;
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
// 2. Request to make the new identity active
|
|
927
|
+
if (makeActive) {
|
|
928
|
+
window.dispatchEvent(new CustomEvent(EVENT_IBGIB_IDENTITY_REQUEST_CHANGE, {
|
|
929
|
+
detail: { activeIdentityAddr: primaryTjpAddr },
|
|
930
|
+
bubbles: true,
|
|
931
|
+
composed: true
|
|
932
|
+
}));
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
// 3. Display success status
|
|
936
|
+
const successMsg = "Identity successfully created and activated!";
|
|
937
|
+
this.showBusy({
|
|
938
|
+
isBusy: true,
|
|
939
|
+
title: 'Success!',
|
|
940
|
+
msg: successMsg,
|
|
941
|
+
animationEmoji: '✅'
|
|
942
|
+
});
|
|
943
|
+
await delay(humanEyeDelay);
|
|
944
|
+
if (this.elements?.keystoneAddrEl) {
|
|
945
|
+
this.elements.keystoneAddrEl.textContent = primaryTjpAddr;
|
|
946
|
+
}
|
|
947
|
+
if (this.elements?.statusArea) {
|
|
948
|
+
this.elements.statusArea.classList.remove('hidden');
|
|
949
|
+
}
|
|
950
|
+
this.setStatus(successMsg, "success");
|
|
951
|
+
console.log(`${lc} ✓ ${successMsg}`);
|
|
952
|
+
|
|
953
|
+
// 4. Dispatch UI navigation message to close creator and open identity manager
|
|
954
|
+
window.dispatchEvent(new CustomEvent(EVENT_IBGIB_UI_MESSAGE, {
|
|
955
|
+
detail: { action: 'show-identity-manager', addr: primaryTjpAddr },
|
|
956
|
+
bubbles: true,
|
|
957
|
+
composed: true
|
|
958
|
+
}));
|
|
959
|
+
|
|
960
|
+
this.showBusy({ isBusy: false });
|
|
961
|
+
}
|
|
962
|
+
|
|
589
963
|
private setFormDisabled(disabled: boolean): void {
|
|
590
964
|
const root = this.shadowRoot!;
|
|
591
965
|
const inputIds = [
|
|
@@ -22,6 +22,7 @@ import { getKeystoneSyncService } from "../../../../identity/keystone-sync-servi
|
|
|
22
22
|
import { updateSpecialIndex } from "@ibgib/core-gib/dist/timeline/timeline-api.mjs";
|
|
23
23
|
import { getTjpAddr, toDto } from "@ibgib/core-gib/dist/common/other/ibgib-helper.mjs";
|
|
24
24
|
import { parseSpaceIb } from "@ibgib/core-gib/dist/witness/space/space-helper.mjs";
|
|
25
|
+
import { executeSsoOAuthPopup } from "../../../../identity/sso-popup-helper.mjs";
|
|
25
26
|
|
|
26
27
|
import { getComponentCtorArg } from "../../../../app-bootstrap/init-orchestration.mjs";
|
|
27
28
|
import { promptForSecret } from "../../../../helpers.web.mjs";
|
|
@@ -403,7 +404,7 @@ export class KeystoneDetailsComponentInstance
|
|
|
403
404
|
|
|
404
405
|
await this.updateFrameDetailsView();
|
|
405
406
|
} catch (error) {
|
|
406
|
-
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
407
|
+
console.error(`${lc} Registered identity keystone address ${this.ibGibAddr} not found in local browser space: ${extractErrorMsg(error)} (E: a123456789abcdef0123456789c0003)`);
|
|
407
408
|
throw error;
|
|
408
409
|
} finally {
|
|
409
410
|
if (logalot) { console.log(`${lc} complete.`); }
|
|
@@ -1039,70 +1040,17 @@ export class KeystoneDetailsComponentInstance
|
|
|
1039
1040
|
this.setSSOStatus(`Linking ${providerId}...`, "info");
|
|
1040
1041
|
|
|
1041
1042
|
try {
|
|
1042
|
-
|
|
1043
|
-
const res = await fetch('/api/identity/sso/config');
|
|
1044
|
-
if (!res.ok) {
|
|
1045
|
-
throw new Error(`Failed to fetch SSO config from server: ${res.statusText}`);
|
|
1046
|
-
}
|
|
1047
|
-
const data = await res.json();
|
|
1048
|
-
if (!data.success || !data.providers) {
|
|
1049
|
-
throw new Error(`SSO configuration fetch failed: ${data.message || 'unknown error'}`);
|
|
1050
|
-
}
|
|
1051
|
-
|
|
1052
|
-
const providerConfig = data.providers.find((p: any) => p.providerId === providerId);
|
|
1053
|
-
if (!providerConfig) {
|
|
1054
|
-
throw new Error(`SSO provider '${providerId}' is not configured on the server.`);
|
|
1055
|
-
}
|
|
1056
|
-
|
|
1057
|
-
// 2. Generate a secure, high-entropy user nonce
|
|
1058
|
-
const userNonce = (Math.random().toString(36).substring(2) + Math.random().toString(36).substring(2)).substring(0, 32);
|
|
1059
|
-
|
|
1060
|
-
// 3. Construct state payload
|
|
1061
|
-
const stateObj = {
|
|
1043
|
+
const resPopup = await executeSsoOAuthPopup({
|
|
1062
1044
|
providerId,
|
|
1063
|
-
parentTjpAddr: this.ibGibAddr
|
|
1064
|
-
|
|
1065
|
-
};
|
|
1066
|
-
const state = btoa(JSON.stringify(stateObj));
|
|
1067
|
-
|
|
1068
|
-
// 4. Construct Auth URL
|
|
1069
|
-
const authUrl = `${providerConfig.authUrl}?client_id=${providerConfig.clientId}&redirect_uri=${encodeURIComponent(providerConfig.redirectUri)}&response_type=code&scope=${encodeURIComponent(providerConfig.scope)}&state=${encodeURIComponent(state)}`;
|
|
1070
|
-
|
|
1071
|
-
// 5. Open popup window
|
|
1072
|
-
const popup = window.open(
|
|
1073
|
-
authUrl,
|
|
1074
|
-
'sso-oauth-popup',
|
|
1075
|
-
'width=600,height=700,status=yes,toolbar=no,menubar=no,location=yes'
|
|
1076
|
-
);
|
|
1077
|
-
if (!popup) {
|
|
1078
|
-
throw new Error("Popup blocked by browser. Please enable popups for this site.");
|
|
1079
|
-
}
|
|
1045
|
+
parentTjpAddr: this.ibGibAddr
|
|
1046
|
+
});
|
|
1080
1047
|
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
const callbackData = event.data;
|
|
1086
|
-
if (callbackData && callbackData.type === 'sso-oauth-callback') {
|
|
1087
|
-
window.removeEventListener('message', messageListener);
|
|
1088
|
-
clearInterval(checkClosedInterval);
|
|
1089
|
-
await this.handleSSOLinkCallback(callbackData.code, stateObj, providerConfig.redirectUri);
|
|
1090
|
-
}
|
|
1091
|
-
};
|
|
1092
|
-
window.addEventListener('message', messageListener);
|
|
1093
|
-
|
|
1094
|
-
// 7. Poll for manual closure of the popup window
|
|
1095
|
-
checkClosedInterval = setInterval(() => {
|
|
1096
|
-
if (popup.closed) {
|
|
1097
|
-
clearInterval(checkClosedInterval);
|
|
1098
|
-
window.removeEventListener('message', messageListener);
|
|
1099
|
-
const statusEl = this.elements!.ssoStatusMsgEl;
|
|
1100
|
-
if (statusEl.textContent && statusEl.textContent.startsWith('Linking')) {
|
|
1101
|
-
this.setSSOStatus('Linking cancelled (window closed).', 'error');
|
|
1102
|
-
}
|
|
1103
|
-
}
|
|
1104
|
-
}, 500);
|
|
1048
|
+
if (!resPopup.success || !resPopup.code) {
|
|
1049
|
+
this.setSSOStatus(resPopup.message || `Linking cancelled.`, "error");
|
|
1050
|
+
return;
|
|
1051
|
+
}
|
|
1105
1052
|
|
|
1053
|
+
await this.handleSSOLinkCallback(resPopup.code, resPopup.stateObj, resPopup.redirectUri!);
|
|
1106
1054
|
} catch (error) {
|
|
1107
1055
|
console.error(`${lc} SSO link initiation failed: ${extractErrorMsg(error)}`);
|
|
1108
1056
|
this.setSSOStatus(`Link failed: ${extractErrorMsg(error)}`, "error");
|