@ibgib/web-gib 0.0.60 → 0.0.62
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.d.mts.map +1 -1
- package/dist/helpers.mjs +1 -0
- package/dist/helpers.mjs.map +1 -1
- package/dist/helpers.web.d.mts +3 -1
- package/dist/helpers.web.d.mts.map +1 -1
- package/dist/helpers.web.mjs +17 -1
- package/dist/helpers.web.mjs.map +1 -1
- package/dist/identity/device-delegate-helper.d.mts +91 -0
- package/dist/identity/device-delegate-helper.d.mts.map +1 -0
- package/dist/identity/device-delegate-helper.mjs +190 -0
- package/dist/identity/device-delegate-helper.mjs.map +1 -0
- package/dist/identity/device-delegate.respec.d.mts +2 -0
- package/dist/identity/device-delegate.respec.d.mts.map +1 -0
- package/dist/identity/device-delegate.respec.mjs +106 -0
- package/dist/identity/device-delegate.respec.mjs.map +1 -0
- package/dist/identity/keystone-sync-service.d.mts +18 -0
- package/dist/identity/keystone-sync-service.d.mts.map +1 -1
- package/dist/identity/keystone-sync-service.mjs +77 -1
- package/dist/identity/keystone-sync-service.mjs.map +1 -1
- package/dist/identity/passkey-helper.d.mts +72 -0
- package/dist/identity/passkey-helper.d.mts.map +1 -0
- package/dist/identity/passkey-helper.mjs +237 -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 +2 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/dist/storage/storage-helpers.web.d.mts +9 -0
- package/dist/storage/storage-helpers.web.d.mts.map +1 -1
- package/dist/storage/storage-helpers.web.mjs +42 -0
- package/dist/storage/storage-helpers.web.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 +31 -43
- package/dist/ui/component/identity/add-device/add-device.mjs.map +1 -1
- 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 +42 -3
- package/dist/ui/component/identity/keystone-creator/keystone-creator.mjs +524 -104
- package/dist/ui/component/identity/keystone-creator/keystone-creator.mjs.map +1 -1
- package/dist/ui/component/identity/keystone-details/keystone-details.d.mts +19 -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 +38 -0
- package/dist/ui/component/identity/keystone-details/keystone-details.mjs +1217 -132
- 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-constants.d.mts +1 -0
- package/dist/ui/ui-constants.d.mts.map +1 -1
- package/dist/ui/ui-constants.mjs +1 -0
- package/dist/ui/ui-constants.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 +110 -6
- package/dist/ui/ui-helpers.mjs.map +1 -1
- package/dist/ui/ui-types.d.mts +5 -0
- package/dist/ui/ui-types.d.mts.map +1 -1
- package/package.json +2 -2
- package/src/AUTO-GENERATED-version.mts +1 -1
- package/src/helpers.mts +1 -0
- package/src/helpers.web.mts +22 -2
- package/src/identity/custodian-delegate-helper.mts.bak +7 -0
- package/src/identity/device-delegate-helper.mts +268 -0
- package/src/identity/device-delegate.respec.mts +125 -0
- package/src/identity/keystone-sync-service.mts +95 -2
- package/src/identity/passkey-helper.mts +304 -0
- package/src/identity/passkey.respec.mts +194 -0
- package/src/index.mts +2 -2
- package/src/storage/storage-helpers.web.mts +47 -0
- package/src/ui/component/identity/add-device/add-device.mts +37 -48
- package/src/ui/component/identity/keystone-creator/keystone-creator.html +42 -3
- package/src/ui/component/identity/keystone-creator/keystone-creator.mts +568 -111
- package/src/ui/component/identity/keystone-details/keystone-details.html +38 -0
- package/src/ui/component/identity/keystone-details/keystone-details.mts +1290 -142
- package/src/ui/component/identity/remove-device/remove-device.mts +1 -0
- package/src/ui/ui-constants.mts +1 -0
- package/src/ui/ui-helpers.mts +118 -5
- package/src/ui/ui-types.mts +6 -0
- package/dist/identity/custodian-delegate-helper.d.mts +0 -22
- package/dist/identity/custodian-delegate-helper.d.mts.map +0 -1
- package/dist/identity/custodian-delegate-helper.mjs +0 -67
- package/dist/identity/custodian-delegate-helper.mjs.map +0 -1
- package/src/identity/custodian-delegate-helper.mts +0 -86
|
@@ -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";
|
|
@@ -14,7 +14,7 @@ import { toDto, getTjpAddr, getTimelinesGroupedByTjp } from "@ibgib/core-gib/dis
|
|
|
14
14
|
import { FlatIbGibGraph } from "@ibgib/core-gib/dist/common/other/graph-types.mjs";
|
|
15
15
|
import { validateEmail } from "@ibgib/core-gib/dist/keystone/keystone-helpers.mjs";
|
|
16
16
|
import { KEYSTONE_USERNAME_REGEXP, KEYSTONE_DESCRIPTION_REGEXP, KEYSTONE_VERB_SYNC, KEYSTONE_VERB_CONNECT } from "@ibgib/core-gib/dist/keystone/keystone-constants.mjs";
|
|
17
|
-
import { deriveDelegateSecret } from "@ibgib/core-gib/dist/keystone/
|
|
17
|
+
import { deriveDelegateSecret, createSyncDelegateKeystone } from "@ibgib/core-gib/dist/keystone/delegate/delegate-helpers.mjs";
|
|
18
18
|
|
|
19
19
|
import { getGlobalMetaspace_waitIfNeeded, } from "../../../../helpers.mjs";
|
|
20
20
|
import { IbGibDynamicComponentMetaBase, IbGibFormInstanceBase, } from "../../ibgib-dynamic-component-bases.mjs";
|
|
@@ -24,8 +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 {
|
|
27
|
+
import { getOrCreateDeviceDelegateSecret, setDeviceDelegateSecret, createSovereignDeviceSyncDelegate, createOAuth2DeviceSyncDelegate, createEmailDeviceSyncDelegate } from "../../../../identity/device-delegate-helper.mjs";
|
|
28
28
|
import { executeSsoOAuthPopup } from "../../../../identity/sso-popup-helper.mjs";
|
|
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";
|
|
29
32
|
import { EVENT_IBGIB_IDENTITY_REQUEST_CHANGE, EVENT_IBGIB_UI_MESSAGE } from "../../../ui-constants.mjs";
|
|
30
33
|
import { alertUser, shadowRoot_getElementById } from "../../../../helpers.web.mjs";
|
|
31
34
|
|
|
@@ -80,6 +83,12 @@ interface KeystoneCreatorElements extends ElementsBase {
|
|
|
80
83
|
inputEmail: HTMLInputElement;
|
|
81
84
|
inputSecret: HTMLInputElement;
|
|
82
85
|
checkboxMakeActive: HTMLInputElement;
|
|
86
|
+
inputPasskeyUsername: HTMLInputElement;
|
|
87
|
+
inputPasskeyDescription: HTMLInputElement;
|
|
88
|
+
inputPasskeyEmail: HTMLInputElement;
|
|
89
|
+
inputPasskeyName: HTMLInputElement;
|
|
90
|
+
checkboxPasskeyMakeActive: HTMLInputElement;
|
|
91
|
+
btnGeneratePasskey: HTMLButtonElement;
|
|
83
92
|
statusArea: HTMLDivElement;
|
|
84
93
|
statusText: HTMLParagraphElement;
|
|
85
94
|
}
|
|
@@ -93,6 +102,7 @@ export class KeystoneCreatorComponentInstance
|
|
|
93
102
|
|
|
94
103
|
protected lc: string = `[KeystoneCreatorComponentInstance]`;
|
|
95
104
|
private hasExistingIdentities = false;
|
|
105
|
+
private resendCooldownInterval: any = null;
|
|
96
106
|
|
|
97
107
|
constructor() {
|
|
98
108
|
super();
|
|
@@ -102,6 +112,36 @@ export class KeystoneCreatorComponentInstance
|
|
|
102
112
|
await super.initialize(opts);
|
|
103
113
|
}
|
|
104
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
|
+
|
|
105
145
|
override async created(): Promise<void> {
|
|
106
146
|
this.elements = {} as KeystoneCreatorElements;
|
|
107
147
|
|
|
@@ -115,6 +155,12 @@ export class KeystoneCreatorComponentInstance
|
|
|
115
155
|
this.elements.inputEmail = this.shadowRoot!.getElementById('input-email') as HTMLInputElement;
|
|
116
156
|
this.elements.inputSecret = this.shadowRoot!.getElementById('input-secret') as HTMLInputElement;
|
|
117
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;
|
|
118
164
|
this.elements.statusArea = this.shadowRoot!.getElementById('status-area') as HTMLDivElement;
|
|
119
165
|
this.elements.statusText = this.shadowRoot!.getElementById('status-text') as HTMLParagraphElement;
|
|
120
166
|
|
|
@@ -149,49 +195,50 @@ export class KeystoneCreatorComponentInstance
|
|
|
149
195
|
this.hasExistingIdentities = false;
|
|
150
196
|
}
|
|
151
197
|
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
if (
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
+
}
|
|
161
209
|
}
|
|
162
210
|
}
|
|
163
211
|
}
|
|
164
212
|
|
|
165
|
-
override async disconnected(): Promise<void> {
|
|
166
|
-
// cleanup if needed
|
|
167
|
-
}
|
|
168
|
-
|
|
169
213
|
private initHandlers() {
|
|
170
|
-
// Mode Tab Switching (Quick vs Password-based)
|
|
214
|
+
// Mode Tab Switching (Quick vs Passkey vs Password-based)
|
|
171
215
|
const tabQuick = this.shadowRoot?.getElementById('tab-quick') as HTMLButtonElement | null;
|
|
216
|
+
const tabPasskey = this.shadowRoot?.getElementById('tab-passkey') as HTMLButtonElement | null;
|
|
172
217
|
const tabPassword = this.shadowRoot?.getElementById('tab-password') as HTMLButtonElement | null;
|
|
173
218
|
const panelQuick = this.shadowRoot?.getElementById('panel-quick') as HTMLDivElement | null;
|
|
219
|
+
const panelPasskey = this.shadowRoot?.getElementById('panel-passkey') as HTMLDivElement | null;
|
|
174
220
|
const panelPassword = this.shadowRoot?.getElementById('panel-password') as HTMLDivElement | null;
|
|
175
221
|
const subtitleQuick = this.shadowRoot?.getElementById('subtitle-quick') as HTMLParagraphElement | null;
|
|
222
|
+
const subtitlePasskey = this.shadowRoot?.getElementById('subtitle-passkey') as HTMLParagraphElement | null;
|
|
176
223
|
const subtitlePassword = this.shadowRoot?.getElementById('subtitle-password') as HTMLParagraphElement | null;
|
|
177
224
|
|
|
178
|
-
|
|
179
|
-
tabQuick
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
panelPassword?.classList.add('hidden');
|
|
183
|
-
subtitleQuick?.classList.remove('hidden');
|
|
184
|
-
subtitlePassword?.classList.add('hidden');
|
|
185
|
-
});
|
|
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');
|
|
186
229
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
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'));
|
|
195
242
|
|
|
196
243
|
const btnGoogleSso = this.shadowRoot?.getElementById('btn-google-sso') as HTMLButtonElement | null;
|
|
197
244
|
btnGoogleSso?.addEventListener('click', () => this.handleSsoClick('google'));
|
|
@@ -215,11 +262,13 @@ export class KeystoneCreatorComponentInstance
|
|
|
215
262
|
const secQuickOtp = this.shadowRoot?.getElementById('section-quick-otp') as HTMLDivElement | null;
|
|
216
263
|
const inputQuickOtp = this.shadowRoot?.getElementById('input-quick-otp') as HTMLInputElement | null;
|
|
217
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;
|
|
218
266
|
|
|
219
267
|
btnQuickSendCode?.addEventListener('click', () => this.handleQuickSendCode());
|
|
220
|
-
|
|
221
268
|
btnQuickVerifyCode?.addEventListener('click', () => this.handleQuickVerifyCode());
|
|
269
|
+
btnQuickResendCode?.addEventListener('click', () => this.handleQuickResendCode());
|
|
222
270
|
|
|
271
|
+
this.elements!.btnGeneratePasskey?.addEventListener('click', () => this.handleGeneratePasskey());
|
|
223
272
|
this.elements!.btnGenerateEl.addEventListener('click', () => this.handleGenerate());
|
|
224
273
|
|
|
225
274
|
this.shadowRoot!.getElementById('btn-switch-sign-in')?.addEventListener('click', (e) => {
|
|
@@ -232,6 +281,16 @@ export class KeystoneCreatorComponentInstance
|
|
|
232
281
|
});
|
|
233
282
|
|
|
234
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
|
+
|
|
235
294
|
this.elements!.inputUsername?.addEventListener('input', () => {
|
|
236
295
|
this.elements!.inputUsername.classList.remove('invalid');
|
|
237
296
|
});
|
|
@@ -307,25 +366,6 @@ export class KeystoneCreatorComponentInstance
|
|
|
307
366
|
animationEmoji: '🔑'
|
|
308
367
|
});
|
|
309
368
|
|
|
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
369
|
const metaspace = await getGlobalMetaspace_waitIfNeeded();
|
|
330
370
|
const space = await metaspace.getLocalUserSpace({ lock: false });
|
|
331
371
|
if (!space) {
|
|
@@ -333,14 +373,16 @@ export class KeystoneCreatorComponentInstance
|
|
|
333
373
|
return;
|
|
334
374
|
}
|
|
335
375
|
|
|
336
|
-
const
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
|
|
376
|
+
const email = resLogin.userInfo?.email || '';
|
|
377
|
+
const username = email ? email.split('@')[0] : 'user';
|
|
378
|
+
|
|
379
|
+
const { delegateKeystone, delegateSecret } = await createOAuth2DeviceSyncDelegate({
|
|
380
|
+
providerId,
|
|
381
|
+
username,
|
|
382
|
+
primaryTjpAddr,
|
|
383
|
+
custodianReqs,
|
|
340
384
|
metaspace,
|
|
341
385
|
space,
|
|
342
|
-
frameDetails: delegateBuilder.getFrameDetails(),
|
|
343
|
-
isPrimary: false
|
|
344
386
|
});
|
|
345
387
|
|
|
346
388
|
this.setStatus('Registering device delegate with custodian server...', 'info');
|
|
@@ -403,6 +445,7 @@ export class KeystoneCreatorComponentInstance
|
|
|
403
445
|
if (resCode.success) {
|
|
404
446
|
secQuickOtp?.classList.remove('hidden');
|
|
405
447
|
this.setStatus("✉️ 6-digit verification code sent to your email!", "success");
|
|
448
|
+
this.startResendCooldownTimer({ btnId: 'btn-quick-resend-code', durationSecs: 30 });
|
|
406
449
|
} else {
|
|
407
450
|
this.setStatus(resCode.message || "Failed to dispatch verification code.", "error");
|
|
408
451
|
}
|
|
@@ -418,6 +461,54 @@ export class KeystoneCreatorComponentInstance
|
|
|
418
461
|
}
|
|
419
462
|
}
|
|
420
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
|
+
|
|
421
512
|
/**
|
|
422
513
|
* Triggered by the "Verify Code" button.
|
|
423
514
|
* Submits 6-digit OTP code to custodian server, builds local sync delegate keystone,
|
|
@@ -467,24 +558,6 @@ export class KeystoneCreatorComponentInstance
|
|
|
467
558
|
animationEmoji: '🔑'
|
|
468
559
|
});
|
|
469
560
|
|
|
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
561
|
const metaspace = await getGlobalMetaspace_waitIfNeeded();
|
|
489
562
|
const space = await metaspace.getLocalUserSpace({ lock: false });
|
|
490
563
|
if (!space) {
|
|
@@ -492,14 +565,12 @@ export class KeystoneCreatorComponentInstance
|
|
|
492
565
|
return;
|
|
493
566
|
}
|
|
494
567
|
|
|
495
|
-
const
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
568
|
+
const { delegateKeystone, delegateSecret } = await createEmailDeviceSyncDelegate({
|
|
569
|
+
email,
|
|
570
|
+
primaryTjpAddr,
|
|
571
|
+
custodianReqs,
|
|
499
572
|
metaspace,
|
|
500
573
|
space,
|
|
501
|
-
frameDetails: delegateBuilder.getFrameDetails(),
|
|
502
|
-
isPrimary: false
|
|
503
574
|
});
|
|
504
575
|
|
|
505
576
|
// Register delegate with server
|
|
@@ -540,6 +611,389 @@ export class KeystoneCreatorComponentInstance
|
|
|
540
611
|
}
|
|
541
612
|
}
|
|
542
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 rawSalt = crypto.getRandomValues(new Uint8Array(32));
|
|
700
|
+
const saltHex = bufferToHex(rawSalt.buffer as ArrayBuffer);
|
|
701
|
+
const regResult = await registerPasskeyCredential({
|
|
702
|
+
username,
|
|
703
|
+
displayName: username,
|
|
704
|
+
salt: saltHex
|
|
705
|
+
});
|
|
706
|
+
const { credentialId, transports } = regResult;
|
|
707
|
+
|
|
708
|
+
let passkeySecret = regResult.passkeySecret;
|
|
709
|
+
if (!passkeySecret) {
|
|
710
|
+
const prfResult = await evaluatePasskeyPrf({
|
|
711
|
+
credentialId,
|
|
712
|
+
salt: saltHex,
|
|
713
|
+
transports
|
|
714
|
+
});
|
|
715
|
+
passkeySecret = prfResult.passkeySecret;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
const passkeyName = (inputName?.value || '').trim() || generateDefaultDeviceTag();
|
|
719
|
+
const passkeyTag = getSaferSubstring({ text: passkeyName, length: 12, keepLiterals: ['-'] }) || 'key';
|
|
720
|
+
const poolId = `passkey-manage-${passkeyTag}`;
|
|
721
|
+
|
|
722
|
+
const passkeyEntry: KeystonePasskeyEntry = {
|
|
723
|
+
credentialId,
|
|
724
|
+
poolId,
|
|
725
|
+
name: passkeyName,
|
|
726
|
+
salt: saltHex,
|
|
727
|
+
createdUTC: new Date().toISOString(),
|
|
728
|
+
...(transports && transports.length > 0 ? { transports } : {})
|
|
729
|
+
};
|
|
730
|
+
|
|
731
|
+
this.showBusy({
|
|
732
|
+
isBusy: true,
|
|
733
|
+
title: 'Generating Identity...',
|
|
734
|
+
msg: 'Compiling sovereign passkey challenge pools...',
|
|
735
|
+
animationEmoji: '⚙️'
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
// 2. Prepare Sovereign Keystone Configs
|
|
739
|
+
const sovereignPrimaryProfileName = process.env.KEYSTONE_PROFILE_SOVEREIGN_PRIMARY;
|
|
740
|
+
if (!sovereignPrimaryProfileName || !sovereignPrimaryProfileName.trim()) {
|
|
741
|
+
throw new Error('KEYSTONE_PROFILE_SOVEREIGN_PRIMARY environment variable is missing.');
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
const builder = KeystoneProfileBuilder.buildKeystone(sovereignPrimaryProfileName.trim() as any)
|
|
745
|
+
.withUsername(username)
|
|
746
|
+
.withDescription(description);
|
|
747
|
+
if (email) {
|
|
748
|
+
builder.withEmails([email]);
|
|
749
|
+
}
|
|
750
|
+
builder.withDetails({
|
|
751
|
+
client: 'space-gib-web',
|
|
752
|
+
role: 'domain-identity',
|
|
753
|
+
passkeys: {
|
|
754
|
+
[credentialId]: passkeyEntry
|
|
755
|
+
}
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
const configs = await builder.compileConfigs();
|
|
759
|
+
// Re-label manage pool to passkey pool id
|
|
760
|
+
for (const cfg of configs) {
|
|
761
|
+
if (cfg.id === 'manage') {
|
|
762
|
+
cfg.id = poolId;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// 3. Prepare Metaspace & Local Space
|
|
767
|
+
const metaspace = await getGlobalMetaspace_waitIfNeeded();
|
|
768
|
+
const space = await metaspace.getLocalUserSpace({ lock: false });
|
|
769
|
+
if (!space) {
|
|
770
|
+
this.setStatus("Failed to access local user space.", "error");
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// 4. Perform Local Genesis
|
|
775
|
+
this.showBusy({
|
|
776
|
+
isBusy: true,
|
|
777
|
+
title: 'Generating Identity...',
|
|
778
|
+
msg: 'Generating cryptographic domain identity keys...',
|
|
779
|
+
animationEmoji: '🔑'
|
|
780
|
+
});
|
|
781
|
+
|
|
782
|
+
const keystoneService = new KeystoneService_V1();
|
|
783
|
+
const keystoneIbGib = await keystoneService.genesis({
|
|
784
|
+
masterSecret: passkeySecret,
|
|
785
|
+
emails: email ? [email] : undefined,
|
|
786
|
+
credentialIds: [credentialId],
|
|
787
|
+
configs,
|
|
788
|
+
metaspace,
|
|
789
|
+
space,
|
|
790
|
+
frameDetails: builder.getFrameDetails(),
|
|
791
|
+
isPrimary: true
|
|
792
|
+
});
|
|
793
|
+
|
|
794
|
+
const primaryTjpAddr = getTjpAddr({ ibGib: keystoneIbGib, defaultIfNone: 'incomingAddr' })!;
|
|
795
|
+
console.log(`${lc} passkey local genesis complete: ${primaryTjpAddr}`);
|
|
796
|
+
await delay(humanEyeDelay);
|
|
797
|
+
this.setStatus("Local genesis complete. Creating passkey sync delegate...", "info");
|
|
798
|
+
|
|
799
|
+
// 5. Create Passkey Device Sync Delegate
|
|
800
|
+
this.showBusy({
|
|
801
|
+
isBusy: true,
|
|
802
|
+
title: 'Creating Sync Delegate...',
|
|
803
|
+
msg: 'Generating cryptographic sync delegate keys...',
|
|
804
|
+
animationEmoji: '🔑'
|
|
805
|
+
});
|
|
806
|
+
|
|
807
|
+
const { delegateKeystone: delegateKeystoneIbGib, deviceTag, delegateSecret } = await createPasskeyDeviceSyncDelegate({
|
|
808
|
+
username,
|
|
809
|
+
primaryTjpAddr,
|
|
810
|
+
passkeySecret,
|
|
811
|
+
transports,
|
|
812
|
+
metaspace,
|
|
813
|
+
space,
|
|
814
|
+
});
|
|
815
|
+
|
|
816
|
+
// 6. Register Delegate locally on primary keystone
|
|
817
|
+
const evolvedParentKeystoneIbGib = await keystoneService.registerDelegate({
|
|
818
|
+
parentKeystone: keystoneIbGib,
|
|
819
|
+
delegateKeystone: delegateKeystoneIbGib,
|
|
820
|
+
masterSecret: passkeySecret,
|
|
821
|
+
signingPoolId: poolId,
|
|
822
|
+
allowedVerbs: [KEYSTONE_VERB_SYNC, KEYSTONE_VERB_CONNECT],
|
|
823
|
+
deviceName: deviceTag,
|
|
824
|
+
metaspace,
|
|
825
|
+
space,
|
|
826
|
+
});
|
|
827
|
+
|
|
828
|
+
// 7. Post to Server & Handle Verification
|
|
829
|
+
this.setStatus("Syncing genesis keystone to server...", "info");
|
|
830
|
+
this.showBusy({
|
|
831
|
+
isBusy: true,
|
|
832
|
+
title: 'Syncing to Server...',
|
|
833
|
+
msg: 'Staging pending genesis keystone on server...',
|
|
834
|
+
animationEmoji: '🔄'
|
|
835
|
+
});
|
|
836
|
+
|
|
837
|
+
const resSync = await getApiBridge().postGenesisKeystone(keystoneIbGib);
|
|
838
|
+
if (!resSync.success) {
|
|
839
|
+
const msg = `Sync failed: ${resSync.message}`;
|
|
840
|
+
console.error(`${lc} ${msg}`);
|
|
841
|
+
this.setStatus(msg, "error");
|
|
842
|
+
this.showBusy({ isBusy: false });
|
|
843
|
+
if (btn) {
|
|
844
|
+
btn.disabled = false;
|
|
845
|
+
btn.textContent = '🛡️ Create Identity with Passkey / Biometrics';
|
|
846
|
+
}
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
const isPending = (resSync as any).pending || (resSync as any).statusCode === 202;
|
|
851
|
+
|
|
852
|
+
if (isPending) {
|
|
853
|
+
console.log(`${lc} Genesis keystone quarantined in pending staging area. Prompting for 6-digit verification code.`);
|
|
854
|
+
this.showBusy({ isBusy: false });
|
|
855
|
+
this.setFormDisabled(true);
|
|
856
|
+
|
|
857
|
+
if (!this.shadowRoot) { throw new Error(`(UNEXPECTED) this.shadowRoot falsy? (E: 6d316f5a2bf8e6a0286306468d35c826)`); }
|
|
858
|
+
const secVerify = shadowRoot_getElementById<HTMLDivElement>(this.shadowRoot, 'section-code-verification');
|
|
859
|
+
const inputVerifyCode = shadowRoot_getElementById<HTMLInputElement>(this.shadowRoot, 'input-verify-code');
|
|
860
|
+
const btnSubmitCode = shadowRoot_getElementById<HTMLButtonElement>(this.shadowRoot, 'btn-submit-code');
|
|
861
|
+
const btnResendCode = shadowRoot_getElementById<HTMLButtonElement>(this.shadowRoot, 'btn-resend-code');
|
|
862
|
+
|
|
863
|
+
secVerify.classList.remove('hidden');
|
|
864
|
+
secVerify.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
865
|
+
this.setStatus("✉️ Verification code sent to your email. Enter 6-digit code below:", "info");
|
|
866
|
+
this.startResendCooldownTimer({ btnId: 'btn-resend-code', durationSecs: 30 });
|
|
867
|
+
|
|
868
|
+
const onResendCode = async () => {
|
|
869
|
+
btnResendCode.disabled = true;
|
|
870
|
+
btnResendCode.textContent = "Sending...";
|
|
871
|
+
this.setStatus("Resending verification code to your email...", "info");
|
|
872
|
+
try {
|
|
873
|
+
const resResend = await getApiBridge().postVerificationCode({ email, primaryTjpAddr });
|
|
874
|
+
if (resResend.success) {
|
|
875
|
+
this.setStatus("✉️ Fresh 6-digit verification code sent to your email!", "success");
|
|
876
|
+
this.startResendCooldownTimer({ btnId: 'btn-resend-code', durationSecs: 30 });
|
|
877
|
+
inputVerifyCode.value = '';
|
|
878
|
+
inputVerifyCode.focus();
|
|
879
|
+
} else {
|
|
880
|
+
this.setStatus(resResend.message || "Failed to resend verification code.", "error");
|
|
881
|
+
btnResendCode.disabled = false;
|
|
882
|
+
btnResendCode.textContent = "Resend Code";
|
|
883
|
+
}
|
|
884
|
+
} catch (error) {
|
|
885
|
+
const emsg = extractErrorMsg(error);
|
|
886
|
+
console.error(`${lc} ${emsg}`);
|
|
887
|
+
this.setStatus(`Failed to resend verification code: ${emsg}`, "error");
|
|
888
|
+
btnResendCode.disabled = false;
|
|
889
|
+
btnResendCode.textContent = "Resend Code";
|
|
890
|
+
}
|
|
891
|
+
};
|
|
892
|
+
btnResendCode.addEventListener('click', onResendCode);
|
|
893
|
+
|
|
894
|
+
await new Promise<void>((resolve) => {
|
|
895
|
+
const onSubmitCode = async () => {
|
|
896
|
+
const code = inputVerifyCode.value.trim();
|
|
897
|
+
if (!code || code.length !== 6 || !/^\d{6}$/.test(code)) {
|
|
898
|
+
this.setStatus("Please enter a valid 6-digit numeric verification code (e.g. 482901).", "error");
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
btnSubmitCode.disabled = true;
|
|
903
|
+
btnSubmitCode.textContent = "Verifying...";
|
|
904
|
+
this.showBusy({
|
|
905
|
+
isBusy: true,
|
|
906
|
+
title: 'Verifying & Activating...',
|
|
907
|
+
msg: 'Great! Juust a moment...\n(Validating code, promoting server domain space, other security stuff...)',
|
|
908
|
+
animationEmoji: '🔐'
|
|
909
|
+
});
|
|
910
|
+
|
|
911
|
+
const resVerify = await getApiBridge().postVerifyCode({ email, primaryTjpAddr, code });
|
|
912
|
+
if (!resVerify.success) {
|
|
913
|
+
this.showBusy({ isBusy: false });
|
|
914
|
+
this.setStatus(resVerify.message || "Incorrect verification code.", "error");
|
|
915
|
+
btnSubmitCode.disabled = false;
|
|
916
|
+
btnSubmitCode.textContent = "Verify Code";
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
console.log(`${lc} Code verified & server domain promoted! Syncing delegate...`);
|
|
921
|
+
this.showBusy({
|
|
922
|
+
isBusy: true,
|
|
923
|
+
title: 'Syncing Delegate...',
|
|
924
|
+
msg: 'Posting evolved domain identity and sync delegate to server...',
|
|
925
|
+
animationEmoji: '⚡'
|
|
926
|
+
});
|
|
927
|
+
|
|
928
|
+
btnSubmitCode.removeEventListener('click', onSubmitCode);
|
|
929
|
+
secVerify.classList.add('hidden');
|
|
930
|
+
this.setFormDisabled(false);
|
|
931
|
+
|
|
932
|
+
// Now that server domain space is promoted, post evolved keystone + delegate
|
|
933
|
+
const resEvolve = await getApiBridge().putEvolveKeystone({
|
|
934
|
+
domainAddr: primaryTjpAddr,
|
|
935
|
+
keystoneIbGib: evolvedParentKeystoneIbGib,
|
|
936
|
+
relatedKeystoneIbGibs: [delegateKeystoneIbGib]
|
|
937
|
+
});
|
|
938
|
+
if (!resEvolve.success) {
|
|
939
|
+
console.warn(`${lc} Delegate sync warning after code verification: ${resEvolve.message}`);
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
resolve();
|
|
943
|
+
};
|
|
944
|
+
|
|
945
|
+
btnSubmitCode.addEventListener('click', onSubmitCode);
|
|
946
|
+
});
|
|
947
|
+
} else {
|
|
948
|
+
// Post evolved keystone + delegate keystone immediately
|
|
949
|
+
this.setStatus("Syncing delegate registration to server...", "info");
|
|
950
|
+
this.showBusy({
|
|
951
|
+
isBusy: true,
|
|
952
|
+
title: 'Syncing Delegate...',
|
|
953
|
+
msg: 'Posting evolved domain identity and sync delegate to server...',
|
|
954
|
+
animationEmoji: '🔄'
|
|
955
|
+
});
|
|
956
|
+
const resEvolve = await getApiBridge().putEvolveKeystone({
|
|
957
|
+
domainAddr: primaryTjpAddr,
|
|
958
|
+
keystoneIbGib: evolvedParentKeystoneIbGib,
|
|
959
|
+
relatedKeystoneIbGibs: [delegateKeystoneIbGib]
|
|
960
|
+
});
|
|
961
|
+
if (!resEvolve.success) {
|
|
962
|
+
const msg = `Delegate registration sync failed: ${resEvolve.message}`;
|
|
963
|
+
console.error(`${lc} ${msg}`);
|
|
964
|
+
this.setStatus(msg, "error");
|
|
965
|
+
this.showBusy({ isBusy: false });
|
|
966
|
+
if (btn) {
|
|
967
|
+
btn.disabled = false;
|
|
968
|
+
btn.textContent = '🛡️ Create Identity with Passkey / Biometrics';
|
|
969
|
+
}
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
// 8. Finalize Onboarding
|
|
975
|
+
await this.finalizeIdentityOnboarding({
|
|
976
|
+
primaryKeystone: evolvedParentKeystoneIbGib,
|
|
977
|
+
delegateKeystone: delegateKeystoneIbGib,
|
|
978
|
+
primaryTjpAddr,
|
|
979
|
+
masterSecret: passkeySecret,
|
|
980
|
+
delegateSecret,
|
|
981
|
+
makeActive: checkboxActive?.checked ?? true
|
|
982
|
+
});
|
|
983
|
+
|
|
984
|
+
} catch (error) {
|
|
985
|
+
const emsg = extractErrorMsg(error);
|
|
986
|
+
console.error(`${lc} ${emsg}`, error);
|
|
987
|
+
this.setStatus(`Error: ${emsg}`, "error");
|
|
988
|
+
} finally {
|
|
989
|
+
this.showBusy({ isBusy: false });
|
|
990
|
+
if (btn) {
|
|
991
|
+
btn.disabled = false;
|
|
992
|
+
btn.textContent = '🛡️ Create Identity with Passkey / Biometrics';
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
|
|
543
997
|
/**
|
|
544
998
|
* Triggered by the "Generate" button.
|
|
545
999
|
* Orchestrates: Secret -> Local Genesis -> Graph Collection -> Server Sync.
|
|
@@ -683,28 +1137,12 @@ export class KeystoneCreatorComponentInstance
|
|
|
683
1137
|
animationEmoji: '🔑'
|
|
684
1138
|
});
|
|
685
1139
|
|
|
686
|
-
const delegateSecret = await
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
const delegateBuilder = KeystoneProfileBuilder.buildKeystone(sovereignSyncProfileName.trim() as any)
|
|
693
|
-
.withUsername(`${username}-sync`)
|
|
694
|
-
.withDescription(`Sync Delegate for ${username}`)
|
|
695
|
-
.withDetails({
|
|
696
|
-
client: 'space-gib-web',
|
|
697
|
-
role: 'sync-delegate',
|
|
698
|
-
primaryTjpAddr: primaryTjpAddr,
|
|
699
|
-
});
|
|
700
|
-
const delegateConfigs = await delegateBuilder.compileConfigs();
|
|
701
|
-
|
|
702
|
-
const delegateKeystoneIbGib = await keystoneService.genesis({
|
|
703
|
-
masterSecret: delegateSecret,
|
|
704
|
-
configs: delegateConfigs,
|
|
1140
|
+
const { delegateKeystone: delegateKeystoneIbGib, deviceTag, delegateSecret } = await createSovereignDeviceSyncDelegate({
|
|
1141
|
+
username,
|
|
1142
|
+
primaryTjpAddr,
|
|
1143
|
+
masterSecret,
|
|
705
1144
|
metaspace,
|
|
706
1145
|
space,
|
|
707
|
-
frameDetails: delegateBuilder.getFrameDetails()
|
|
708
1146
|
});
|
|
709
1147
|
|
|
710
1148
|
const delegateAddr = getIbGibAddr({ ibGib: delegateKeystoneIbGib });
|
|
@@ -723,9 +1161,11 @@ export class KeystoneCreatorComponentInstance
|
|
|
723
1161
|
parentKeystone: keystoneIbGib,
|
|
724
1162
|
delegateKeystone: delegateKeystoneIbGib,
|
|
725
1163
|
masterSecret,
|
|
1164
|
+
signingPoolId: 'manage',
|
|
726
1165
|
metaspace,
|
|
727
1166
|
space,
|
|
728
|
-
allowedVerbs: [KEYSTONE_VERB_SYNC, KEYSTONE_VERB_CONNECT]
|
|
1167
|
+
allowedVerbs: [KEYSTONE_VERB_SYNC, KEYSTONE_VERB_CONNECT],
|
|
1168
|
+
deviceName: deviceTag,
|
|
729
1169
|
});
|
|
730
1170
|
const evolvedParentAddr = getIbGibAddr({ ibGib: evolvedParentKeystoneIbGib });
|
|
731
1171
|
console.log(`${lc} delegate registered. evolved parent address: ${evolvedParentAddr}`);
|
|
@@ -768,19 +1208,31 @@ export class KeystoneCreatorComponentInstance
|
|
|
768
1208
|
secVerify.classList.remove('hidden');
|
|
769
1209
|
secVerify.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
770
1210
|
this.setStatus("✉️ Verification code sent to your email. Enter 6-digit code below:", "info");
|
|
1211
|
+
this.startResendCooldownTimer({ btnId: 'btn-resend-code', durationSecs: 30 });
|
|
771
1212
|
|
|
772
1213
|
const onResendCode = async () => {
|
|
773
1214
|
btnResendCode.disabled = true;
|
|
774
1215
|
btnResendCode.textContent = "Sending...";
|
|
775
1216
|
this.setStatus("Resending verification code to your email...", "info");
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
1217
|
+
try {
|
|
1218
|
+
const resResend = await getApiBridge().postVerificationCode({ email, primaryTjpAddr: primaryTjpAddr });
|
|
1219
|
+
if (resResend.success) {
|
|
1220
|
+
this.setStatus("✉️ Fresh 6-digit verification code sent to your email!", "success");
|
|
1221
|
+
this.startResendCooldownTimer({ btnId: 'btn-resend-code', durationSecs: 30 });
|
|
1222
|
+
inputVerifyCode.value = '';
|
|
1223
|
+
inputVerifyCode.focus();
|
|
1224
|
+
} else {
|
|
1225
|
+
this.setStatus(resResend.message || "Failed to resend verification code.", "error");
|
|
1226
|
+
btnResendCode.disabled = false;
|
|
1227
|
+
btnResendCode.textContent = "Resend Code";
|
|
1228
|
+
}
|
|
1229
|
+
} catch (error) {
|
|
1230
|
+
const emsg = extractErrorMsg(error);
|
|
1231
|
+
console.error(`${lc} ${emsg}`);
|
|
1232
|
+
this.setStatus(`Failed to resend verification code: ${emsg}`, "error");
|
|
1233
|
+
btnResendCode.disabled = false;
|
|
1234
|
+
btnResendCode.textContent = "Resend Code";
|
|
781
1235
|
}
|
|
782
|
-
btnResendCode.disabled = false;
|
|
783
|
-
btnResendCode.textContent = "Resend Code";
|
|
784
1236
|
};
|
|
785
1237
|
btnResendCode.addEventListener('click', onResendCode);
|
|
786
1238
|
|
|
@@ -1009,6 +1461,11 @@ export class KeystoneCreatorComponentInstance
|
|
|
1009
1461
|
this.setStatus(successMsg, "success");
|
|
1010
1462
|
console.log(`${lc} ✓ ${successMsg}`);
|
|
1011
1463
|
|
|
1464
|
+
// 3.5 Sanitize secret input fields from DOM
|
|
1465
|
+
if (this.elements?.inputSecret) {
|
|
1466
|
+
this.elements.inputSecret.value = '';
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1012
1469
|
// 4. Dispatch UI navigation message to close creator and open identity manager
|
|
1013
1470
|
window.dispatchEvent(new CustomEvent(EVENT_IBGIB_UI_MESSAGE, {
|
|
1014
1471
|
detail: { action: 'show-identity-manager', addr: primaryTjpAddr },
|