@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
|
@@ -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,13 +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 } from "@ibgib/core-gib/dist/keystone/strategy/hash-reveal-v1/hash-reveal-v1.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";
|
|
18
|
+
import { generateDefaultDeviceTag } from "../../../../util/device-helper.mjs";
|
|
19
|
+
import { registerPasskeyCredential, evaluatePasskeyPrf, createPasskeyDeviceSyncDelegate, bufferToHex } from "../../../../identity/passkey-helper.mjs";
|
|
19
20
|
import { EVENT_IBGIB_IDENTITY_REQUEST_CHANGE, EVENT_IBGIB_UI_MESSAGE } from "../../../ui-constants.mjs";
|
|
20
21
|
import { alertUser, shadowRoot_getElementById } from "../../../../helpers.web.mjs";
|
|
21
22
|
export const KEYSTONE_CREATOR_COMPONENT_NAME = 'ibgib-keystone-creator';
|
|
@@ -50,12 +51,43 @@ export class KeystoneCreatorComponentMeta extends IbGibDynamicComponentMetaBase
|
|
|
50
51
|
export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
|
|
51
52
|
lc = `[KeystoneCreatorComponentInstance]`;
|
|
52
53
|
hasExistingIdentities = false;
|
|
54
|
+
resendCooldownInterval = null;
|
|
53
55
|
constructor() {
|
|
54
56
|
super();
|
|
55
57
|
}
|
|
56
58
|
async initialize(opts) {
|
|
57
59
|
await super.initialize(opts);
|
|
58
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
|
+
}
|
|
59
91
|
async created() {
|
|
60
92
|
this.elements = {};
|
|
61
93
|
this.elements.containerEl = this.shadowRoot.getElementById('container');
|
|
@@ -68,6 +100,12 @@ export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
|
|
|
68
100
|
this.elements.inputEmail = this.shadowRoot.getElementById('input-email');
|
|
69
101
|
this.elements.inputSecret = this.shadowRoot.getElementById('input-secret');
|
|
70
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');
|
|
71
109
|
this.elements.statusArea = this.shadowRoot.getElementById('status-area');
|
|
72
110
|
this.elements.statusText = this.shadowRoot.getElementById('status-text');
|
|
73
111
|
// ElementBase requires contentEl
|
|
@@ -98,46 +136,46 @@ export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
|
|
|
98
136
|
console.warn(`${lc} Error checking existing identities: ${extractErrorMsg(error)}`);
|
|
99
137
|
this.hasExistingIdentities = false;
|
|
100
138
|
}
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
if (
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
+
}
|
|
111
151
|
}
|
|
112
152
|
}
|
|
113
153
|
}
|
|
114
|
-
async disconnected() {
|
|
115
|
-
// cleanup if needed
|
|
116
|
-
}
|
|
117
154
|
initHandlers() {
|
|
118
|
-
// Mode Tab Switching (Quick vs Password-based)
|
|
155
|
+
// Mode Tab Switching (Quick vs Passkey vs Password-based)
|
|
119
156
|
const tabQuick = this.shadowRoot?.getElementById('tab-quick');
|
|
157
|
+
const tabPasskey = this.shadowRoot?.getElementById('tab-passkey');
|
|
120
158
|
const tabPassword = this.shadowRoot?.getElementById('tab-password');
|
|
121
159
|
const panelQuick = this.shadowRoot?.getElementById('panel-quick');
|
|
160
|
+
const panelPasskey = this.shadowRoot?.getElementById('panel-passkey');
|
|
122
161
|
const panelPassword = this.shadowRoot?.getElementById('panel-password');
|
|
123
162
|
const subtitleQuick = this.shadowRoot?.getElementById('subtitle-quick');
|
|
163
|
+
const subtitlePasskey = this.shadowRoot?.getElementById('subtitle-passkey');
|
|
124
164
|
const subtitlePassword = this.shadowRoot?.getElementById('subtitle-password');
|
|
125
|
-
|
|
126
|
-
tabQuick
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
subtitleQuick?.classList.add('hidden');
|
|
140
|
-
});
|
|
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'));
|
|
141
179
|
const btnGoogleSso = this.shadowRoot?.getElementById('btn-google-sso');
|
|
142
180
|
btnGoogleSso?.addEventListener('click', () => this.handleSsoClick('google'));
|
|
143
181
|
const btnGithubSso = this.shadowRoot?.getElementById('btn-github-sso');
|
|
@@ -157,8 +195,11 @@ export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
|
|
|
157
195
|
const secQuickOtp = this.shadowRoot?.getElementById('section-quick-otp');
|
|
158
196
|
const inputQuickOtp = this.shadowRoot?.getElementById('input-quick-otp');
|
|
159
197
|
const btnQuickVerifyCode = this.shadowRoot?.getElementById('btn-quick-verify-code');
|
|
198
|
+
const btnQuickResendCode = this.shadowRoot?.getElementById('btn-quick-resend-code');
|
|
160
199
|
btnQuickSendCode?.addEventListener('click', () => this.handleQuickSendCode());
|
|
161
200
|
btnQuickVerifyCode?.addEventListener('click', () => this.handleQuickVerifyCode());
|
|
201
|
+
btnQuickResendCode?.addEventListener('click', () => this.handleQuickResendCode());
|
|
202
|
+
this.elements.btnGeneratePasskey?.addEventListener('click', () => this.handleGeneratePasskey());
|
|
162
203
|
this.elements.btnGenerateEl.addEventListener('click', () => this.handleGenerate());
|
|
163
204
|
this.shadowRoot.getElementById('btn-switch-sign-in')?.addEventListener('click', (e) => {
|
|
164
205
|
e.preventDefault();
|
|
@@ -169,6 +210,15 @@ export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
|
|
|
169
210
|
}));
|
|
170
211
|
});
|
|
171
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
|
+
});
|
|
172
222
|
this.elements.inputUsername?.addEventListener('input', () => {
|
|
173
223
|
this.elements.inputUsername.classList.remove('invalid');
|
|
174
224
|
});
|
|
@@ -235,37 +285,21 @@ export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
|
|
|
235
285
|
msg: 'Generating cryptographic sync delegate keys...',
|
|
236
286
|
animationEmoji: '🔑'
|
|
237
287
|
});
|
|
238
|
-
const delegateSecret = await deriveLocalCustodianDelegateSecret({ primaryTjpAddr });
|
|
239
|
-
const email = resLogin.userInfo?.email || '';
|
|
240
|
-
const username = email ? email.split('@')[0] : 'user';
|
|
241
|
-
const custodianSyncProfileName = process.env.KEYSTONE_PROFILE_CUSTODIAN_SYNC;
|
|
242
|
-
if (!custodianSyncProfileName || !custodianSyncProfileName.trim()) {
|
|
243
|
-
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)');
|
|
244
|
-
}
|
|
245
|
-
const delegateBuilder = KeystoneProfileBuilder.buildKeystone(custodianSyncProfileName.trim())
|
|
246
|
-
.withUsername(`${username}-sync`)
|
|
247
|
-
.withDescription(`Sync Delegate for ${username}`)
|
|
248
|
-
.withDetails({
|
|
249
|
-
client: 'space-gib-web',
|
|
250
|
-
role: 'sync-delegate',
|
|
251
|
-
primaryTjpAddr,
|
|
252
|
-
custodianReqs,
|
|
253
|
-
});
|
|
254
|
-
const configs = await delegateBuilder.compileConfigs();
|
|
255
288
|
const metaspace = await getGlobalMetaspace_waitIfNeeded();
|
|
256
289
|
const space = await metaspace.getLocalUserSpace({ lock: false });
|
|
257
290
|
if (!space) {
|
|
258
291
|
this.setStatus('Could not access local user space.', 'error');
|
|
259
292
|
return;
|
|
260
293
|
}
|
|
261
|
-
const
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
294
|
+
const email = resLogin.userInfo?.email || '';
|
|
295
|
+
const username = email ? email.split('@')[0] : 'user';
|
|
296
|
+
const { delegateKeystone, delegateSecret } = await createOAuth2DeviceSyncDelegate({
|
|
297
|
+
providerId,
|
|
298
|
+
username,
|
|
299
|
+
primaryTjpAddr,
|
|
300
|
+
custodianReqs,
|
|
265
301
|
metaspace,
|
|
266
302
|
space,
|
|
267
|
-
frameDetails: delegateBuilder.getFrameDetails(),
|
|
268
|
-
isPrimary: false
|
|
269
303
|
});
|
|
270
304
|
this.setStatus('Registering device delegate with custodian server...', 'info');
|
|
271
305
|
this.showBusy({
|
|
@@ -322,6 +356,7 @@ export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
|
|
|
322
356
|
if (resCode.success) {
|
|
323
357
|
secQuickOtp?.classList.remove('hidden');
|
|
324
358
|
this.setStatus("✉️ 6-digit verification code sent to your email!", "success");
|
|
359
|
+
this.startResendCooldownTimer({ btnId: 'btn-quick-resend-code', durationSecs: 30 });
|
|
325
360
|
}
|
|
326
361
|
else {
|
|
327
362
|
this.setStatus(resCode.message || "Failed to dispatch verification code.", "error");
|
|
@@ -339,6 +374,52 @@ export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
|
|
|
339
374
|
}
|
|
340
375
|
}
|
|
341
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
|
+
}
|
|
342
423
|
/**
|
|
343
424
|
* Triggered by the "Verify Code" button.
|
|
344
425
|
* Submits 6-digit OTP code to custodian server, builds local sync delegate keystone,
|
|
@@ -382,36 +463,18 @@ export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
|
|
|
382
463
|
msg: 'Generating cryptographic sync delegate keys...',
|
|
383
464
|
animationEmoji: '🔑'
|
|
384
465
|
});
|
|
385
|
-
const delegateSecret = await deriveLocalCustodianDelegateSecret({ primaryTjpAddr });
|
|
386
|
-
const username = email ? email.split('@')[0] : 'user';
|
|
387
|
-
const custodianSyncProfileName = process.env.KEYSTONE_PROFILE_CUSTODIAN_SYNC;
|
|
388
|
-
if (!custodianSyncProfileName || !custodianSyncProfileName.trim()) {
|
|
389
|
-
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)');
|
|
390
|
-
}
|
|
391
|
-
const delegateBuilder = KeystoneProfileBuilder.buildKeystone(custodianSyncProfileName.trim())
|
|
392
|
-
.withUsername(`${username}-sync`)
|
|
393
|
-
.withDescription(`Sync Delegate for ${username}`)
|
|
394
|
-
.withDetails({
|
|
395
|
-
client: 'space-gib-web',
|
|
396
|
-
role: 'sync-delegate',
|
|
397
|
-
primaryTjpAddr,
|
|
398
|
-
custodianReqs,
|
|
399
|
-
});
|
|
400
|
-
const configs = await delegateBuilder.compileConfigs();
|
|
401
466
|
const metaspace = await getGlobalMetaspace_waitIfNeeded();
|
|
402
467
|
const space = await metaspace.getLocalUserSpace({ lock: false });
|
|
403
468
|
if (!space) {
|
|
404
469
|
this.setStatus("Failed to access local user space.", "error");
|
|
405
470
|
return;
|
|
406
471
|
}
|
|
407
|
-
const
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
472
|
+
const { delegateKeystone, delegateSecret } = await createEmailDeviceSyncDelegate({
|
|
473
|
+
email,
|
|
474
|
+
primaryTjpAddr,
|
|
475
|
+
custodianReqs,
|
|
411
476
|
metaspace,
|
|
412
477
|
space,
|
|
413
|
-
frameDetails: delegateBuilder.getFrameDetails(),
|
|
414
|
-
isPrimary: false
|
|
415
478
|
});
|
|
416
479
|
// Register delegate with server
|
|
417
480
|
this.setStatus("Registering device delegate with custodian server...", "info");
|
|
@@ -449,6 +512,358 @@ export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
|
|
|
449
512
|
}
|
|
450
513
|
}
|
|
451
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
|
+
}
|
|
452
867
|
/**
|
|
453
868
|
* Triggered by the "Generate" button.
|
|
454
869
|
* Orchestrates: Secret -> Local Genesis -> Graph Collection -> Server Sync.
|
|
@@ -577,26 +992,12 @@ export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
|
|
|
577
992
|
msg: 'Generating cryptographic sync delegate keys...',
|
|
578
993
|
animationEmoji: '🔑'
|
|
579
994
|
});
|
|
580
|
-
const delegateSecret = await
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
}
|
|
585
|
-
const delegateBuilder = KeystoneProfileBuilder.buildKeystone(sovereignSyncProfileName.trim())
|
|
586
|
-
.withUsername(`${username}-sync`)
|
|
587
|
-
.withDescription(`Sync Delegate for ${username}`)
|
|
588
|
-
.withDetails({
|
|
589
|
-
client: 'space-gib-web',
|
|
590
|
-
role: 'sync-delegate',
|
|
591
|
-
primaryTjpAddr: primaryTjpAddr,
|
|
592
|
-
});
|
|
593
|
-
const delegateConfigs = await delegateBuilder.compileConfigs();
|
|
594
|
-
const delegateKeystoneIbGib = await keystoneService.genesis({
|
|
595
|
-
masterSecret: delegateSecret,
|
|
596
|
-
configs: delegateConfigs,
|
|
995
|
+
const { delegateKeystone: delegateKeystoneIbGib, deviceTag, delegateSecret } = await createSovereignDeviceSyncDelegate({
|
|
996
|
+
username,
|
|
997
|
+
primaryTjpAddr,
|
|
998
|
+
masterSecret,
|
|
597
999
|
metaspace,
|
|
598
1000
|
space,
|
|
599
|
-
frameDetails: delegateBuilder.getFrameDetails()
|
|
600
1001
|
});
|
|
601
1002
|
const delegateAddr = getIbGibAddr({ ibGib: delegateKeystoneIbGib });
|
|
602
1003
|
console.log(`${lc} sync delegate genesis complete: ${delegateAddr}`);
|
|
@@ -613,9 +1014,11 @@ export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
|
|
|
613
1014
|
parentKeystone: keystoneIbGib,
|
|
614
1015
|
delegateKeystone: delegateKeystoneIbGib,
|
|
615
1016
|
masterSecret,
|
|
1017
|
+
signingPoolId: 'manage',
|
|
616
1018
|
metaspace,
|
|
617
1019
|
space,
|
|
618
|
-
allowedVerbs: [KEYSTONE_VERB_SYNC, KEYSTONE_VERB_CONNECT]
|
|
1020
|
+
allowedVerbs: [KEYSTONE_VERB_SYNC, KEYSTONE_VERB_CONNECT],
|
|
1021
|
+
deviceName: deviceTag,
|
|
619
1022
|
});
|
|
620
1023
|
const evolvedParentAddr = getIbGibAddr({ ibGib: evolvedParentKeystoneIbGib });
|
|
621
1024
|
console.log(`${lc} delegate registered. evolved parent address: ${evolvedParentAddr}`);
|
|
@@ -652,19 +1055,32 @@ export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
|
|
|
652
1055
|
secVerify.classList.remove('hidden');
|
|
653
1056
|
secVerify.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
654
1057
|
this.setStatus("✉️ Verification code sent to your email. Enter 6-digit code below:", "info");
|
|
1058
|
+
this.startResendCooldownTimer({ btnId: 'btn-resend-code', durationSecs: 30 });
|
|
655
1059
|
const onResendCode = async () => {
|
|
656
1060
|
btnResendCode.disabled = true;
|
|
657
1061
|
btnResendCode.textContent = "Sending...";
|
|
658
1062
|
this.setStatus("Resending verification code to your email...", "info");
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
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
|
+
}
|
|
662
1076
|
}
|
|
663
|
-
|
|
664
|
-
|
|
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";
|
|
665
1083
|
}
|
|
666
|
-
btnResendCode.disabled = false;
|
|
667
|
-
btnResendCode.textContent = "Resend Code";
|
|
668
1084
|
};
|
|
669
1085
|
btnResendCode.addEventListener('click', onResendCode);
|
|
670
1086
|
await new Promise((resolve, reject) => {
|
|
@@ -864,6 +1280,10 @@ export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
|
|
|
864
1280
|
}
|
|
865
1281
|
this.setStatus(successMsg, "success");
|
|
866
1282
|
console.log(`${lc} ✓ ${successMsg}`);
|
|
1283
|
+
// 3.5 Sanitize secret input fields from DOM
|
|
1284
|
+
if (this.elements?.inputSecret) {
|
|
1285
|
+
this.elements.inputSecret.value = '';
|
|
1286
|
+
}
|
|
867
1287
|
// 4. Dispatch UI navigation message to close creator and open identity manager
|
|
868
1288
|
window.dispatchEvent(new CustomEvent(EVENT_IBGIB_UI_MESSAGE, {
|
|
869
1289
|
detail: { action: 'show-identity-manager', addr: primaryTjpAddr },
|