@ibgib/web-gib 0.0.61 → 0.0.63
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/helpers.web.d.mts +20 -3
- package/dist/helpers.web.d.mts.map +1 -1
- package/dist/helpers.web.mjs +84 -3
- package/dist/helpers.web.mjs.map +1 -1
- package/dist/identity/device-delegate-helper.d.mts +48 -0
- package/dist/identity/device-delegate-helper.d.mts.map +1 -1
- package/dist/identity/device-delegate-helper.mjs +86 -2
- package/dist/identity/device-delegate-helper.mjs.map +1 -1
- package/dist/identity/device-delegate.respec.mjs +6 -6
- package/dist/identity/device-delegate.respec.mjs.map +1 -1
- package/dist/identity/passkey-helper.d.mts +77 -0
- package/dist/identity/passkey-helper.d.mts.map +1 -0
- package/dist/identity/passkey-helper.mjs +258 -0
- package/dist/identity/passkey-helper.mjs.map +1 -0
- package/dist/identity/passkey.respec.d.mts +2 -0
- package/dist/identity/passkey.respec.d.mts.map +1 -0
- package/dist/identity/passkey.respec.mjs +169 -0
- package/dist/identity/passkey.respec.mjs.map +1 -0
- package/dist/index.d.mts +1 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -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 +1 -0
- package/dist/ui/component/identity/add-device/add-device.mjs.map +1 -1
- package/dist/ui/component/identity/keystone-creator/keystone-creator.css +26 -0
- package/dist/ui/component/identity/keystone-creator/keystone-creator.d.mts +18 -1
- package/dist/ui/component/identity/keystone-creator/keystone-creator.d.mts.map +1 -1
- package/dist/ui/component/identity/keystone-creator/keystone-creator.html +60 -4
- package/dist/ui/component/identity/keystone-creator/keystone-creator.mjs +518 -74
- package/dist/ui/component/identity/keystone-creator/keystone-creator.mjs.map +1 -1
- package/dist/ui/component/identity/keystone-details/keystone-details.d.mts +28 -1
- package/dist/ui/component/identity/keystone-details/keystone-details.d.mts.map +1 -1
- package/dist/ui/component/identity/keystone-details/keystone-details.html +46 -0
- package/dist/ui/component/identity/keystone-details/keystone-details.mjs +1146 -208
- package/dist/ui/component/identity/keystone-details/keystone-details.mjs.map +1 -1
- package/dist/ui/component/identity/remove-device/remove-device.d.mts.map +1 -1
- package/dist/ui/component/identity/remove-device/remove-device.mjs +1 -0
- package/dist/ui/component/identity/remove-device/remove-device.mjs.map +1 -1
- package/dist/ui/ui-helpers.d.mts +3 -0
- package/dist/ui/ui-helpers.d.mts.map +1 -1
- package/dist/ui/ui-helpers.mjs +116 -7
- package/dist/ui/ui-helpers.mjs.map +1 -1
- package/package.json +2 -2
- package/src/AUTO-GENERATED-version.mts +1 -1
- package/src/helpers.web.mts +117 -7
- package/src/identity/device-delegate-helper.mts +135 -2
- package/src/identity/device-delegate.respec.mts +6 -6
- package/src/identity/passkey-helper.mts +337 -0
- package/src/identity/passkey.respec.mts +194 -0
- package/src/index.mts +1 -0
- package/src/ui/component/identity/add-device/add-device.mts +1 -0
- package/src/ui/component/identity/keystone-creator/keystone-creator.css +26 -0
- package/src/ui/component/identity/keystone-creator/keystone-creator.html +60 -4
- package/src/ui/component/identity/keystone-creator/keystone-creator.mts +561 -77
- package/src/ui/component/identity/keystone-details/keystone-details.html +46 -0
- package/src/ui/component/identity/keystone-details/keystone-details.mts +1239 -224
- package/src/ui/component/identity/remove-device/remove-device.mts +1 -0
- package/src/ui/ui-helpers.mts +123 -6
- package/tools/auto-generated-agent-skills/skills/ibgib-create-app-server/SKILL.md +50 -0
- package/src/identity/custodian-delegate-helper.mts.bak +0 -7
- package/src/identity/sso/sso-config-helper.mts.bak +0 -52
- package/src/identity/sso/sso-custodian-service.mts.bak +0 -566
- package/src/identity/sso/sso-custodian-service.respec.mts.bak +0 -298
|
@@ -2,7 +2,7 @@ import thisCss from "./keystone-creator.css";
|
|
|
2
2
|
import thisHtml from "./keystone-creator.html";
|
|
3
3
|
import commonComponentStylesCss from "../../common-component-styles.css";
|
|
4
4
|
|
|
5
|
-
import { delay, extractErrorMsg, } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
|
|
5
|
+
import { delay, extractErrorMsg, getSaferSubstring } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
|
|
6
6
|
import { IbGibAddr } from "@ibgib/ts-gib/dist/types.mjs";
|
|
7
7
|
import { IbGib_V1 } from "@ibgib/ts-gib/dist/V1/types.mjs";
|
|
8
8
|
import { getIbGibAddr } from "@ibgib/ts-gib/dist/helper.mjs";
|
|
@@ -24,9 +24,11 @@ import {
|
|
|
24
24
|
} from "../../component-types.mjs";
|
|
25
25
|
import { getComponentCtorArg } from "../../../../app-bootstrap/init-orchestration.mjs";
|
|
26
26
|
import { getApiBridge } from "../../../../api/ibgib-api-bridge.mjs";
|
|
27
|
-
import { getOrCreateDeviceDelegateSecret, setDeviceDelegateSecret } from "../../../../identity/device-delegate-helper.mjs";
|
|
27
|
+
import { getOrCreateDeviceDelegateSecret, setDeviceDelegateSecret, createSovereignDeviceSyncDelegate, createOAuth2DeviceSyncDelegate, createEmailDeviceSyncDelegate } from "../../../../identity/device-delegate-helper.mjs";
|
|
28
28
|
import { executeSsoOAuthPopup } from "../../../../identity/sso-popup-helper.mjs";
|
|
29
29
|
import { generateDefaultDeviceTag } from "../../../../util/device-helper.mjs";
|
|
30
|
+
import { isPasskeyPrfSupported, registerPasskeyCredential, evaluatePasskeyPrf, createPasskeyDeviceSyncDelegate, bufferToHex } from "../../../../identity/passkey-helper.mjs";
|
|
31
|
+
import { KeystonePasskeyEntry } from "@ibgib/core-gib/dist/keystone/keystone-types.mjs";
|
|
30
32
|
import { EVENT_IBGIB_IDENTITY_REQUEST_CHANGE, EVENT_IBGIB_UI_MESSAGE } from "../../../ui-constants.mjs";
|
|
31
33
|
import { alertUser, shadowRoot_getElementById } from "../../../../helpers.web.mjs";
|
|
32
34
|
|
|
@@ -81,6 +83,12 @@ interface KeystoneCreatorElements extends ElementsBase {
|
|
|
81
83
|
inputEmail: HTMLInputElement;
|
|
82
84
|
inputSecret: HTMLInputElement;
|
|
83
85
|
checkboxMakeActive: HTMLInputElement;
|
|
86
|
+
inputPasskeyUsername: HTMLInputElement;
|
|
87
|
+
inputPasskeyDescription: HTMLInputElement;
|
|
88
|
+
inputPasskeyEmail: HTMLInputElement;
|
|
89
|
+
inputPasskeyName: HTMLInputElement;
|
|
90
|
+
checkboxPasskeyMakeActive: HTMLInputElement;
|
|
91
|
+
btnGeneratePasskey: HTMLButtonElement;
|
|
84
92
|
statusArea: HTMLDivElement;
|
|
85
93
|
statusText: HTMLParagraphElement;
|
|
86
94
|
}
|
|
@@ -94,6 +102,7 @@ export class KeystoneCreatorComponentInstance
|
|
|
94
102
|
|
|
95
103
|
protected lc: string = `[KeystoneCreatorComponentInstance]`;
|
|
96
104
|
private hasExistingIdentities = false;
|
|
105
|
+
private resendCooldownInterval: any = null;
|
|
97
106
|
|
|
98
107
|
constructor() {
|
|
99
108
|
super();
|
|
@@ -103,6 +112,36 @@ export class KeystoneCreatorComponentInstance
|
|
|
103
112
|
await super.initialize(opts);
|
|
104
113
|
}
|
|
105
114
|
|
|
115
|
+
override async disconnected(): Promise<void> {
|
|
116
|
+
if (this.resendCooldownInterval) {
|
|
117
|
+
clearInterval(this.resendCooldownInterval);
|
|
118
|
+
this.resendCooldownInterval = null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private startResendCooldownTimer({ btnId, durationSecs = 30 }: { btnId: string; durationSecs?: number }): void {
|
|
123
|
+
const btn = this.shadowRoot?.getElementById(btnId) as HTMLButtonElement | null;
|
|
124
|
+
if (!btn) return;
|
|
125
|
+
if (this.resendCooldownInterval) {
|
|
126
|
+
clearInterval(this.resendCooldownInterval);
|
|
127
|
+
this.resendCooldownInterval = null;
|
|
128
|
+
}
|
|
129
|
+
let remaining = durationSecs;
|
|
130
|
+
btn.disabled = true;
|
|
131
|
+
btn.textContent = `Resend Code (${remaining}s)`;
|
|
132
|
+
this.resendCooldownInterval = setInterval(() => {
|
|
133
|
+
remaining -= 1;
|
|
134
|
+
if (remaining <= 0) {
|
|
135
|
+
clearInterval(this.resendCooldownInterval);
|
|
136
|
+
this.resendCooldownInterval = null;
|
|
137
|
+
btn.disabled = false;
|
|
138
|
+
btn.textContent = "Resend Code";
|
|
139
|
+
} else {
|
|
140
|
+
btn.textContent = `Resend Code (${remaining}s)`;
|
|
141
|
+
}
|
|
142
|
+
}, 1000);
|
|
143
|
+
}
|
|
144
|
+
|
|
106
145
|
override async created(): Promise<void> {
|
|
107
146
|
this.elements = {} as KeystoneCreatorElements;
|
|
108
147
|
|
|
@@ -116,6 +155,12 @@ export class KeystoneCreatorComponentInstance
|
|
|
116
155
|
this.elements.inputEmail = this.shadowRoot!.getElementById('input-email') as HTMLInputElement;
|
|
117
156
|
this.elements.inputSecret = this.shadowRoot!.getElementById('input-secret') as HTMLInputElement;
|
|
118
157
|
this.elements.checkboxMakeActive = this.shadowRoot!.getElementById('checkbox-make-active') as HTMLInputElement;
|
|
158
|
+
this.elements.inputPasskeyUsername = this.shadowRoot!.getElementById('input-passkey-username') as HTMLInputElement;
|
|
159
|
+
this.elements.inputPasskeyDescription = this.shadowRoot!.getElementById('input-passkey-description') as HTMLInputElement;
|
|
160
|
+
this.elements.inputPasskeyEmail = this.shadowRoot!.getElementById('input-passkey-email') as HTMLInputElement;
|
|
161
|
+
this.elements.inputPasskeyName = this.shadowRoot!.getElementById('input-passkey-name') as HTMLInputElement;
|
|
162
|
+
this.elements.checkboxPasskeyMakeActive = this.shadowRoot!.getElementById('checkbox-passkey-make-active') as HTMLInputElement;
|
|
163
|
+
this.elements.btnGeneratePasskey = this.shadowRoot!.getElementById('btn-generate-passkey') as HTMLButtonElement;
|
|
119
164
|
this.elements.statusArea = this.shadowRoot!.getElementById('status-area') as HTMLDivElement;
|
|
120
165
|
this.elements.statusText = this.shadowRoot!.getElementById('status-text') as HTMLParagraphElement;
|
|
121
166
|
|
|
@@ -150,49 +195,50 @@ export class KeystoneCreatorComponentInstance
|
|
|
150
195
|
this.hasExistingIdentities = false;
|
|
151
196
|
}
|
|
152
197
|
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
if (
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
198
|
+
const checkboxes = [this.elements!.checkboxMakeActive, this.elements!.checkboxPasskeyMakeActive];
|
|
199
|
+
for (const checkbox of checkboxes) {
|
|
200
|
+
if (checkbox) {
|
|
201
|
+
if (!this.hasExistingIdentities) {
|
|
202
|
+
checkbox.checked = true;
|
|
203
|
+
checkbox.disabled = true;
|
|
204
|
+
checkbox.title = "At least one active identity is required.";
|
|
205
|
+
} else {
|
|
206
|
+
checkbox.disabled = false;
|
|
207
|
+
checkbox.title = "";
|
|
208
|
+
}
|
|
162
209
|
}
|
|
163
210
|
}
|
|
164
211
|
}
|
|
165
212
|
|
|
166
|
-
override async disconnected(): Promise<void> {
|
|
167
|
-
// cleanup if needed
|
|
168
|
-
}
|
|
169
|
-
|
|
170
213
|
private initHandlers() {
|
|
171
|
-
// Mode Tab Switching (Quick vs Password-based)
|
|
214
|
+
// Mode Tab Switching (Quick vs Passkey vs Password-based)
|
|
172
215
|
const tabQuick = this.shadowRoot?.getElementById('tab-quick') as HTMLButtonElement | null;
|
|
216
|
+
const tabPasskey = this.shadowRoot?.getElementById('tab-passkey') as HTMLButtonElement | null;
|
|
173
217
|
const tabPassword = this.shadowRoot?.getElementById('tab-password') as HTMLButtonElement | null;
|
|
174
218
|
const panelQuick = this.shadowRoot?.getElementById('panel-quick') as HTMLDivElement | null;
|
|
219
|
+
const panelPasskey = this.shadowRoot?.getElementById('panel-passkey') as HTMLDivElement | null;
|
|
175
220
|
const panelPassword = this.shadowRoot?.getElementById('panel-password') as HTMLDivElement | null;
|
|
176
221
|
const subtitleQuick = this.shadowRoot?.getElementById('subtitle-quick') as HTMLParagraphElement | null;
|
|
222
|
+
const subtitlePasskey = this.shadowRoot?.getElementById('subtitle-passkey') as HTMLParagraphElement | null;
|
|
177
223
|
const subtitlePassword = this.shadowRoot?.getElementById('subtitle-password') as HTMLParagraphElement | null;
|
|
178
224
|
|
|
179
|
-
|
|
180
|
-
tabQuick
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
panelPassword?.classList.add('hidden');
|
|
184
|
-
subtitleQuick?.classList.remove('hidden');
|
|
185
|
-
subtitlePassword?.classList.add('hidden');
|
|
186
|
-
});
|
|
225
|
+
const switchTab = (mode: 'quick' | 'passkey' | 'password') => {
|
|
226
|
+
tabQuick?.classList.toggle('active', mode === 'quick');
|
|
227
|
+
tabPasskey?.classList.toggle('active', mode === 'passkey');
|
|
228
|
+
tabPassword?.classList.toggle('active', mode === 'password');
|
|
187
229
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
}
|
|
230
|
+
panelQuick?.classList.toggle('hidden', mode !== 'quick');
|
|
231
|
+
panelPasskey?.classList.toggle('hidden', mode !== 'passkey');
|
|
232
|
+
panelPassword?.classList.toggle('hidden', mode !== 'password');
|
|
233
|
+
|
|
234
|
+
subtitleQuick?.classList.toggle('hidden', mode !== 'quick');
|
|
235
|
+
subtitlePasskey?.classList.toggle('hidden', mode !== 'passkey');
|
|
236
|
+
subtitlePassword?.classList.toggle('hidden', mode !== 'password');
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
tabQuick?.addEventListener('click', () => switchTab('quick'));
|
|
240
|
+
tabPasskey?.addEventListener('click', () => switchTab('passkey'));
|
|
241
|
+
tabPassword?.addEventListener('click', () => switchTab('password'));
|
|
196
242
|
|
|
197
243
|
const btnGoogleSso = this.shadowRoot?.getElementById('btn-google-sso') as HTMLButtonElement | null;
|
|
198
244
|
btnGoogleSso?.addEventListener('click', () => this.handleSsoClick('google'));
|
|
@@ -216,11 +262,13 @@ export class KeystoneCreatorComponentInstance
|
|
|
216
262
|
const secQuickOtp = this.shadowRoot?.getElementById('section-quick-otp') as HTMLDivElement | null;
|
|
217
263
|
const inputQuickOtp = this.shadowRoot?.getElementById('input-quick-otp') as HTMLInputElement | null;
|
|
218
264
|
const btnQuickVerifyCode = this.shadowRoot?.getElementById('btn-quick-verify-code') as HTMLButtonElement | null;
|
|
265
|
+
const btnQuickResendCode = this.shadowRoot?.getElementById('btn-quick-resend-code') as HTMLButtonElement | null;
|
|
219
266
|
|
|
220
267
|
btnQuickSendCode?.addEventListener('click', () => this.handleQuickSendCode());
|
|
221
|
-
|
|
222
268
|
btnQuickVerifyCode?.addEventListener('click', () => this.handleQuickVerifyCode());
|
|
269
|
+
btnQuickResendCode?.addEventListener('click', () => this.handleQuickResendCode());
|
|
223
270
|
|
|
271
|
+
this.elements!.btnGeneratePasskey?.addEventListener('click', () => this.handleGeneratePasskey());
|
|
224
272
|
this.elements!.btnGenerateEl.addEventListener('click', () => this.handleGenerate());
|
|
225
273
|
|
|
226
274
|
this.shadowRoot!.getElementById('btn-switch-sign-in')?.addEventListener('click', (e) => {
|
|
@@ -233,6 +281,16 @@ export class KeystoneCreatorComponentInstance
|
|
|
233
281
|
});
|
|
234
282
|
|
|
235
283
|
// Clear invalid styling when user inputs text
|
|
284
|
+
this.elements!.inputPasskeyUsername?.addEventListener('input', () => {
|
|
285
|
+
this.elements!.inputPasskeyUsername.classList.remove('invalid');
|
|
286
|
+
});
|
|
287
|
+
this.elements!.inputPasskeyDescription?.addEventListener('input', () => {
|
|
288
|
+
this.elements!.inputPasskeyDescription.classList.remove('invalid');
|
|
289
|
+
});
|
|
290
|
+
this.elements!.inputPasskeyEmail?.addEventListener('input', () => {
|
|
291
|
+
this.elements!.inputPasskeyEmail.classList.remove('invalid');
|
|
292
|
+
});
|
|
293
|
+
|
|
236
294
|
this.elements!.inputUsername?.addEventListener('input', () => {
|
|
237
295
|
this.elements!.inputUsername.classList.remove('invalid');
|
|
238
296
|
});
|
|
@@ -315,22 +373,14 @@ export class KeystoneCreatorComponentInstance
|
|
|
315
373
|
return;
|
|
316
374
|
}
|
|
317
375
|
|
|
318
|
-
const delegateSecret = await getOrCreateDeviceDelegateSecret({ primaryTjpAddr });
|
|
319
376
|
const email = resLogin.userInfo?.email || '';
|
|
320
377
|
const username = email ? email.split('@')[0] : 'user';
|
|
321
|
-
const custodianSyncProfileName = process.env.KEYSTONE_PROFILE_CUSTODIAN_SYNC;
|
|
322
|
-
if (!custodianSyncProfileName || !custodianSyncProfileName.trim()) {
|
|
323
|
-
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)');
|
|
324
|
-
}
|
|
325
378
|
|
|
326
|
-
const delegateKeystone = await
|
|
327
|
-
|
|
379
|
+
const { delegateKeystone, delegateSecret } = await createOAuth2DeviceSyncDelegate({
|
|
380
|
+
providerId,
|
|
328
381
|
username,
|
|
329
|
-
deviceTag: generateDefaultDeviceTag(),
|
|
330
382
|
primaryTjpAddr,
|
|
331
|
-
delegateSecret,
|
|
332
383
|
custodianReqs,
|
|
333
|
-
additionalFrameDetails: { client: 'space-gib-web' },
|
|
334
384
|
metaspace,
|
|
335
385
|
space,
|
|
336
386
|
});
|
|
@@ -395,6 +445,7 @@ export class KeystoneCreatorComponentInstance
|
|
|
395
445
|
if (resCode.success) {
|
|
396
446
|
secQuickOtp?.classList.remove('hidden');
|
|
397
447
|
this.setStatus("✉️ 6-digit verification code sent to your email!", "success");
|
|
448
|
+
this.startResendCooldownTimer({ btnId: 'btn-quick-resend-code', durationSecs: 30 });
|
|
398
449
|
} else {
|
|
399
450
|
this.setStatus(resCode.message || "Failed to dispatch verification code.", "error");
|
|
400
451
|
}
|
|
@@ -410,6 +461,54 @@ export class KeystoneCreatorComponentInstance
|
|
|
410
461
|
}
|
|
411
462
|
}
|
|
412
463
|
|
|
464
|
+
/**
|
|
465
|
+
* Triggered by the "Resend Code" button in the quick email panel.
|
|
466
|
+
*/
|
|
467
|
+
protected async handleQuickResendCode(): Promise<void> {
|
|
468
|
+
const lc = `${this.lc}[${this.handleQuickResendCode.name}]`;
|
|
469
|
+
const btnQuickResendCode = this.shadowRoot?.getElementById('btn-quick-resend-code') as HTMLButtonElement | null;
|
|
470
|
+
const inputQuickEmail = this.shadowRoot?.getElementById('input-quick-email') as HTMLInputElement | null;
|
|
471
|
+
const inputQuickOtp = this.shadowRoot?.getElementById('input-quick-otp') as HTMLInputElement | null;
|
|
472
|
+
|
|
473
|
+
const email = (inputQuickEmail?.value || '').trim();
|
|
474
|
+
if (!email || !validateEmail(email)) {
|
|
475
|
+
this.setStatus("Please enter a valid email address.", "error");
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
if (btnQuickResendCode) {
|
|
480
|
+
btnQuickResendCode.disabled = true;
|
|
481
|
+
btnQuickResendCode.textContent = "Sending...";
|
|
482
|
+
}
|
|
483
|
+
this.setStatus("Resending verification code...", "info");
|
|
484
|
+
|
|
485
|
+
try {
|
|
486
|
+
const resCode = await getApiBridge().postVerificationCode({ email });
|
|
487
|
+
if (resCode.success) {
|
|
488
|
+
this.setStatus(`✉️ Fresh 6-digit verification code sent to ${email}!`, "success");
|
|
489
|
+
this.startResendCooldownTimer({ btnId: 'btn-quick-resend-code', durationSecs: 30 });
|
|
490
|
+
if (inputQuickOtp) {
|
|
491
|
+
inputQuickOtp.value = '';
|
|
492
|
+
inputQuickOtp.focus();
|
|
493
|
+
}
|
|
494
|
+
} else {
|
|
495
|
+
this.setStatus(resCode.message || "Failed to resend verification code.", "error");
|
|
496
|
+
if (btnQuickResendCode) {
|
|
497
|
+
btnQuickResendCode.disabled = false;
|
|
498
|
+
btnQuickResendCode.textContent = "Resend Code";
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
} catch (error) {
|
|
502
|
+
const emsg = extractErrorMsg(error);
|
|
503
|
+
console.error(`${lc} ${emsg}`);
|
|
504
|
+
this.setStatus(`Failed to resend verification code: ${emsg}`, "error");
|
|
505
|
+
if (btnQuickResendCode) {
|
|
506
|
+
btnQuickResendCode.disabled = false;
|
|
507
|
+
btnQuickResendCode.textContent = "Resend Code";
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
413
512
|
/**
|
|
414
513
|
* Triggered by the "Verify Code" button.
|
|
415
514
|
* Submits 6-digit OTP code to custodian server, builds local sync delegate keystone,
|
|
@@ -466,21 +565,10 @@ export class KeystoneCreatorComponentInstance
|
|
|
466
565
|
return;
|
|
467
566
|
}
|
|
468
567
|
|
|
469
|
-
const delegateSecret = await
|
|
470
|
-
|
|
471
|
-
const custodianSyncProfileName = process.env.KEYSTONE_PROFILE_CUSTODIAN_SYNC;
|
|
472
|
-
if (!custodianSyncProfileName || !custodianSyncProfileName.trim()) {
|
|
473
|
-
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)');
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
const delegateKeystone = await createSyncDelegateKeystone({
|
|
477
|
-
profileName: custodianSyncProfileName.trim() as any,
|
|
478
|
-
username,
|
|
479
|
-
deviceTag: generateDefaultDeviceTag(),
|
|
568
|
+
const { delegateKeystone, delegateSecret } = await createEmailDeviceSyncDelegate({
|
|
569
|
+
email,
|
|
480
570
|
primaryTjpAddr,
|
|
481
|
-
delegateSecret,
|
|
482
571
|
custodianReqs,
|
|
483
|
-
additionalFrameDetails: { client: 'space-gib-web' },
|
|
484
572
|
metaspace,
|
|
485
573
|
space,
|
|
486
574
|
});
|
|
@@ -523,6 +611,399 @@ export class KeystoneCreatorComponentInstance
|
|
|
523
611
|
}
|
|
524
612
|
}
|
|
525
613
|
|
|
614
|
+
/**
|
|
615
|
+
* Triggered by the "Create Identity with Passkey / Biometrics" button.
|
|
616
|
+
* Orchestrates: WebAuthn Credential -> PRF Secret Evaluation -> Local Passkey Genesis -> Graph Collection -> Server Sync.
|
|
617
|
+
*/
|
|
618
|
+
protected async handleGeneratePasskey() {
|
|
619
|
+
const lc = `${this.lc}[${this.handleGeneratePasskey.name}]`;
|
|
620
|
+
const btn = this.shadowRoot?.getElementById('btn-generate-passkey') as HTMLButtonElement | null;
|
|
621
|
+
try {
|
|
622
|
+
console.log(`${lc} starting passkey genesis orchestration...`);
|
|
623
|
+
const humanEyeDelay = 500;
|
|
624
|
+
this.setStatus("Initializing passkey genesis...", "info");
|
|
625
|
+
|
|
626
|
+
const inputUsername = this.elements!.inputPasskeyUsername;
|
|
627
|
+
const inputDesc = this.elements!.inputPasskeyDescription;
|
|
628
|
+
const inputEmail = this.elements!.inputPasskeyEmail;
|
|
629
|
+
const inputName = this.elements!.inputPasskeyName;
|
|
630
|
+
const checkboxActive = this.elements!.checkboxPasskeyMakeActive;
|
|
631
|
+
|
|
632
|
+
const username = (inputUsername?.value || '').trim();
|
|
633
|
+
if (inputUsername) inputUsername.value = username;
|
|
634
|
+
|
|
635
|
+
if (!this.validateInputPattern({
|
|
636
|
+
input: inputUsername!,
|
|
637
|
+
pattern: KEYSTONE_USERNAME_REGEXP,
|
|
638
|
+
errorMessage: "Username must be 1-63 characters (alphanumeric, underscores, hyphens, dots).",
|
|
639
|
+
required: true
|
|
640
|
+
})) {
|
|
641
|
+
this.setStatus("Invalid username. Must be 1-63 characters (alphanumeric, _, -, .).", "error");
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
const description = (inputDesc?.value || '').trim();
|
|
646
|
+
if (inputDesc) inputDesc.value = description;
|
|
647
|
+
if (inputDesc && !this.validateInputPattern({
|
|
648
|
+
input: inputDesc,
|
|
649
|
+
pattern: KEYSTONE_DESCRIPTION_REGEXP,
|
|
650
|
+
errorMessage: "Description must be 0-128 characters.",
|
|
651
|
+
required: false
|
|
652
|
+
})) {
|
|
653
|
+
this.setStatus("Invalid description. Max 128 chars.", "error");
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
const email = (inputEmail?.value || '').trim();
|
|
658
|
+
if (inputEmail) inputEmail.value = email;
|
|
659
|
+
if (email && !validateEmail(email)) {
|
|
660
|
+
inputEmail?.classList.add('invalid');
|
|
661
|
+
this.setStatus("Invalid email address format.", "error");
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
if (btn) {
|
|
666
|
+
btn.disabled = true;
|
|
667
|
+
btn.textContent = 'Awaiting Biometrics / Security Key...';
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// Pre-check email availability on server if email provided
|
|
671
|
+
if (email) {
|
|
672
|
+
this.showBusy({
|
|
673
|
+
isBusy: true,
|
|
674
|
+
title: 'Checking Email Availability...',
|
|
675
|
+
msg: 'Checking server identity index...',
|
|
676
|
+
animationEmoji: '🔍'
|
|
677
|
+
});
|
|
678
|
+
const resCheck = await getApiBridge().checkEmailAvailability({ email });
|
|
679
|
+
if (!resCheck.available) {
|
|
680
|
+
const msg = resCheck.message || "Email/Identity is already registered.";
|
|
681
|
+
this.setStatus(msg, "error");
|
|
682
|
+
this.showBusy({ isBusy: false });
|
|
683
|
+
if (btn) {
|
|
684
|
+
btn.disabled = false;
|
|
685
|
+
btn.textContent = '🛡️ Create Identity with Passkey / Biometrics';
|
|
686
|
+
}
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// 1. WebAuthn Passkey Registration & PRF Secret Evaluation
|
|
692
|
+
this.showBusy({
|
|
693
|
+
isBusy: true,
|
|
694
|
+
title: 'Touch Biometrics or Security Key',
|
|
695
|
+
msg: 'Please touch your fingerprint sensor, Face ID, or insert your USB security key when prompted...',
|
|
696
|
+
animationEmoji: '🛡️'
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
const selectAttachment = this.shadowRoot?.querySelector('#select-passkey-attachment') as HTMLSelectElement | null;
|
|
700
|
+
const attachmentVal = selectAttachment?.value;
|
|
701
|
+
const authenticatorAttachment: AuthenticatorAttachment | undefined =
|
|
702
|
+
(attachmentVal === 'cross-platform' || attachmentVal === 'platform')
|
|
703
|
+
? attachmentVal
|
|
704
|
+
: undefined;
|
|
705
|
+
|
|
706
|
+
const rawSalt = crypto.getRandomValues(new Uint8Array(32));
|
|
707
|
+
const saltHex = bufferToHex(rawSalt.buffer as ArrayBuffer);
|
|
708
|
+
const regResult = await registerPasskeyCredential({
|
|
709
|
+
username,
|
|
710
|
+
displayName: username,
|
|
711
|
+
salt: saltHex,
|
|
712
|
+
authenticatorAttachment,
|
|
713
|
+
userVerification: 'preferred'
|
|
714
|
+
});
|
|
715
|
+
const { credentialId, transports } = regResult;
|
|
716
|
+
|
|
717
|
+
let passkeySecret = regResult.passkeySecret;
|
|
718
|
+
if (!passkeySecret) {
|
|
719
|
+
const prfResult = await evaluatePasskeyPrf({
|
|
720
|
+
credentialId,
|
|
721
|
+
salt: saltHex,
|
|
722
|
+
transports,
|
|
723
|
+
userVerification: 'preferred'
|
|
724
|
+
});
|
|
725
|
+
passkeySecret = prfResult.passkeySecret;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
const passkeyName = (inputName?.value || '').trim() || generateDefaultDeviceTag();
|
|
729
|
+
const passkeyTag = getSaferSubstring({ text: passkeyName, length: 12, keepLiterals: ['-'] }) || 'key';
|
|
730
|
+
const poolId = `passkey-manage-${passkeyTag}`;
|
|
731
|
+
|
|
732
|
+
const passkeyEntry: KeystonePasskeyEntry = {
|
|
733
|
+
credentialId,
|
|
734
|
+
poolId,
|
|
735
|
+
name: passkeyName,
|
|
736
|
+
salt: saltHex,
|
|
737
|
+
createdUTC: new Date().toISOString(),
|
|
738
|
+
...(transports && transports.length > 0 ? { transports } : {})
|
|
739
|
+
};
|
|
740
|
+
|
|
741
|
+
this.showBusy({
|
|
742
|
+
isBusy: true,
|
|
743
|
+
title: 'Generating Identity...',
|
|
744
|
+
msg: 'Compiling sovereign passkey challenge pools...',
|
|
745
|
+
animationEmoji: '⚙️'
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
// 2. Prepare Sovereign Keystone Configs
|
|
749
|
+
const sovereignPrimaryProfileName = process.env.KEYSTONE_PROFILE_SOVEREIGN_PRIMARY;
|
|
750
|
+
if (!sovereignPrimaryProfileName || !sovereignPrimaryProfileName.trim()) {
|
|
751
|
+
throw new Error('KEYSTONE_PROFILE_SOVEREIGN_PRIMARY environment variable is missing.');
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
const builder = KeystoneProfileBuilder.buildKeystone(sovereignPrimaryProfileName.trim() as any)
|
|
755
|
+
.withUsername(username)
|
|
756
|
+
.withDescription(description);
|
|
757
|
+
if (email) {
|
|
758
|
+
builder.withEmails([email]);
|
|
759
|
+
}
|
|
760
|
+
builder.withDetails({
|
|
761
|
+
client: 'space-gib-web',
|
|
762
|
+
role: 'domain-identity',
|
|
763
|
+
passkeys: {
|
|
764
|
+
[credentialId]: passkeyEntry
|
|
765
|
+
}
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
const configs = await builder.compileConfigs();
|
|
769
|
+
// Re-label manage pool to passkey pool id
|
|
770
|
+
for (const cfg of configs) {
|
|
771
|
+
if (cfg.id === 'manage') {
|
|
772
|
+
cfg.id = poolId;
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// 3. Prepare Metaspace & Local Space
|
|
777
|
+
const metaspace = await getGlobalMetaspace_waitIfNeeded();
|
|
778
|
+
const space = await metaspace.getLocalUserSpace({ lock: false });
|
|
779
|
+
if (!space) {
|
|
780
|
+
this.setStatus("Failed to access local user space.", "error");
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// 4. Perform Local Genesis
|
|
785
|
+
this.showBusy({
|
|
786
|
+
isBusy: true,
|
|
787
|
+
title: 'Generating Identity...',
|
|
788
|
+
msg: 'Generating cryptographic domain identity keys...',
|
|
789
|
+
animationEmoji: '🔑'
|
|
790
|
+
});
|
|
791
|
+
|
|
792
|
+
const keystoneService = new KeystoneService_V1();
|
|
793
|
+
const keystoneIbGib = await keystoneService.genesis({
|
|
794
|
+
masterSecret: passkeySecret,
|
|
795
|
+
emails: email ? [email] : undefined,
|
|
796
|
+
credentialIds: [credentialId],
|
|
797
|
+
configs,
|
|
798
|
+
metaspace,
|
|
799
|
+
space,
|
|
800
|
+
frameDetails: builder.getFrameDetails(),
|
|
801
|
+
isPrimary: true
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
const primaryTjpAddr = getTjpAddr({ ibGib: keystoneIbGib, defaultIfNone: 'incomingAddr' })!;
|
|
805
|
+
console.log(`${lc} passkey local genesis complete: ${primaryTjpAddr}`);
|
|
806
|
+
await delay(humanEyeDelay);
|
|
807
|
+
this.setStatus("Local genesis complete. Creating passkey sync delegate...", "info");
|
|
808
|
+
|
|
809
|
+
// 5. Create Passkey Device Sync Delegate
|
|
810
|
+
this.showBusy({
|
|
811
|
+
isBusy: true,
|
|
812
|
+
title: 'Creating Sync Delegate...',
|
|
813
|
+
msg: 'Generating cryptographic sync delegate keys...',
|
|
814
|
+
animationEmoji: '🔑'
|
|
815
|
+
});
|
|
816
|
+
|
|
817
|
+
const { delegateKeystone: delegateKeystoneIbGib, deviceTag, delegateSecret } = await createPasskeyDeviceSyncDelegate({
|
|
818
|
+
username,
|
|
819
|
+
primaryTjpAddr,
|
|
820
|
+
passkeySecret,
|
|
821
|
+
transports,
|
|
822
|
+
metaspace,
|
|
823
|
+
space,
|
|
824
|
+
});
|
|
825
|
+
|
|
826
|
+
// 6. Register Delegate locally on primary keystone
|
|
827
|
+
const evolvedParentKeystoneIbGib = await keystoneService.registerDelegate({
|
|
828
|
+
parentKeystone: keystoneIbGib,
|
|
829
|
+
delegateKeystone: delegateKeystoneIbGib,
|
|
830
|
+
masterSecret: passkeySecret,
|
|
831
|
+
signingPoolId: poolId,
|
|
832
|
+
allowedVerbs: [KEYSTONE_VERB_SYNC, KEYSTONE_VERB_CONNECT],
|
|
833
|
+
deviceName: deviceTag,
|
|
834
|
+
metaspace,
|
|
835
|
+
space,
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
// 7. Post to Server & Handle Verification
|
|
839
|
+
this.setStatus("Syncing genesis keystone to server...", "info");
|
|
840
|
+
this.showBusy({
|
|
841
|
+
isBusy: true,
|
|
842
|
+
title: 'Syncing to Server...',
|
|
843
|
+
msg: 'Staging pending genesis keystone on server...',
|
|
844
|
+
animationEmoji: '🔄'
|
|
845
|
+
});
|
|
846
|
+
|
|
847
|
+
const resSync = await getApiBridge().postGenesisKeystone(keystoneIbGib);
|
|
848
|
+
if (!resSync.success) {
|
|
849
|
+
const msg = `Sync failed: ${resSync.message}`;
|
|
850
|
+
console.error(`${lc} ${msg}`);
|
|
851
|
+
this.setStatus(msg, "error");
|
|
852
|
+
this.showBusy({ isBusy: false });
|
|
853
|
+
if (btn) {
|
|
854
|
+
btn.disabled = false;
|
|
855
|
+
btn.textContent = '🛡️ Create Identity with Passkey / Biometrics';
|
|
856
|
+
}
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
const isPending = (resSync as any).pending || (resSync as any).statusCode === 202;
|
|
861
|
+
|
|
862
|
+
if (isPending) {
|
|
863
|
+
console.log(`${lc} Genesis keystone quarantined in pending staging area. Prompting for 6-digit verification code.`);
|
|
864
|
+
this.showBusy({ isBusy: false });
|
|
865
|
+
this.setFormDisabled(true);
|
|
866
|
+
|
|
867
|
+
if (!this.shadowRoot) { throw new Error(`(UNEXPECTED) this.shadowRoot falsy? (E: 6d316f5a2bf8e6a0286306468d35c826)`); }
|
|
868
|
+
const secVerify = shadowRoot_getElementById<HTMLDivElement>(this.shadowRoot, 'section-code-verification');
|
|
869
|
+
const inputVerifyCode = shadowRoot_getElementById<HTMLInputElement>(this.shadowRoot, 'input-verify-code');
|
|
870
|
+
const btnSubmitCode = shadowRoot_getElementById<HTMLButtonElement>(this.shadowRoot, 'btn-submit-code');
|
|
871
|
+
const btnResendCode = shadowRoot_getElementById<HTMLButtonElement>(this.shadowRoot, 'btn-resend-code');
|
|
872
|
+
|
|
873
|
+
secVerify.classList.remove('hidden');
|
|
874
|
+
secVerify.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
875
|
+
this.setStatus("✉️ Verification code sent to your email. Enter 6-digit code below:", "info");
|
|
876
|
+
this.startResendCooldownTimer({ btnId: 'btn-resend-code', durationSecs: 30 });
|
|
877
|
+
|
|
878
|
+
const onResendCode = async () => {
|
|
879
|
+
btnResendCode.disabled = true;
|
|
880
|
+
btnResendCode.textContent = "Sending...";
|
|
881
|
+
this.setStatus("Resending verification code to your email...", "info");
|
|
882
|
+
try {
|
|
883
|
+
const resResend = await getApiBridge().postVerificationCode({ email, primaryTjpAddr });
|
|
884
|
+
if (resResend.success) {
|
|
885
|
+
this.setStatus("✉️ Fresh 6-digit verification code sent to your email!", "success");
|
|
886
|
+
this.startResendCooldownTimer({ btnId: 'btn-resend-code', durationSecs: 30 });
|
|
887
|
+
inputVerifyCode.value = '';
|
|
888
|
+
inputVerifyCode.focus();
|
|
889
|
+
} else {
|
|
890
|
+
this.setStatus(resResend.message || "Failed to resend verification code.", "error");
|
|
891
|
+
btnResendCode.disabled = false;
|
|
892
|
+
btnResendCode.textContent = "Resend Code";
|
|
893
|
+
}
|
|
894
|
+
} catch (error) {
|
|
895
|
+
const emsg = extractErrorMsg(error);
|
|
896
|
+
console.error(`${lc} ${emsg}`);
|
|
897
|
+
this.setStatus(`Failed to resend verification code: ${emsg}`, "error");
|
|
898
|
+
btnResendCode.disabled = false;
|
|
899
|
+
btnResendCode.textContent = "Resend Code";
|
|
900
|
+
}
|
|
901
|
+
};
|
|
902
|
+
btnResendCode.addEventListener('click', onResendCode);
|
|
903
|
+
|
|
904
|
+
await new Promise<void>((resolve) => {
|
|
905
|
+
const onSubmitCode = async () => {
|
|
906
|
+
const code = inputVerifyCode.value.trim();
|
|
907
|
+
if (!code || code.length !== 6 || !/^\d{6}$/.test(code)) {
|
|
908
|
+
this.setStatus("Please enter a valid 6-digit numeric verification code (e.g. 482901).", "error");
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
btnSubmitCode.disabled = true;
|
|
913
|
+
btnSubmitCode.textContent = "Verifying...";
|
|
914
|
+
this.showBusy({
|
|
915
|
+
isBusy: true,
|
|
916
|
+
title: 'Verifying & Activating...',
|
|
917
|
+
msg: 'Great! Juust a moment...\n(Validating code, promoting server domain space, other security stuff...)',
|
|
918
|
+
animationEmoji: '🔐'
|
|
919
|
+
});
|
|
920
|
+
|
|
921
|
+
const resVerify = await getApiBridge().postVerifyCode({ email, primaryTjpAddr, code });
|
|
922
|
+
if (!resVerify.success) {
|
|
923
|
+
this.showBusy({ isBusy: false });
|
|
924
|
+
this.setStatus(resVerify.message || "Incorrect verification code.", "error");
|
|
925
|
+
btnSubmitCode.disabled = false;
|
|
926
|
+
btnSubmitCode.textContent = "Verify Code";
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
console.log(`${lc} Code verified & server domain promoted! Syncing delegate...`);
|
|
931
|
+
this.showBusy({
|
|
932
|
+
isBusy: true,
|
|
933
|
+
title: 'Syncing Delegate...',
|
|
934
|
+
msg: 'Posting evolved domain identity and sync delegate to server...',
|
|
935
|
+
animationEmoji: '⚡'
|
|
936
|
+
});
|
|
937
|
+
|
|
938
|
+
btnSubmitCode.removeEventListener('click', onSubmitCode);
|
|
939
|
+
secVerify.classList.add('hidden');
|
|
940
|
+
this.setFormDisabled(false);
|
|
941
|
+
|
|
942
|
+
// Now that server domain space is promoted, post evolved keystone + delegate
|
|
943
|
+
const resEvolve = await getApiBridge().putEvolveKeystone({
|
|
944
|
+
domainAddr: primaryTjpAddr,
|
|
945
|
+
keystoneIbGib: evolvedParentKeystoneIbGib,
|
|
946
|
+
relatedKeystoneIbGibs: [delegateKeystoneIbGib]
|
|
947
|
+
});
|
|
948
|
+
if (!resEvolve.success) {
|
|
949
|
+
console.warn(`${lc} Delegate sync warning after code verification: ${resEvolve.message}`);
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
resolve();
|
|
953
|
+
};
|
|
954
|
+
|
|
955
|
+
btnSubmitCode.addEventListener('click', onSubmitCode);
|
|
956
|
+
});
|
|
957
|
+
} else {
|
|
958
|
+
// Post evolved keystone + delegate keystone immediately
|
|
959
|
+
this.setStatus("Syncing delegate registration to server...", "info");
|
|
960
|
+
this.showBusy({
|
|
961
|
+
isBusy: true,
|
|
962
|
+
title: 'Syncing Delegate...',
|
|
963
|
+
msg: 'Posting evolved domain identity and sync delegate to server...',
|
|
964
|
+
animationEmoji: '🔄'
|
|
965
|
+
});
|
|
966
|
+
const resEvolve = await getApiBridge().putEvolveKeystone({
|
|
967
|
+
domainAddr: primaryTjpAddr,
|
|
968
|
+
keystoneIbGib: evolvedParentKeystoneIbGib,
|
|
969
|
+
relatedKeystoneIbGibs: [delegateKeystoneIbGib]
|
|
970
|
+
});
|
|
971
|
+
if (!resEvolve.success) {
|
|
972
|
+
const msg = `Delegate registration sync failed: ${resEvolve.message}`;
|
|
973
|
+
console.error(`${lc} ${msg}`);
|
|
974
|
+
this.setStatus(msg, "error");
|
|
975
|
+
this.showBusy({ isBusy: false });
|
|
976
|
+
if (btn) {
|
|
977
|
+
btn.disabled = false;
|
|
978
|
+
btn.textContent = '🛡️ Create Identity with Passkey / Biometrics';
|
|
979
|
+
}
|
|
980
|
+
return;
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
// 8. Finalize Onboarding
|
|
985
|
+
await this.finalizeIdentityOnboarding({
|
|
986
|
+
primaryKeystone: evolvedParentKeystoneIbGib,
|
|
987
|
+
delegateKeystone: delegateKeystoneIbGib,
|
|
988
|
+
primaryTjpAddr,
|
|
989
|
+
masterSecret: passkeySecret,
|
|
990
|
+
delegateSecret,
|
|
991
|
+
makeActive: checkboxActive?.checked ?? true
|
|
992
|
+
});
|
|
993
|
+
|
|
994
|
+
} catch (error) {
|
|
995
|
+
const emsg = extractErrorMsg(error);
|
|
996
|
+
console.error(`${lc} ${emsg}`, error);
|
|
997
|
+
this.setStatus(`Error: ${emsg}`, "error");
|
|
998
|
+
} finally {
|
|
999
|
+
this.showBusy({ isBusy: false });
|
|
1000
|
+
if (btn) {
|
|
1001
|
+
btn.disabled = false;
|
|
1002
|
+
btn.textContent = '🛡️ Create Identity with Passkey / Biometrics';
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
|
|
526
1007
|
/**
|
|
527
1008
|
* Triggered by the "Generate" button.
|
|
528
1009
|
* Orchestrates: Secret -> Local Genesis -> Graph Collection -> Server Sync.
|
|
@@ -666,21 +1147,10 @@ export class KeystoneCreatorComponentInstance
|
|
|
666
1147
|
animationEmoji: '🔑'
|
|
667
1148
|
});
|
|
668
1149
|
|
|
669
|
-
const delegateSecret = await
|
|
670
|
-
await setDeviceDelegateSecret({ primaryTjpAddr, delegateSecret });
|
|
671
|
-
|
|
672
|
-
const sovereignSyncProfileName = process.env.KEYSTONE_PROFILE_SOVEREIGN_SYNC;
|
|
673
|
-
if (!sovereignSyncProfileName || !sovereignSyncProfileName.trim()) {
|
|
674
|
-
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)');
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
const delegateKeystoneIbGib = await createSyncDelegateKeystone({
|
|
678
|
-
profileName: sovereignSyncProfileName.trim() as any,
|
|
1150
|
+
const { delegateKeystone: delegateKeystoneIbGib, deviceTag, delegateSecret } = await createSovereignDeviceSyncDelegate({
|
|
679
1151
|
username,
|
|
680
|
-
deviceTag: generateDefaultDeviceTag(),
|
|
681
1152
|
primaryTjpAddr,
|
|
682
|
-
|
|
683
|
-
additionalFrameDetails: { client: 'space-gib-web' },
|
|
1153
|
+
masterSecret,
|
|
684
1154
|
metaspace,
|
|
685
1155
|
space,
|
|
686
1156
|
});
|
|
@@ -701,9 +1171,11 @@ export class KeystoneCreatorComponentInstance
|
|
|
701
1171
|
parentKeystone: keystoneIbGib,
|
|
702
1172
|
delegateKeystone: delegateKeystoneIbGib,
|
|
703
1173
|
masterSecret,
|
|
1174
|
+
signingPoolId: 'manage',
|
|
704
1175
|
metaspace,
|
|
705
1176
|
space,
|
|
706
|
-
allowedVerbs: [KEYSTONE_VERB_SYNC, KEYSTONE_VERB_CONNECT]
|
|
1177
|
+
allowedVerbs: [KEYSTONE_VERB_SYNC, KEYSTONE_VERB_CONNECT],
|
|
1178
|
+
deviceName: deviceTag,
|
|
707
1179
|
});
|
|
708
1180
|
const evolvedParentAddr = getIbGibAddr({ ibGib: evolvedParentKeystoneIbGib });
|
|
709
1181
|
console.log(`${lc} delegate registered. evolved parent address: ${evolvedParentAddr}`);
|
|
@@ -746,19 +1218,31 @@ export class KeystoneCreatorComponentInstance
|
|
|
746
1218
|
secVerify.classList.remove('hidden');
|
|
747
1219
|
secVerify.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
748
1220
|
this.setStatus("✉️ Verification code sent to your email. Enter 6-digit code below:", "info");
|
|
1221
|
+
this.startResendCooldownTimer({ btnId: 'btn-resend-code', durationSecs: 30 });
|
|
749
1222
|
|
|
750
1223
|
const onResendCode = async () => {
|
|
751
1224
|
btnResendCode.disabled = true;
|
|
752
1225
|
btnResendCode.textContent = "Sending...";
|
|
753
1226
|
this.setStatus("Resending verification code to your email...", "info");
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
1227
|
+
try {
|
|
1228
|
+
const resResend = await getApiBridge().postVerificationCode({ email, primaryTjpAddr: primaryTjpAddr });
|
|
1229
|
+
if (resResend.success) {
|
|
1230
|
+
this.setStatus("✉️ Fresh 6-digit verification code sent to your email!", "success");
|
|
1231
|
+
this.startResendCooldownTimer({ btnId: 'btn-resend-code', durationSecs: 30 });
|
|
1232
|
+
inputVerifyCode.value = '';
|
|
1233
|
+
inputVerifyCode.focus();
|
|
1234
|
+
} else {
|
|
1235
|
+
this.setStatus(resResend.message || "Failed to resend verification code.", "error");
|
|
1236
|
+
btnResendCode.disabled = false;
|
|
1237
|
+
btnResendCode.textContent = "Resend Code";
|
|
1238
|
+
}
|
|
1239
|
+
} catch (error) {
|
|
1240
|
+
const emsg = extractErrorMsg(error);
|
|
1241
|
+
console.error(`${lc} ${emsg}`);
|
|
1242
|
+
this.setStatus(`Failed to resend verification code: ${emsg}`, "error");
|
|
1243
|
+
btnResendCode.disabled = false;
|
|
1244
|
+
btnResendCode.textContent = "Resend Code";
|
|
759
1245
|
}
|
|
760
|
-
btnResendCode.disabled = false;
|
|
761
|
-
btnResendCode.textContent = "Resend Code";
|
|
762
1246
|
};
|
|
763
1247
|
btnResendCode.addEventListener('click', onResendCode);
|
|
764
1248
|
|