@ibgib/web-gib 0.0.61 → 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.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 +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 +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 +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.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 +510 -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 +16 -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 +946 -122
- 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 +110 -6
- 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 +22 -2
- package/src/identity/device-delegate-helper.mts +135 -2
- package/src/identity/device-delegate.respec.mts +6 -6
- package/src/identity/passkey-helper.mts +304 -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.html +42 -3
- package/src/ui/component/identity/keystone-creator/keystone-creator.mts +551 -77
- package/src/ui/component/identity/keystone-details/keystone-details.html +38 -0
- package/src/ui/component/identity/keystone-details/keystone-details.mts +1011 -131
- package/src/ui/component/identity/remove-device/remove-device.mts +1 -0
- package/src/ui/ui-helpers.mts +118 -5
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import thisCss from "./keystone-creator.css";
|
|
2
2
|
import thisHtml from "./keystone-creator.html";
|
|
3
3
|
import commonComponentStylesCss from "../../common-component-styles.css";
|
|
4
|
-
import { delay, extractErrorMsg, } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
|
|
4
|
+
import { delay, extractErrorMsg, getSaferSubstring } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
|
|
5
5
|
import { getIbGibAddr } from "@ibgib/ts-gib/dist/helper.mjs";
|
|
6
6
|
import { KeystoneService_V1 } from "@ibgib/core-gib/dist/keystone/keystone-service-v1.mjs";
|
|
7
7
|
import { KeystoneProfileBuilder } from "@ibgib/core-gib/dist/keystone/policy/keystone-profile-builder.mjs";
|
|
@@ -9,14 +9,14 @@ import { updateSpecialIndex, } from "@ibgib/core-gib/dist/timeline/timeline-api.
|
|
|
9
9
|
import { toDto, getTjpAddr, getTimelinesGroupedByTjp } from "@ibgib/core-gib/dist/common/other/ibgib-helper.mjs";
|
|
10
10
|
import { validateEmail } from "@ibgib/core-gib/dist/keystone/keystone-helpers.mjs";
|
|
11
11
|
import { KEYSTONE_USERNAME_REGEXP, KEYSTONE_DESCRIPTION_REGEXP, KEYSTONE_VERB_SYNC, KEYSTONE_VERB_CONNECT } from "@ibgib/core-gib/dist/keystone/keystone-constants.mjs";
|
|
12
|
-
import { deriveDelegateSecret, createSyncDelegateKeystone } from "@ibgib/core-gib/dist/keystone/delegate/delegate-helpers.mjs";
|
|
13
12
|
import { getGlobalMetaspace_waitIfNeeded, } from "../../../../helpers.mjs";
|
|
14
13
|
import { IbGibDynamicComponentMetaBase, IbGibFormInstanceBase, } from "../../ibgib-dynamic-component-bases.mjs";
|
|
15
14
|
import { getComponentCtorArg } from "../../../../app-bootstrap/init-orchestration.mjs";
|
|
16
15
|
import { getApiBridge } from "../../../../api/ibgib-api-bridge.mjs";
|
|
17
|
-
import {
|
|
16
|
+
import { createSovereignDeviceSyncDelegate, createOAuth2DeviceSyncDelegate, createEmailDeviceSyncDelegate } from "../../../../identity/device-delegate-helper.mjs";
|
|
18
17
|
import { executeSsoOAuthPopup } from "../../../../identity/sso-popup-helper.mjs";
|
|
19
18
|
import { generateDefaultDeviceTag } from "../../../../util/device-helper.mjs";
|
|
19
|
+
import { registerPasskeyCredential, evaluatePasskeyPrf, createPasskeyDeviceSyncDelegate, bufferToHex } from "../../../../identity/passkey-helper.mjs";
|
|
20
20
|
import { EVENT_IBGIB_IDENTITY_REQUEST_CHANGE, EVENT_IBGIB_UI_MESSAGE } from "../../../ui-constants.mjs";
|
|
21
21
|
import { alertUser, shadowRoot_getElementById } from "../../../../helpers.web.mjs";
|
|
22
22
|
export const KEYSTONE_CREATOR_COMPONENT_NAME = 'ibgib-keystone-creator';
|
|
@@ -51,12 +51,43 @@ export class KeystoneCreatorComponentMeta extends IbGibDynamicComponentMetaBase
|
|
|
51
51
|
export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
|
|
52
52
|
lc = `[KeystoneCreatorComponentInstance]`;
|
|
53
53
|
hasExistingIdentities = false;
|
|
54
|
+
resendCooldownInterval = null;
|
|
54
55
|
constructor() {
|
|
55
56
|
super();
|
|
56
57
|
}
|
|
57
58
|
async initialize(opts) {
|
|
58
59
|
await super.initialize(opts);
|
|
59
60
|
}
|
|
61
|
+
async disconnected() {
|
|
62
|
+
if (this.resendCooldownInterval) {
|
|
63
|
+
clearInterval(this.resendCooldownInterval);
|
|
64
|
+
this.resendCooldownInterval = null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
startResendCooldownTimer({ btnId, durationSecs = 30 }) {
|
|
68
|
+
const btn = this.shadowRoot?.getElementById(btnId);
|
|
69
|
+
if (!btn)
|
|
70
|
+
return;
|
|
71
|
+
if (this.resendCooldownInterval) {
|
|
72
|
+
clearInterval(this.resendCooldownInterval);
|
|
73
|
+
this.resendCooldownInterval = null;
|
|
74
|
+
}
|
|
75
|
+
let remaining = durationSecs;
|
|
76
|
+
btn.disabled = true;
|
|
77
|
+
btn.textContent = `Resend Code (${remaining}s)`;
|
|
78
|
+
this.resendCooldownInterval = setInterval(() => {
|
|
79
|
+
remaining -= 1;
|
|
80
|
+
if (remaining <= 0) {
|
|
81
|
+
clearInterval(this.resendCooldownInterval);
|
|
82
|
+
this.resendCooldownInterval = null;
|
|
83
|
+
btn.disabled = false;
|
|
84
|
+
btn.textContent = "Resend Code";
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
btn.textContent = `Resend Code (${remaining}s)`;
|
|
88
|
+
}
|
|
89
|
+
}, 1000);
|
|
90
|
+
}
|
|
60
91
|
async created() {
|
|
61
92
|
this.elements = {};
|
|
62
93
|
this.elements.containerEl = this.shadowRoot.getElementById('container');
|
|
@@ -69,6 +100,12 @@ export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
|
|
|
69
100
|
this.elements.inputEmail = this.shadowRoot.getElementById('input-email');
|
|
70
101
|
this.elements.inputSecret = this.shadowRoot.getElementById('input-secret');
|
|
71
102
|
this.elements.checkboxMakeActive = this.shadowRoot.getElementById('checkbox-make-active');
|
|
103
|
+
this.elements.inputPasskeyUsername = this.shadowRoot.getElementById('input-passkey-username');
|
|
104
|
+
this.elements.inputPasskeyDescription = this.shadowRoot.getElementById('input-passkey-description');
|
|
105
|
+
this.elements.inputPasskeyEmail = this.shadowRoot.getElementById('input-passkey-email');
|
|
106
|
+
this.elements.inputPasskeyName = this.shadowRoot.getElementById('input-passkey-name');
|
|
107
|
+
this.elements.checkboxPasskeyMakeActive = this.shadowRoot.getElementById('checkbox-passkey-make-active');
|
|
108
|
+
this.elements.btnGeneratePasskey = this.shadowRoot.getElementById('btn-generate-passkey');
|
|
72
109
|
this.elements.statusArea = this.shadowRoot.getElementById('status-area');
|
|
73
110
|
this.elements.statusText = this.shadowRoot.getElementById('status-text');
|
|
74
111
|
// ElementBase requires contentEl
|
|
@@ -99,46 +136,46 @@ export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
|
|
|
99
136
|
console.warn(`${lc} Error checking existing identities: ${extractErrorMsg(error)}`);
|
|
100
137
|
this.hasExistingIdentities = false;
|
|
101
138
|
}
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
if (
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
139
|
+
const checkboxes = [this.elements.checkboxMakeActive, this.elements.checkboxPasskeyMakeActive];
|
|
140
|
+
for (const checkbox of checkboxes) {
|
|
141
|
+
if (checkbox) {
|
|
142
|
+
if (!this.hasExistingIdentities) {
|
|
143
|
+
checkbox.checked = true;
|
|
144
|
+
checkbox.disabled = true;
|
|
145
|
+
checkbox.title = "At least one active identity is required.";
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
checkbox.disabled = false;
|
|
149
|
+
checkbox.title = "";
|
|
150
|
+
}
|
|
112
151
|
}
|
|
113
152
|
}
|
|
114
153
|
}
|
|
115
|
-
async disconnected() {
|
|
116
|
-
// cleanup if needed
|
|
117
|
-
}
|
|
118
154
|
initHandlers() {
|
|
119
|
-
// Mode Tab Switching (Quick vs Password-based)
|
|
155
|
+
// Mode Tab Switching (Quick vs Passkey vs Password-based)
|
|
120
156
|
const tabQuick = this.shadowRoot?.getElementById('tab-quick');
|
|
157
|
+
const tabPasskey = this.shadowRoot?.getElementById('tab-passkey');
|
|
121
158
|
const tabPassword = this.shadowRoot?.getElementById('tab-password');
|
|
122
159
|
const panelQuick = this.shadowRoot?.getElementById('panel-quick');
|
|
160
|
+
const panelPasskey = this.shadowRoot?.getElementById('panel-passkey');
|
|
123
161
|
const panelPassword = this.shadowRoot?.getElementById('panel-password');
|
|
124
162
|
const subtitleQuick = this.shadowRoot?.getElementById('subtitle-quick');
|
|
163
|
+
const subtitlePasskey = this.shadowRoot?.getElementById('subtitle-passkey');
|
|
125
164
|
const subtitlePassword = this.shadowRoot?.getElementById('subtitle-password');
|
|
126
|
-
|
|
127
|
-
tabQuick
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
subtitleQuick?.classList.add('hidden');
|
|
141
|
-
});
|
|
165
|
+
const switchTab = (mode) => {
|
|
166
|
+
tabQuick?.classList.toggle('active', mode === 'quick');
|
|
167
|
+
tabPasskey?.classList.toggle('active', mode === 'passkey');
|
|
168
|
+
tabPassword?.classList.toggle('active', mode === 'password');
|
|
169
|
+
panelQuick?.classList.toggle('hidden', mode !== 'quick');
|
|
170
|
+
panelPasskey?.classList.toggle('hidden', mode !== 'passkey');
|
|
171
|
+
panelPassword?.classList.toggle('hidden', mode !== 'password');
|
|
172
|
+
subtitleQuick?.classList.toggle('hidden', mode !== 'quick');
|
|
173
|
+
subtitlePasskey?.classList.toggle('hidden', mode !== 'passkey');
|
|
174
|
+
subtitlePassword?.classList.toggle('hidden', mode !== 'password');
|
|
175
|
+
};
|
|
176
|
+
tabQuick?.addEventListener('click', () => switchTab('quick'));
|
|
177
|
+
tabPasskey?.addEventListener('click', () => switchTab('passkey'));
|
|
178
|
+
tabPassword?.addEventListener('click', () => switchTab('password'));
|
|
142
179
|
const btnGoogleSso = this.shadowRoot?.getElementById('btn-google-sso');
|
|
143
180
|
btnGoogleSso?.addEventListener('click', () => this.handleSsoClick('google'));
|
|
144
181
|
const btnGithubSso = this.shadowRoot?.getElementById('btn-github-sso');
|
|
@@ -158,8 +195,11 @@ export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
|
|
|
158
195
|
const secQuickOtp = this.shadowRoot?.getElementById('section-quick-otp');
|
|
159
196
|
const inputQuickOtp = this.shadowRoot?.getElementById('input-quick-otp');
|
|
160
197
|
const btnQuickVerifyCode = this.shadowRoot?.getElementById('btn-quick-verify-code');
|
|
198
|
+
const btnQuickResendCode = this.shadowRoot?.getElementById('btn-quick-resend-code');
|
|
161
199
|
btnQuickSendCode?.addEventListener('click', () => this.handleQuickSendCode());
|
|
162
200
|
btnQuickVerifyCode?.addEventListener('click', () => this.handleQuickVerifyCode());
|
|
201
|
+
btnQuickResendCode?.addEventListener('click', () => this.handleQuickResendCode());
|
|
202
|
+
this.elements.btnGeneratePasskey?.addEventListener('click', () => this.handleGeneratePasskey());
|
|
163
203
|
this.elements.btnGenerateEl.addEventListener('click', () => this.handleGenerate());
|
|
164
204
|
this.shadowRoot.getElementById('btn-switch-sign-in')?.addEventListener('click', (e) => {
|
|
165
205
|
e.preventDefault();
|
|
@@ -170,6 +210,15 @@ export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
|
|
|
170
210
|
}));
|
|
171
211
|
});
|
|
172
212
|
// Clear invalid styling when user inputs text
|
|
213
|
+
this.elements.inputPasskeyUsername?.addEventListener('input', () => {
|
|
214
|
+
this.elements.inputPasskeyUsername.classList.remove('invalid');
|
|
215
|
+
});
|
|
216
|
+
this.elements.inputPasskeyDescription?.addEventListener('input', () => {
|
|
217
|
+
this.elements.inputPasskeyDescription.classList.remove('invalid');
|
|
218
|
+
});
|
|
219
|
+
this.elements.inputPasskeyEmail?.addEventListener('input', () => {
|
|
220
|
+
this.elements.inputPasskeyEmail.classList.remove('invalid');
|
|
221
|
+
});
|
|
173
222
|
this.elements.inputUsername?.addEventListener('input', () => {
|
|
174
223
|
this.elements.inputUsername.classList.remove('invalid');
|
|
175
224
|
});
|
|
@@ -242,21 +291,13 @@ export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
|
|
|
242
291
|
this.setStatus('Could not access local user space.', 'error');
|
|
243
292
|
return;
|
|
244
293
|
}
|
|
245
|
-
const delegateSecret = await getOrCreateDeviceDelegateSecret({ primaryTjpAddr });
|
|
246
294
|
const email = resLogin.userInfo?.email || '';
|
|
247
295
|
const username = email ? email.split('@')[0] : 'user';
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
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)');
|
|
251
|
-
}
|
|
252
|
-
const delegateKeystone = await createSyncDelegateKeystone({
|
|
253
|
-
profileName: custodianSyncProfileName.trim(),
|
|
296
|
+
const { delegateKeystone, delegateSecret } = await createOAuth2DeviceSyncDelegate({
|
|
297
|
+
providerId,
|
|
254
298
|
username,
|
|
255
|
-
deviceTag: generateDefaultDeviceTag(),
|
|
256
299
|
primaryTjpAddr,
|
|
257
|
-
delegateSecret,
|
|
258
300
|
custodianReqs,
|
|
259
|
-
additionalFrameDetails: { client: 'space-gib-web' },
|
|
260
301
|
metaspace,
|
|
261
302
|
space,
|
|
262
303
|
});
|
|
@@ -315,6 +356,7 @@ export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
|
|
|
315
356
|
if (resCode.success) {
|
|
316
357
|
secQuickOtp?.classList.remove('hidden');
|
|
317
358
|
this.setStatus("✉️ 6-digit verification code sent to your email!", "success");
|
|
359
|
+
this.startResendCooldownTimer({ btnId: 'btn-quick-resend-code', durationSecs: 30 });
|
|
318
360
|
}
|
|
319
361
|
else {
|
|
320
362
|
this.setStatus(resCode.message || "Failed to dispatch verification code.", "error");
|
|
@@ -332,6 +374,52 @@ export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
|
|
|
332
374
|
}
|
|
333
375
|
}
|
|
334
376
|
}
|
|
377
|
+
/**
|
|
378
|
+
* Triggered by the "Resend Code" button in the quick email panel.
|
|
379
|
+
*/
|
|
380
|
+
async handleQuickResendCode() {
|
|
381
|
+
const lc = `${this.lc}[${this.handleQuickResendCode.name}]`;
|
|
382
|
+
const btnQuickResendCode = this.shadowRoot?.getElementById('btn-quick-resend-code');
|
|
383
|
+
const inputQuickEmail = this.shadowRoot?.getElementById('input-quick-email');
|
|
384
|
+
const inputQuickOtp = this.shadowRoot?.getElementById('input-quick-otp');
|
|
385
|
+
const email = (inputQuickEmail?.value || '').trim();
|
|
386
|
+
if (!email || !validateEmail(email)) {
|
|
387
|
+
this.setStatus("Please enter a valid email address.", "error");
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
if (btnQuickResendCode) {
|
|
391
|
+
btnQuickResendCode.disabled = true;
|
|
392
|
+
btnQuickResendCode.textContent = "Sending...";
|
|
393
|
+
}
|
|
394
|
+
this.setStatus("Resending verification code...", "info");
|
|
395
|
+
try {
|
|
396
|
+
const resCode = await getApiBridge().postVerificationCode({ email });
|
|
397
|
+
if (resCode.success) {
|
|
398
|
+
this.setStatus(`✉️ Fresh 6-digit verification code sent to ${email}!`, "success");
|
|
399
|
+
this.startResendCooldownTimer({ btnId: 'btn-quick-resend-code', durationSecs: 30 });
|
|
400
|
+
if (inputQuickOtp) {
|
|
401
|
+
inputQuickOtp.value = '';
|
|
402
|
+
inputQuickOtp.focus();
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
else {
|
|
406
|
+
this.setStatus(resCode.message || "Failed to resend verification code.", "error");
|
|
407
|
+
if (btnQuickResendCode) {
|
|
408
|
+
btnQuickResendCode.disabled = false;
|
|
409
|
+
btnQuickResendCode.textContent = "Resend Code";
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
catch (error) {
|
|
414
|
+
const emsg = extractErrorMsg(error);
|
|
415
|
+
console.error(`${lc} ${emsg}`);
|
|
416
|
+
this.setStatus(`Failed to resend verification code: ${emsg}`, "error");
|
|
417
|
+
if (btnQuickResendCode) {
|
|
418
|
+
btnQuickResendCode.disabled = false;
|
|
419
|
+
btnQuickResendCode.textContent = "Resend Code";
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
335
423
|
/**
|
|
336
424
|
* Triggered by the "Verify Code" button.
|
|
337
425
|
* Submits 6-digit OTP code to custodian server, builds local sync delegate keystone,
|
|
@@ -381,20 +469,10 @@ export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
|
|
|
381
469
|
this.setStatus("Failed to access local user space.", "error");
|
|
382
470
|
return;
|
|
383
471
|
}
|
|
384
|
-
const delegateSecret = await
|
|
385
|
-
|
|
386
|
-
const custodianSyncProfileName = process.env.KEYSTONE_PROFILE_CUSTODIAN_SYNC;
|
|
387
|
-
if (!custodianSyncProfileName || !custodianSyncProfileName.trim()) {
|
|
388
|
-
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)');
|
|
389
|
-
}
|
|
390
|
-
const delegateKeystone = await createSyncDelegateKeystone({
|
|
391
|
-
profileName: custodianSyncProfileName.trim(),
|
|
392
|
-
username,
|
|
393
|
-
deviceTag: generateDefaultDeviceTag(),
|
|
472
|
+
const { delegateKeystone, delegateSecret } = await createEmailDeviceSyncDelegate({
|
|
473
|
+
email,
|
|
394
474
|
primaryTjpAddr,
|
|
395
|
-
delegateSecret,
|
|
396
475
|
custodianReqs,
|
|
397
|
-
additionalFrameDetails: { client: 'space-gib-web' },
|
|
398
476
|
metaspace,
|
|
399
477
|
space,
|
|
400
478
|
});
|
|
@@ -434,6 +512,358 @@ export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
|
|
|
434
512
|
}
|
|
435
513
|
}
|
|
436
514
|
}
|
|
515
|
+
/**
|
|
516
|
+
* Triggered by the "Create Identity with Passkey / Biometrics" button.
|
|
517
|
+
* Orchestrates: WebAuthn Credential -> PRF Secret Evaluation -> Local Passkey Genesis -> Graph Collection -> Server Sync.
|
|
518
|
+
*/
|
|
519
|
+
async handleGeneratePasskey() {
|
|
520
|
+
const lc = `${this.lc}[${this.handleGeneratePasskey.name}]`;
|
|
521
|
+
const btn = this.shadowRoot?.getElementById('btn-generate-passkey');
|
|
522
|
+
try {
|
|
523
|
+
console.log(`${lc} starting passkey genesis orchestration...`);
|
|
524
|
+
const humanEyeDelay = 500;
|
|
525
|
+
this.setStatus("Initializing passkey genesis...", "info");
|
|
526
|
+
const inputUsername = this.elements.inputPasskeyUsername;
|
|
527
|
+
const inputDesc = this.elements.inputPasskeyDescription;
|
|
528
|
+
const inputEmail = this.elements.inputPasskeyEmail;
|
|
529
|
+
const inputName = this.elements.inputPasskeyName;
|
|
530
|
+
const checkboxActive = this.elements.checkboxPasskeyMakeActive;
|
|
531
|
+
const username = (inputUsername?.value || '').trim();
|
|
532
|
+
if (inputUsername)
|
|
533
|
+
inputUsername.value = username;
|
|
534
|
+
if (!this.validateInputPattern({
|
|
535
|
+
input: inputUsername,
|
|
536
|
+
pattern: KEYSTONE_USERNAME_REGEXP,
|
|
537
|
+
errorMessage: "Username must be 1-63 characters (alphanumeric, underscores, hyphens, dots).",
|
|
538
|
+
required: true
|
|
539
|
+
})) {
|
|
540
|
+
this.setStatus("Invalid username. Must be 1-63 characters (alphanumeric, _, -, .).", "error");
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
const description = (inputDesc?.value || '').trim();
|
|
544
|
+
if (inputDesc)
|
|
545
|
+
inputDesc.value = description;
|
|
546
|
+
if (inputDesc && !this.validateInputPattern({
|
|
547
|
+
input: inputDesc,
|
|
548
|
+
pattern: KEYSTONE_DESCRIPTION_REGEXP,
|
|
549
|
+
errorMessage: "Description must be 0-128 characters.",
|
|
550
|
+
required: false
|
|
551
|
+
})) {
|
|
552
|
+
this.setStatus("Invalid description. Max 128 chars.", "error");
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
const email = (inputEmail?.value || '').trim();
|
|
556
|
+
if (inputEmail)
|
|
557
|
+
inputEmail.value = email;
|
|
558
|
+
if (email && !validateEmail(email)) {
|
|
559
|
+
inputEmail?.classList.add('invalid');
|
|
560
|
+
this.setStatus("Invalid email address format.", "error");
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
if (btn) {
|
|
564
|
+
btn.disabled = true;
|
|
565
|
+
btn.textContent = 'Awaiting Biometrics / Security Key...';
|
|
566
|
+
}
|
|
567
|
+
// Pre-check email availability on server if email provided
|
|
568
|
+
if (email) {
|
|
569
|
+
this.showBusy({
|
|
570
|
+
isBusy: true,
|
|
571
|
+
title: 'Checking Email Availability...',
|
|
572
|
+
msg: 'Checking server identity index...',
|
|
573
|
+
animationEmoji: '🔍'
|
|
574
|
+
});
|
|
575
|
+
const resCheck = await getApiBridge().checkEmailAvailability({ email });
|
|
576
|
+
if (!resCheck.available) {
|
|
577
|
+
const msg = resCheck.message || "Email/Identity is already registered.";
|
|
578
|
+
this.setStatus(msg, "error");
|
|
579
|
+
this.showBusy({ isBusy: false });
|
|
580
|
+
if (btn) {
|
|
581
|
+
btn.disabled = false;
|
|
582
|
+
btn.textContent = '🛡️ Create Identity with Passkey / Biometrics';
|
|
583
|
+
}
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
// 1. WebAuthn Passkey Registration & PRF Secret Evaluation
|
|
588
|
+
this.showBusy({
|
|
589
|
+
isBusy: true,
|
|
590
|
+
title: 'Touch Biometrics or Security Key',
|
|
591
|
+
msg: 'Please touch your fingerprint sensor, Face ID, or insert your USB security key when prompted...',
|
|
592
|
+
animationEmoji: '🛡️'
|
|
593
|
+
});
|
|
594
|
+
const rawSalt = crypto.getRandomValues(new Uint8Array(32));
|
|
595
|
+
const saltHex = bufferToHex(rawSalt.buffer);
|
|
596
|
+
const regResult = await registerPasskeyCredential({
|
|
597
|
+
username,
|
|
598
|
+
displayName: username,
|
|
599
|
+
salt: saltHex
|
|
600
|
+
});
|
|
601
|
+
const { credentialId, transports } = regResult;
|
|
602
|
+
let passkeySecret = regResult.passkeySecret;
|
|
603
|
+
if (!passkeySecret) {
|
|
604
|
+
const prfResult = await evaluatePasskeyPrf({
|
|
605
|
+
credentialId,
|
|
606
|
+
salt: saltHex,
|
|
607
|
+
transports
|
|
608
|
+
});
|
|
609
|
+
passkeySecret = prfResult.passkeySecret;
|
|
610
|
+
}
|
|
611
|
+
const passkeyName = (inputName?.value || '').trim() || generateDefaultDeviceTag();
|
|
612
|
+
const passkeyTag = getSaferSubstring({ text: passkeyName, length: 12, keepLiterals: ['-'] }) || 'key';
|
|
613
|
+
const poolId = `passkey-manage-${passkeyTag}`;
|
|
614
|
+
const passkeyEntry = {
|
|
615
|
+
credentialId,
|
|
616
|
+
poolId,
|
|
617
|
+
name: passkeyName,
|
|
618
|
+
salt: saltHex,
|
|
619
|
+
createdUTC: new Date().toISOString(),
|
|
620
|
+
...(transports && transports.length > 0 ? { transports } : {})
|
|
621
|
+
};
|
|
622
|
+
this.showBusy({
|
|
623
|
+
isBusy: true,
|
|
624
|
+
title: 'Generating Identity...',
|
|
625
|
+
msg: 'Compiling sovereign passkey challenge pools...',
|
|
626
|
+
animationEmoji: '⚙️'
|
|
627
|
+
});
|
|
628
|
+
// 2. Prepare Sovereign Keystone Configs
|
|
629
|
+
const sovereignPrimaryProfileName = process.env.KEYSTONE_PROFILE_SOVEREIGN_PRIMARY;
|
|
630
|
+
if (!sovereignPrimaryProfileName || !sovereignPrimaryProfileName.trim()) {
|
|
631
|
+
throw new Error('KEYSTONE_PROFILE_SOVEREIGN_PRIMARY environment variable is missing.');
|
|
632
|
+
}
|
|
633
|
+
const builder = KeystoneProfileBuilder.buildKeystone(sovereignPrimaryProfileName.trim())
|
|
634
|
+
.withUsername(username)
|
|
635
|
+
.withDescription(description);
|
|
636
|
+
if (email) {
|
|
637
|
+
builder.withEmails([email]);
|
|
638
|
+
}
|
|
639
|
+
builder.withDetails({
|
|
640
|
+
client: 'space-gib-web',
|
|
641
|
+
role: 'domain-identity',
|
|
642
|
+
passkeys: {
|
|
643
|
+
[credentialId]: passkeyEntry
|
|
644
|
+
}
|
|
645
|
+
});
|
|
646
|
+
const configs = await builder.compileConfigs();
|
|
647
|
+
// Re-label manage pool to passkey pool id
|
|
648
|
+
for (const cfg of configs) {
|
|
649
|
+
if (cfg.id === 'manage') {
|
|
650
|
+
cfg.id = poolId;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
// 3. Prepare Metaspace & Local Space
|
|
654
|
+
const metaspace = await getGlobalMetaspace_waitIfNeeded();
|
|
655
|
+
const space = await metaspace.getLocalUserSpace({ lock: false });
|
|
656
|
+
if (!space) {
|
|
657
|
+
this.setStatus("Failed to access local user space.", "error");
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
// 4. Perform Local Genesis
|
|
661
|
+
this.showBusy({
|
|
662
|
+
isBusy: true,
|
|
663
|
+
title: 'Generating Identity...',
|
|
664
|
+
msg: 'Generating cryptographic domain identity keys...',
|
|
665
|
+
animationEmoji: '🔑'
|
|
666
|
+
});
|
|
667
|
+
const keystoneService = new KeystoneService_V1();
|
|
668
|
+
const keystoneIbGib = await keystoneService.genesis({
|
|
669
|
+
masterSecret: passkeySecret,
|
|
670
|
+
emails: email ? [email] : undefined,
|
|
671
|
+
credentialIds: [credentialId],
|
|
672
|
+
configs,
|
|
673
|
+
metaspace,
|
|
674
|
+
space,
|
|
675
|
+
frameDetails: builder.getFrameDetails(),
|
|
676
|
+
isPrimary: true
|
|
677
|
+
});
|
|
678
|
+
const primaryTjpAddr = getTjpAddr({ ibGib: keystoneIbGib, defaultIfNone: 'incomingAddr' });
|
|
679
|
+
console.log(`${lc} passkey local genesis complete: ${primaryTjpAddr}`);
|
|
680
|
+
await delay(humanEyeDelay);
|
|
681
|
+
this.setStatus("Local genesis complete. Creating passkey sync delegate...", "info");
|
|
682
|
+
// 5. Create Passkey Device Sync Delegate
|
|
683
|
+
this.showBusy({
|
|
684
|
+
isBusy: true,
|
|
685
|
+
title: 'Creating Sync Delegate...',
|
|
686
|
+
msg: 'Generating cryptographic sync delegate keys...',
|
|
687
|
+
animationEmoji: '🔑'
|
|
688
|
+
});
|
|
689
|
+
const { delegateKeystone: delegateKeystoneIbGib, deviceTag, delegateSecret } = await createPasskeyDeviceSyncDelegate({
|
|
690
|
+
username,
|
|
691
|
+
primaryTjpAddr,
|
|
692
|
+
passkeySecret,
|
|
693
|
+
transports,
|
|
694
|
+
metaspace,
|
|
695
|
+
space,
|
|
696
|
+
});
|
|
697
|
+
// 6. Register Delegate locally on primary keystone
|
|
698
|
+
const evolvedParentKeystoneIbGib = await keystoneService.registerDelegate({
|
|
699
|
+
parentKeystone: keystoneIbGib,
|
|
700
|
+
delegateKeystone: delegateKeystoneIbGib,
|
|
701
|
+
masterSecret: passkeySecret,
|
|
702
|
+
signingPoolId: poolId,
|
|
703
|
+
allowedVerbs: [KEYSTONE_VERB_SYNC, KEYSTONE_VERB_CONNECT],
|
|
704
|
+
deviceName: deviceTag,
|
|
705
|
+
metaspace,
|
|
706
|
+
space,
|
|
707
|
+
});
|
|
708
|
+
// 7. Post to Server & Handle Verification
|
|
709
|
+
this.setStatus("Syncing genesis keystone to server...", "info");
|
|
710
|
+
this.showBusy({
|
|
711
|
+
isBusy: true,
|
|
712
|
+
title: 'Syncing to Server...',
|
|
713
|
+
msg: 'Staging pending genesis keystone on server...',
|
|
714
|
+
animationEmoji: '🔄'
|
|
715
|
+
});
|
|
716
|
+
const resSync = await getApiBridge().postGenesisKeystone(keystoneIbGib);
|
|
717
|
+
if (!resSync.success) {
|
|
718
|
+
const msg = `Sync failed: ${resSync.message}`;
|
|
719
|
+
console.error(`${lc} ${msg}`);
|
|
720
|
+
this.setStatus(msg, "error");
|
|
721
|
+
this.showBusy({ isBusy: false });
|
|
722
|
+
if (btn) {
|
|
723
|
+
btn.disabled = false;
|
|
724
|
+
btn.textContent = '🛡️ Create Identity with Passkey / Biometrics';
|
|
725
|
+
}
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
const isPending = resSync.pending || resSync.statusCode === 202;
|
|
729
|
+
if (isPending) {
|
|
730
|
+
console.log(`${lc} Genesis keystone quarantined in pending staging area. Prompting for 6-digit verification code.`);
|
|
731
|
+
this.showBusy({ isBusy: false });
|
|
732
|
+
this.setFormDisabled(true);
|
|
733
|
+
if (!this.shadowRoot) {
|
|
734
|
+
throw new Error(`(UNEXPECTED) this.shadowRoot falsy? (E: 6d316f5a2bf8e6a0286306468d35c826)`);
|
|
735
|
+
}
|
|
736
|
+
const secVerify = shadowRoot_getElementById(this.shadowRoot, 'section-code-verification');
|
|
737
|
+
const inputVerifyCode = shadowRoot_getElementById(this.shadowRoot, 'input-verify-code');
|
|
738
|
+
const btnSubmitCode = shadowRoot_getElementById(this.shadowRoot, 'btn-submit-code');
|
|
739
|
+
const btnResendCode = shadowRoot_getElementById(this.shadowRoot, 'btn-resend-code');
|
|
740
|
+
secVerify.classList.remove('hidden');
|
|
741
|
+
secVerify.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
742
|
+
this.setStatus("✉️ Verification code sent to your email. Enter 6-digit code below:", "info");
|
|
743
|
+
this.startResendCooldownTimer({ btnId: 'btn-resend-code', durationSecs: 30 });
|
|
744
|
+
const onResendCode = async () => {
|
|
745
|
+
btnResendCode.disabled = true;
|
|
746
|
+
btnResendCode.textContent = "Sending...";
|
|
747
|
+
this.setStatus("Resending verification code to your email...", "info");
|
|
748
|
+
try {
|
|
749
|
+
const resResend = await getApiBridge().postVerificationCode({ email, primaryTjpAddr });
|
|
750
|
+
if (resResend.success) {
|
|
751
|
+
this.setStatus("✉️ Fresh 6-digit verification code sent to your email!", "success");
|
|
752
|
+
this.startResendCooldownTimer({ btnId: 'btn-resend-code', durationSecs: 30 });
|
|
753
|
+
inputVerifyCode.value = '';
|
|
754
|
+
inputVerifyCode.focus();
|
|
755
|
+
}
|
|
756
|
+
else {
|
|
757
|
+
this.setStatus(resResend.message || "Failed to resend verification code.", "error");
|
|
758
|
+
btnResendCode.disabled = false;
|
|
759
|
+
btnResendCode.textContent = "Resend Code";
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
catch (error) {
|
|
763
|
+
const emsg = extractErrorMsg(error);
|
|
764
|
+
console.error(`${lc} ${emsg}`);
|
|
765
|
+
this.setStatus(`Failed to resend verification code: ${emsg}`, "error");
|
|
766
|
+
btnResendCode.disabled = false;
|
|
767
|
+
btnResendCode.textContent = "Resend Code";
|
|
768
|
+
}
|
|
769
|
+
};
|
|
770
|
+
btnResendCode.addEventListener('click', onResendCode);
|
|
771
|
+
await new Promise((resolve) => {
|
|
772
|
+
const onSubmitCode = async () => {
|
|
773
|
+
const code = inputVerifyCode.value.trim();
|
|
774
|
+
if (!code || code.length !== 6 || !/^\d{6}$/.test(code)) {
|
|
775
|
+
this.setStatus("Please enter a valid 6-digit numeric verification code (e.g. 482901).", "error");
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
btnSubmitCode.disabled = true;
|
|
779
|
+
btnSubmitCode.textContent = "Verifying...";
|
|
780
|
+
this.showBusy({
|
|
781
|
+
isBusy: true,
|
|
782
|
+
title: 'Verifying & Activating...',
|
|
783
|
+
msg: 'Great! Juust a moment...\n(Validating code, promoting server domain space, other security stuff...)',
|
|
784
|
+
animationEmoji: '🔐'
|
|
785
|
+
});
|
|
786
|
+
const resVerify = await getApiBridge().postVerifyCode({ email, primaryTjpAddr, code });
|
|
787
|
+
if (!resVerify.success) {
|
|
788
|
+
this.showBusy({ isBusy: false });
|
|
789
|
+
this.setStatus(resVerify.message || "Incorrect verification code.", "error");
|
|
790
|
+
btnSubmitCode.disabled = false;
|
|
791
|
+
btnSubmitCode.textContent = "Verify Code";
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
console.log(`${lc} Code verified & server domain promoted! Syncing delegate...`);
|
|
795
|
+
this.showBusy({
|
|
796
|
+
isBusy: true,
|
|
797
|
+
title: 'Syncing Delegate...',
|
|
798
|
+
msg: 'Posting evolved domain identity and sync delegate to server...',
|
|
799
|
+
animationEmoji: '⚡'
|
|
800
|
+
});
|
|
801
|
+
btnSubmitCode.removeEventListener('click', onSubmitCode);
|
|
802
|
+
secVerify.classList.add('hidden');
|
|
803
|
+
this.setFormDisabled(false);
|
|
804
|
+
// Now that server domain space is promoted, post evolved keystone + delegate
|
|
805
|
+
const resEvolve = await getApiBridge().putEvolveKeystone({
|
|
806
|
+
domainAddr: primaryTjpAddr,
|
|
807
|
+
keystoneIbGib: evolvedParentKeystoneIbGib,
|
|
808
|
+
relatedKeystoneIbGibs: [delegateKeystoneIbGib]
|
|
809
|
+
});
|
|
810
|
+
if (!resEvolve.success) {
|
|
811
|
+
console.warn(`${lc} Delegate sync warning after code verification: ${resEvolve.message}`);
|
|
812
|
+
}
|
|
813
|
+
resolve();
|
|
814
|
+
};
|
|
815
|
+
btnSubmitCode.addEventListener('click', onSubmitCode);
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
else {
|
|
819
|
+
// Post evolved keystone + delegate keystone immediately
|
|
820
|
+
this.setStatus("Syncing delegate registration to server...", "info");
|
|
821
|
+
this.showBusy({
|
|
822
|
+
isBusy: true,
|
|
823
|
+
title: 'Syncing Delegate...',
|
|
824
|
+
msg: 'Posting evolved domain identity and sync delegate to server...',
|
|
825
|
+
animationEmoji: '🔄'
|
|
826
|
+
});
|
|
827
|
+
const resEvolve = await getApiBridge().putEvolveKeystone({
|
|
828
|
+
domainAddr: primaryTjpAddr,
|
|
829
|
+
keystoneIbGib: evolvedParentKeystoneIbGib,
|
|
830
|
+
relatedKeystoneIbGibs: [delegateKeystoneIbGib]
|
|
831
|
+
});
|
|
832
|
+
if (!resEvolve.success) {
|
|
833
|
+
const msg = `Delegate registration sync failed: ${resEvolve.message}`;
|
|
834
|
+
console.error(`${lc} ${msg}`);
|
|
835
|
+
this.setStatus(msg, "error");
|
|
836
|
+
this.showBusy({ isBusy: false });
|
|
837
|
+
if (btn) {
|
|
838
|
+
btn.disabled = false;
|
|
839
|
+
btn.textContent = '🛡️ Create Identity with Passkey / Biometrics';
|
|
840
|
+
}
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
// 8. Finalize Onboarding
|
|
845
|
+
await this.finalizeIdentityOnboarding({
|
|
846
|
+
primaryKeystone: evolvedParentKeystoneIbGib,
|
|
847
|
+
delegateKeystone: delegateKeystoneIbGib,
|
|
848
|
+
primaryTjpAddr,
|
|
849
|
+
masterSecret: passkeySecret,
|
|
850
|
+
delegateSecret,
|
|
851
|
+
makeActive: checkboxActive?.checked ?? true
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
catch (error) {
|
|
855
|
+
const emsg = extractErrorMsg(error);
|
|
856
|
+
console.error(`${lc} ${emsg}`, error);
|
|
857
|
+
this.setStatus(`Error: ${emsg}`, "error");
|
|
858
|
+
}
|
|
859
|
+
finally {
|
|
860
|
+
this.showBusy({ isBusy: false });
|
|
861
|
+
if (btn) {
|
|
862
|
+
btn.disabled = false;
|
|
863
|
+
btn.textContent = '🛡️ Create Identity with Passkey / Biometrics';
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
}
|
|
437
867
|
/**
|
|
438
868
|
* Triggered by the "Generate" button.
|
|
439
869
|
* Orchestrates: Secret -> Local Genesis -> Graph Collection -> Server Sync.
|
|
@@ -562,19 +992,10 @@ export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
|
|
|
562
992
|
msg: 'Generating cryptographic sync delegate keys...',
|
|
563
993
|
animationEmoji: '🔑'
|
|
564
994
|
});
|
|
565
|
-
const delegateSecret = await
|
|
566
|
-
await setDeviceDelegateSecret({ primaryTjpAddr, delegateSecret });
|
|
567
|
-
const sovereignSyncProfileName = process.env.KEYSTONE_PROFILE_SOVEREIGN_SYNC;
|
|
568
|
-
if (!sovereignSyncProfileName || !sovereignSyncProfileName.trim()) {
|
|
569
|
-
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)');
|
|
570
|
-
}
|
|
571
|
-
const delegateKeystoneIbGib = await createSyncDelegateKeystone({
|
|
572
|
-
profileName: sovereignSyncProfileName.trim(),
|
|
995
|
+
const { delegateKeystone: delegateKeystoneIbGib, deviceTag, delegateSecret } = await createSovereignDeviceSyncDelegate({
|
|
573
996
|
username,
|
|
574
|
-
deviceTag: generateDefaultDeviceTag(),
|
|
575
997
|
primaryTjpAddr,
|
|
576
|
-
|
|
577
|
-
additionalFrameDetails: { client: 'space-gib-web' },
|
|
998
|
+
masterSecret,
|
|
578
999
|
metaspace,
|
|
579
1000
|
space,
|
|
580
1001
|
});
|
|
@@ -593,9 +1014,11 @@ export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
|
|
|
593
1014
|
parentKeystone: keystoneIbGib,
|
|
594
1015
|
delegateKeystone: delegateKeystoneIbGib,
|
|
595
1016
|
masterSecret,
|
|
1017
|
+
signingPoolId: 'manage',
|
|
596
1018
|
metaspace,
|
|
597
1019
|
space,
|
|
598
|
-
allowedVerbs: [KEYSTONE_VERB_SYNC, KEYSTONE_VERB_CONNECT]
|
|
1020
|
+
allowedVerbs: [KEYSTONE_VERB_SYNC, KEYSTONE_VERB_CONNECT],
|
|
1021
|
+
deviceName: deviceTag,
|
|
599
1022
|
});
|
|
600
1023
|
const evolvedParentAddr = getIbGibAddr({ ibGib: evolvedParentKeystoneIbGib });
|
|
601
1024
|
console.log(`${lc} delegate registered. evolved parent address: ${evolvedParentAddr}`);
|
|
@@ -632,19 +1055,32 @@ export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
|
|
|
632
1055
|
secVerify.classList.remove('hidden');
|
|
633
1056
|
secVerify.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
634
1057
|
this.setStatus("✉️ Verification code sent to your email. Enter 6-digit code below:", "info");
|
|
1058
|
+
this.startResendCooldownTimer({ btnId: 'btn-resend-code', durationSecs: 30 });
|
|
635
1059
|
const onResendCode = async () => {
|
|
636
1060
|
btnResendCode.disabled = true;
|
|
637
1061
|
btnResendCode.textContent = "Sending...";
|
|
638
1062
|
this.setStatus("Resending verification code to your email...", "info");
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
1063
|
+
try {
|
|
1064
|
+
const resResend = await getApiBridge().postVerificationCode({ email, primaryTjpAddr: primaryTjpAddr });
|
|
1065
|
+
if (resResend.success) {
|
|
1066
|
+
this.setStatus("✉️ Fresh 6-digit verification code sent to your email!", "success");
|
|
1067
|
+
this.startResendCooldownTimer({ btnId: 'btn-resend-code', durationSecs: 30 });
|
|
1068
|
+
inputVerifyCode.value = '';
|
|
1069
|
+
inputVerifyCode.focus();
|
|
1070
|
+
}
|
|
1071
|
+
else {
|
|
1072
|
+
this.setStatus(resResend.message || "Failed to resend verification code.", "error");
|
|
1073
|
+
btnResendCode.disabled = false;
|
|
1074
|
+
btnResendCode.textContent = "Resend Code";
|
|
1075
|
+
}
|
|
642
1076
|
}
|
|
643
|
-
|
|
644
|
-
|
|
1077
|
+
catch (error) {
|
|
1078
|
+
const emsg = extractErrorMsg(error);
|
|
1079
|
+
console.error(`${lc} ${emsg}`);
|
|
1080
|
+
this.setStatus(`Failed to resend verification code: ${emsg}`, "error");
|
|
1081
|
+
btnResendCode.disabled = false;
|
|
1082
|
+
btnResendCode.textContent = "Resend Code";
|
|
645
1083
|
}
|
|
646
|
-
btnResendCode.disabled = false;
|
|
647
|
-
btnResendCode.textContent = "Resend Code";
|
|
648
1084
|
};
|
|
649
1085
|
btnResendCode.addEventListener('click', onResendCode);
|
|
650
1086
|
await new Promise((resolve, reject) => {
|