@ibgib/web-gib 0.0.57 → 0.0.59
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AUTO-GENERATED-version.d.mts +1 -1
- package/dist/AUTO-GENERATED-version.mjs +1 -1
- package/dist/api/ibgib-api-bridge.d.mts +63 -1
- package/dist/api/ibgib-api-bridge.d.mts.map +1 -1
- package/dist/api/ibgib-api-bridge.mjs +135 -0
- package/dist/api/ibgib-api-bridge.mjs.map +1 -1
- package/dist/identity/custodian-delegate-helper.d.mts +22 -0
- package/dist/identity/custodian-delegate-helper.d.mts.map +1 -0
- package/dist/identity/custodian-delegate-helper.mjs +50 -0
- package/dist/identity/custodian-delegate-helper.mjs.map +1 -0
- package/dist/identity/sso-popup-helper.d.mts +33 -0
- package/dist/identity/sso-popup-helper.d.mts.map +1 -0
- package/dist/identity/sso-popup-helper.mjs +124 -0
- package/dist/identity/sso-popup-helper.mjs.map +1 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +3 -0
- package/dist/index.mjs.map +1 -1
- package/dist/ui/component/identity/add-device/add-device.d.mts.map +1 -1
- package/dist/ui/component/identity/add-device/add-device.mjs +5 -1
- package/dist/ui/component/identity/add-device/add-device.mjs.map +1 -1
- package/dist/ui/component/identity/identity-header/identity-header.d.mts.map +1 -1
- package/dist/ui/component/identity/identity-header/identity-header.mjs +2 -1
- package/dist/ui/component/identity/identity-header/identity-header.mjs.map +1 -1
- package/dist/ui/component/identity/keystone-creator/keystone-creator.css +109 -23
- package/dist/ui/component/identity/keystone-creator/keystone-creator.d.mts +22 -0
- package/dist/ui/component/identity/keystone-creator/keystone-creator.d.mts.map +1 -1
- package/dist/ui/component/identity/keystone-creator/keystone-creator.html +122 -60
- package/dist/ui/component/identity/keystone-creator/keystone-creator.mjs +445 -73
- package/dist/ui/component/identity/keystone-creator/keystone-creator.mjs.map +1 -1
- package/dist/ui/component/identity/keystone-details/keystone-details.d.mts.map +1 -1
- package/dist/ui/component/identity/keystone-details/keystone-details.mjs +10 -53
- package/dist/ui/component/identity/keystone-details/keystone-details.mjs.map +1 -1
- package/package.json +4 -4
- package/src/AUTO-GENERATED-version.mts +1 -1
- package/src/api/ibgib-api-bridge.mts +195 -1
- package/src/identity/custodian-delegate-helper.mts +61 -0
- package/src/identity/sso-popup-helper.mts +155 -0
- package/src/index.mts +3 -0
- package/src/ui/component/identity/add-device/add-device.mts +5 -1
- package/src/ui/component/identity/identity-header/identity-header.mts +2 -1
- package/src/ui/component/identity/keystone-creator/keystone-creator.css +109 -23
- package/src/ui/component/identity/keystone-creator/keystone-creator.html +122 -60
- package/src/ui/component/identity/keystone-creator/keystone-creator.mts +507 -77
- package/src/ui/component/identity/keystone-details/keystone-details.mts +10 -62
- package/tools/auto-generated-agent-skills/skills/ibgib-implementation-plan/SKILL.md +3 -0
|
@@ -14,8 +14,10 @@ import { getGlobalMetaspace_waitIfNeeded, } from "../../../../helpers.mjs";
|
|
|
14
14
|
import { IbGibDynamicComponentMetaBase, IbGibFormInstanceBase, } from "../../ibgib-dynamic-component-bases.mjs";
|
|
15
15
|
import { getComponentCtorArg } from "../../../../app-bootstrap/init-orchestration.mjs";
|
|
16
16
|
import { getApiBridge } from "../../../../api/ibgib-api-bridge.mjs";
|
|
17
|
+
import { deriveLocalCustodianDelegateSecret } from "../../../../identity/custodian-delegate-helper.mjs";
|
|
18
|
+
import { executeSsoOAuthPopup } from "../../../../identity/sso-popup-helper.mjs";
|
|
17
19
|
import { EVENT_IBGIB_IDENTITY_REQUEST_CHANGE, EVENT_IBGIB_UI_MESSAGE } from "../../../ui-constants.mjs";
|
|
18
|
-
import { shadowRoot_getElementById } from "../../../../helpers.web.mjs";
|
|
20
|
+
import { alertUser, shadowRoot_getElementById } from "../../../../helpers.web.mjs";
|
|
19
21
|
export const KEYSTONE_CREATOR_COMPONENT_NAME = 'ibgib-keystone-creator';
|
|
20
22
|
/**
|
|
21
23
|
* Metadata for the Keystone Creator component.
|
|
@@ -113,6 +115,50 @@ export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
|
|
|
113
115
|
// cleanup if needed
|
|
114
116
|
}
|
|
115
117
|
initHandlers() {
|
|
118
|
+
// Mode Tab Switching (Quick vs Password-based)
|
|
119
|
+
const tabQuick = this.shadowRoot?.getElementById('tab-quick');
|
|
120
|
+
const tabPassword = this.shadowRoot?.getElementById('tab-password');
|
|
121
|
+
const panelQuick = this.shadowRoot?.getElementById('panel-quick');
|
|
122
|
+
const panelPassword = this.shadowRoot?.getElementById('panel-password');
|
|
123
|
+
const subtitleQuick = this.shadowRoot?.getElementById('subtitle-quick');
|
|
124
|
+
const subtitlePassword = this.shadowRoot?.getElementById('subtitle-password');
|
|
125
|
+
tabQuick?.addEventListener('click', () => {
|
|
126
|
+
tabQuick.classList.add('active');
|
|
127
|
+
tabPassword?.classList.remove('active');
|
|
128
|
+
panelQuick?.classList.remove('hidden');
|
|
129
|
+
panelPassword?.classList.add('hidden');
|
|
130
|
+
subtitleQuick?.classList.remove('hidden');
|
|
131
|
+
subtitlePassword?.classList.add('hidden');
|
|
132
|
+
});
|
|
133
|
+
tabPassword?.addEventListener('click', () => {
|
|
134
|
+
tabPassword.classList.add('active');
|
|
135
|
+
tabQuick?.classList.remove('active');
|
|
136
|
+
panelPassword?.classList.remove('hidden');
|
|
137
|
+
panelQuick?.classList.add('hidden');
|
|
138
|
+
subtitlePassword?.classList.remove('hidden');
|
|
139
|
+
subtitleQuick?.classList.add('hidden');
|
|
140
|
+
});
|
|
141
|
+
const btnGoogleSso = this.shadowRoot?.getElementById('btn-google-sso');
|
|
142
|
+
btnGoogleSso?.addEventListener('click', () => this.handleSsoClick('google'));
|
|
143
|
+
const btnGithubSso = this.shadowRoot?.getElementById('btn-github-sso');
|
|
144
|
+
btnGithubSso?.addEventListener('click', () => this.handleSsoClick('github'));
|
|
145
|
+
// Email Sign-In Info Button
|
|
146
|
+
const btnEmailInfo = this.shadowRoot?.getElementById('btn-email-info');
|
|
147
|
+
btnEmailInfo?.addEventListener('click', (e) => {
|
|
148
|
+
e.preventDefault();
|
|
149
|
+
alertUser({
|
|
150
|
+
title: 'Custom Email Sign-In',
|
|
151
|
+
msg: 'When you enter your email address, we dispatch a 6-digit one-time verification code to your inbox.\n\nEntering the code creates your cryptographic identity delegate on your device and signs you in seamlessly without needing a password!'
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
// Quick Email Verification Code Handlers
|
|
155
|
+
const btnQuickSendCode = this.shadowRoot?.getElementById('btn-quick-send-code');
|
|
156
|
+
const inputQuickEmail = this.shadowRoot?.getElementById('input-quick-email');
|
|
157
|
+
const secQuickOtp = this.shadowRoot?.getElementById('section-quick-otp');
|
|
158
|
+
const inputQuickOtp = this.shadowRoot?.getElementById('input-quick-otp');
|
|
159
|
+
const btnQuickVerifyCode = this.shadowRoot?.getElementById('btn-quick-verify-code');
|
|
160
|
+
btnQuickSendCode?.addEventListener('click', () => this.handleQuickSendCode());
|
|
161
|
+
btnQuickVerifyCode?.addEventListener('click', () => this.handleQuickVerifyCode());
|
|
116
162
|
this.elements.btnGenerateEl.addEventListener('click', () => this.handleGenerate());
|
|
117
163
|
this.shadowRoot.getElementById('btn-switch-sign-in')?.addEventListener('click', (e) => {
|
|
118
164
|
e.preventDefault();
|
|
@@ -123,16 +169,16 @@ export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
|
|
|
123
169
|
}));
|
|
124
170
|
});
|
|
125
171
|
// Clear invalid styling when user inputs text
|
|
126
|
-
this.elements.inputUsername
|
|
172
|
+
this.elements.inputUsername?.addEventListener('input', () => {
|
|
127
173
|
this.elements.inputUsername.classList.remove('invalid');
|
|
128
174
|
});
|
|
129
|
-
this.elements.inputDescription
|
|
175
|
+
this.elements.inputDescription?.addEventListener('input', () => {
|
|
130
176
|
this.elements.inputDescription.classList.remove('invalid');
|
|
131
177
|
});
|
|
132
|
-
this.elements.inputEmail
|
|
178
|
+
this.elements.inputEmail?.addEventListener('input', () => {
|
|
133
179
|
this.elements.inputEmail.classList.remove('invalid');
|
|
134
180
|
});
|
|
135
|
-
this.elements.inputSecret
|
|
181
|
+
this.elements.inputSecret?.addEventListener('input', () => {
|
|
136
182
|
this.elements.inputSecret.classList.remove('invalid');
|
|
137
183
|
});
|
|
138
184
|
}
|
|
@@ -143,6 +189,266 @@ export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
|
|
|
143
189
|
btn.textContent = isLoading ? 'Generating...' : 'Generate Identity Keystone';
|
|
144
190
|
}
|
|
145
191
|
}
|
|
192
|
+
/**
|
|
193
|
+
* Triggered by Google / GitHub SSO buttons.
|
|
194
|
+
* Opens OAuth popup, exchanges authorization code, builds local delegate keystone,
|
|
195
|
+
* registers delegate on server, and finalizes onboarding.
|
|
196
|
+
*/
|
|
197
|
+
async handleSsoClick(providerId) {
|
|
198
|
+
const lc = `${this.lc}[${this.handleSsoClick.name}]`;
|
|
199
|
+
const providerName = providerId === 'google' ? 'Google' : providerId === 'github' ? 'GitHub' : providerId;
|
|
200
|
+
this.setStatus(`Opening ${providerName} SSO authorization window...`, 'info');
|
|
201
|
+
this.showBusy({
|
|
202
|
+
isBusy: true,
|
|
203
|
+
title: `Connecting to ${providerName}...`,
|
|
204
|
+
msg: `Opening ${providerName} SSO authorization window...\nPlease complete authorization in the popup window.`,
|
|
205
|
+
animationEmoji: '🌐'
|
|
206
|
+
});
|
|
207
|
+
try {
|
|
208
|
+
const resPopup = await executeSsoOAuthPopup({ providerId });
|
|
209
|
+
if (!resPopup.success || !resPopup.code) {
|
|
210
|
+
this.setStatus(resPopup.message || `${providerName} sign-in cancelled.`, 'error');
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
this.setStatus(`Exchanging ${providerName} authorization code...`, 'info');
|
|
214
|
+
this.showBusy({
|
|
215
|
+
isBusy: true,
|
|
216
|
+
title: 'Exchanging SSO Code...',
|
|
217
|
+
msg: `Validating ${providerName} authorization code with custodian server...`,
|
|
218
|
+
animationEmoji: '🔐'
|
|
219
|
+
});
|
|
220
|
+
const apiBridge = getApiBridge();
|
|
221
|
+
const resLogin = await apiBridge.postSsoLogin({
|
|
222
|
+
code: resPopup.code,
|
|
223
|
+
providerId,
|
|
224
|
+
redirectUri: resPopup.redirectUri
|
|
225
|
+
});
|
|
226
|
+
if (!resLogin.success || !resLogin.primaryTjpAddr || !resLogin.custodianReqs) {
|
|
227
|
+
this.setStatus(resLogin.message || `${providerName} login failed.`, 'error');
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
const { primaryTjpAddr, custodianReqs } = resLogin;
|
|
231
|
+
this.setStatus('Deriving local device delegate keys...', 'info');
|
|
232
|
+
this.showBusy({
|
|
233
|
+
isBusy: true,
|
|
234
|
+
title: 'Creating Sync Delegate...',
|
|
235
|
+
msg: 'Generating cryptographic sync delegate keys...',
|
|
236
|
+
animationEmoji: '🔑'
|
|
237
|
+
});
|
|
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
|
+
const metaspace = await getGlobalMetaspace_waitIfNeeded();
|
|
256
|
+
const space = await metaspace.getLocalUserSpace({ lock: false });
|
|
257
|
+
if (!space) {
|
|
258
|
+
this.setStatus('Could not access local user space.', 'error');
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
const keystoneService = new KeystoneService_V1();
|
|
262
|
+
const delegateKeystone = await keystoneService.genesis({
|
|
263
|
+
masterSecret: delegateSecret,
|
|
264
|
+
configs,
|
|
265
|
+
metaspace,
|
|
266
|
+
space,
|
|
267
|
+
frameDetails: delegateBuilder.getFrameDetails(),
|
|
268
|
+
isPrimary: false
|
|
269
|
+
});
|
|
270
|
+
this.setStatus('Registering device delegate with custodian server...', 'info');
|
|
271
|
+
this.showBusy({
|
|
272
|
+
isBusy: true,
|
|
273
|
+
title: 'Registering Delegate...',
|
|
274
|
+
msg: 'Registering local delegate with custodian server...',
|
|
275
|
+
animationEmoji: '📝'
|
|
276
|
+
});
|
|
277
|
+
const resRegister = await apiBridge.postCustodianRegisterDelegate({ delegateKeystone });
|
|
278
|
+
if (!resRegister.success) {
|
|
279
|
+
this.setStatus(resRegister.message || 'Custodian delegate registration failed.', 'error');
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
// Finalize onboarding: update keystones index, activate identity, and navigate to identity manager
|
|
283
|
+
await this.finalizeIdentityOnboarding({
|
|
284
|
+
primaryKeystone: resRegister.evolvedPrimaryKeystone || resLogin.primaryKeystone,
|
|
285
|
+
primaryGraph: resRegister.primaryGraph || resLogin.primaryGraph,
|
|
286
|
+
delegateKeystone,
|
|
287
|
+
primaryTjpAddr,
|
|
288
|
+
delegateSecret,
|
|
289
|
+
makeActive: true
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
catch (error) {
|
|
293
|
+
const emsg = extractErrorMsg(error);
|
|
294
|
+
console.error(`${lc} ${emsg}`, error);
|
|
295
|
+
this.setStatus(`SSO sign-in error: ${emsg}`, 'error');
|
|
296
|
+
}
|
|
297
|
+
finally {
|
|
298
|
+
this.showBusy({ isBusy: false });
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Triggered by the "Send 6-Digit Verification Code" button.
|
|
303
|
+
* Validates quick email input and dispatches a 6-digit OTP code to the user's inbox.
|
|
304
|
+
*/
|
|
305
|
+
async handleQuickSendCode() {
|
|
306
|
+
const lc = `${this.lc}[${this.handleQuickSendCode.name}]`;
|
|
307
|
+
const btnQuickSendCode = this.shadowRoot?.getElementById('btn-quick-send-code');
|
|
308
|
+
const inputQuickEmail = this.shadowRoot?.getElementById('input-quick-email');
|
|
309
|
+
const secQuickOtp = this.shadowRoot?.getElementById('section-quick-otp');
|
|
310
|
+
const email = (inputQuickEmail?.value || '').trim();
|
|
311
|
+
if (!email || !validateEmail(email)) {
|
|
312
|
+
this.setStatus("Please enter a valid email address.", "error");
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
if (btnQuickSendCode) {
|
|
316
|
+
btnQuickSendCode.disabled = true;
|
|
317
|
+
btnQuickSendCode.textContent = "Sending Verification Code...";
|
|
318
|
+
}
|
|
319
|
+
this.setStatus("Requesting verification code...", "info");
|
|
320
|
+
try {
|
|
321
|
+
const resCode = await getApiBridge().postVerificationCode({ email });
|
|
322
|
+
if (resCode.success) {
|
|
323
|
+
secQuickOtp?.classList.remove('hidden');
|
|
324
|
+
this.setStatus("✉️ 6-digit verification code sent to your email!", "success");
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
this.setStatus(resCode.message || "Failed to dispatch verification code.", "error");
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
catch (error) {
|
|
331
|
+
const emsg = extractErrorMsg(error);
|
|
332
|
+
console.error(`${lc} ${emsg}`);
|
|
333
|
+
this.setStatus(`Failed to dispatch verification code: ${emsg}`, "error");
|
|
334
|
+
}
|
|
335
|
+
finally {
|
|
336
|
+
if (btnQuickSendCode) {
|
|
337
|
+
btnQuickSendCode.disabled = false;
|
|
338
|
+
btnQuickSendCode.textContent = "Send 6-Digit Verification Code";
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Triggered by the "Verify Code" button.
|
|
344
|
+
* Submits 6-digit OTP code to custodian server, builds local sync delegate keystone,
|
|
345
|
+
* registers delegate on server, and finalizes onboarding.
|
|
346
|
+
*/
|
|
347
|
+
async handleQuickVerifyCode() {
|
|
348
|
+
const lc = `${this.lc}[${this.handleQuickVerifyCode.name}]`;
|
|
349
|
+
const inputQuickEmail = this.shadowRoot?.getElementById('input-quick-email');
|
|
350
|
+
const inputQuickOtp = this.shadowRoot?.getElementById('input-quick-otp');
|
|
351
|
+
const btnQuickVerifyCode = this.shadowRoot?.getElementById('btn-quick-verify-code');
|
|
352
|
+
const email = (inputQuickEmail?.value || '').trim();
|
|
353
|
+
const code = (inputQuickOtp?.value || '').trim();
|
|
354
|
+
if (!code || code.length !== 6 || !/^\d{6}$/.test(code)) {
|
|
355
|
+
this.setStatus("Please enter a valid 6-digit verification code.", "error");
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
if (btnQuickVerifyCode) {
|
|
359
|
+
btnQuickVerifyCode.disabled = true;
|
|
360
|
+
btnQuickVerifyCode.textContent = "Verifying...";
|
|
361
|
+
}
|
|
362
|
+
this.setStatus("Verifying code & completing onboarding...", "info");
|
|
363
|
+
this.showBusy({
|
|
364
|
+
isBusy: true,
|
|
365
|
+
title: 'Verifying Code...',
|
|
366
|
+
msg: 'Great! Juust a moment...\n(Validating code, promoting server domain space, other security stuff...)',
|
|
367
|
+
animationEmoji: '🔐'
|
|
368
|
+
});
|
|
369
|
+
try {
|
|
370
|
+
const resGenesis = await getApiBridge().postCustodianGenesis({ email, code });
|
|
371
|
+
if (!resGenesis.success || !resGenesis.primaryTjpAddr || !resGenesis.custodianReqs) {
|
|
372
|
+
this.setStatus(resGenesis.message || "Custodian verification failed.", "error");
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
const primaryTjpAddr = resGenesis.primaryTjpAddr;
|
|
376
|
+
const custodianReqs = resGenesis.custodianReqs;
|
|
377
|
+
// Construct local delegate keystone
|
|
378
|
+
this.setStatus("Constructing local sync delegate...", "info");
|
|
379
|
+
this.showBusy({
|
|
380
|
+
isBusy: true,
|
|
381
|
+
title: 'Creating Sync Delegate...',
|
|
382
|
+
msg: 'Generating cryptographic sync delegate keys...',
|
|
383
|
+
animationEmoji: '🔑'
|
|
384
|
+
});
|
|
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
|
+
const metaspace = await getGlobalMetaspace_waitIfNeeded();
|
|
402
|
+
const space = await metaspace.getLocalUserSpace({ lock: false });
|
|
403
|
+
if (!space) {
|
|
404
|
+
this.setStatus("Failed to access local user space.", "error");
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
const keystoneService = new KeystoneService_V1();
|
|
408
|
+
const delegateKeystone = await keystoneService.genesis({
|
|
409
|
+
masterSecret: delegateSecret,
|
|
410
|
+
configs,
|
|
411
|
+
metaspace,
|
|
412
|
+
space,
|
|
413
|
+
frameDetails: delegateBuilder.getFrameDetails(),
|
|
414
|
+
isPrimary: false
|
|
415
|
+
});
|
|
416
|
+
// Register delegate with server
|
|
417
|
+
this.setStatus("Registering device delegate with custodian server...", "info");
|
|
418
|
+
this.showBusy({
|
|
419
|
+
isBusy: true,
|
|
420
|
+
title: 'Registering Delegate...',
|
|
421
|
+
msg: 'Registering local delegate with custodian server...',
|
|
422
|
+
animationEmoji: '📝'
|
|
423
|
+
});
|
|
424
|
+
const resRegister = await getApiBridge().postCustodianRegisterDelegate({ delegateKeystone });
|
|
425
|
+
if (!resRegister.success) {
|
|
426
|
+
this.setStatus(resRegister.message || "Failed to register delegate with custodian server.", "error");
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
// Finalize onboarding: update keystones index, activate identity, and navigate to identity manager
|
|
430
|
+
await this.finalizeIdentityOnboarding({
|
|
431
|
+
primaryKeystone: resRegister.evolvedPrimaryKeystone || resGenesis.primaryKeystone,
|
|
432
|
+
primaryGraph: resRegister.primaryGraph || resGenesis.primaryGraph,
|
|
433
|
+
delegateKeystone,
|
|
434
|
+
primaryTjpAddr,
|
|
435
|
+
delegateSecret,
|
|
436
|
+
makeActive: true
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
catch (error) {
|
|
440
|
+
const emsg = extractErrorMsg(error);
|
|
441
|
+
console.error(`${lc} ${emsg}`, error);
|
|
442
|
+
this.setStatus(`Error: ${emsg}`, "error");
|
|
443
|
+
}
|
|
444
|
+
finally {
|
|
445
|
+
this.showBusy({ isBusy: false });
|
|
446
|
+
if (btnQuickVerifyCode) {
|
|
447
|
+
btnQuickVerifyCode.disabled = false;
|
|
448
|
+
btnQuickVerifyCode.textContent = "Verify Code";
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
146
452
|
/**
|
|
147
453
|
* Triggered by the "Generate" button.
|
|
148
454
|
* Orchestrates: Secret -> Local Genesis -> Graph Collection -> Server Sync.
|
|
@@ -218,12 +524,13 @@ export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
|
|
|
218
524
|
msg: 'Preparing configuration and policy options...',
|
|
219
525
|
animationEmoji: '⚙️'
|
|
220
526
|
});
|
|
221
|
-
// 1. Prepare Primary
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
527
|
+
// 1. Prepare Primary Keystone Configs
|
|
528
|
+
const sovereignPrimaryProfileName = process.env.KEYSTONE_PROFILE_SOVEREIGN_PRIMARY;
|
|
529
|
+
if (!sovereignPrimaryProfileName || !sovereignPrimaryProfileName.trim()) {
|
|
530
|
+
throw new Error('KEYSTONE_PROFILE_SOVEREIGN_PRIMARY environment variable is missing. Expected valid profile name like "primary-profile.dev" or "primary-profile.prod". (E: 3c4d5e6f7890123456789abcdef3)');
|
|
531
|
+
}
|
|
532
|
+
console.log(`${lc} Using sovereign primary keystone policy profile '${sovereignPrimaryProfileName}' (I: 90123456789abcdef0123456789abcde)`);
|
|
533
|
+
const builder = KeystoneProfileBuilder.buildKeystone(sovereignPrimaryProfileName.trim())
|
|
227
534
|
.withUsername(username)
|
|
228
535
|
.withDescription(description);
|
|
229
536
|
if (email) {
|
|
@@ -234,11 +541,12 @@ export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
|
|
|
234
541
|
role: 'domain-identity'
|
|
235
542
|
});
|
|
236
543
|
const configs = await builder.compileConfigs();
|
|
237
|
-
// 2.
|
|
544
|
+
// 2. Prepare Metaspace/Space
|
|
238
545
|
const metaspace = await getGlobalMetaspace_waitIfNeeded();
|
|
239
|
-
const space = await metaspace.getLocalUserSpace({});
|
|
546
|
+
const space = await metaspace.getLocalUserSpace({ lock: false });
|
|
240
547
|
if (!space) {
|
|
241
|
-
|
|
548
|
+
this.setStatus("Failed to access local user space.", "error");
|
|
549
|
+
return;
|
|
242
550
|
}
|
|
243
551
|
// 3. Perform Genesis (Local)
|
|
244
552
|
this.showBusy({
|
|
@@ -270,7 +578,11 @@ export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
|
|
|
270
578
|
animationEmoji: '🔑'
|
|
271
579
|
});
|
|
272
580
|
const delegateSecret = await deriveDelegateSecret({ masterSecret });
|
|
273
|
-
const
|
|
581
|
+
const sovereignSyncProfileName = process.env.KEYSTONE_PROFILE_SOVEREIGN_SYNC;
|
|
582
|
+
if (!sovereignSyncProfileName || !sovereignSyncProfileName.trim()) {
|
|
583
|
+
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)');
|
|
584
|
+
}
|
|
585
|
+
const delegateBuilder = KeystoneProfileBuilder.buildKeystone(sovereignSyncProfileName.trim())
|
|
274
586
|
.withUsername(`${username}-sync`)
|
|
275
587
|
.withDescription(`Sync Delegate for ${username}`)
|
|
276
588
|
.withDetails({
|
|
@@ -313,7 +625,7 @@ export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
|
|
|
313
625
|
this.showBusy({
|
|
314
626
|
isBusy: true,
|
|
315
627
|
title: 'Syncing to Server...',
|
|
316
|
-
msg: '
|
|
628
|
+
msg: 'Staging pending genesis keystone on server...',
|
|
317
629
|
animationEmoji: '🔄'
|
|
318
630
|
});
|
|
319
631
|
const resSync = await getApiBridge().postGenesisKeystone(keystoneIbGib);
|
|
@@ -425,64 +737,15 @@ export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
|
|
|
425
737
|
return;
|
|
426
738
|
}
|
|
427
739
|
}
|
|
428
|
-
// 5.
|
|
429
|
-
this.
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
await updateSpecialIndex({
|
|
437
|
-
type: "keystones",
|
|
438
|
-
rel8nInfos: [
|
|
439
|
-
{
|
|
440
|
-
rel8nName: "keystone",
|
|
441
|
-
ibGibs: [
|
|
442
|
-
toDto({ ibGib: evolvedParentKeystoneIbGib }),
|
|
443
|
-
toDto({ ibGib: delegateKeystoneIbGib })
|
|
444
|
-
],
|
|
445
|
-
},
|
|
446
|
-
],
|
|
447
|
-
metaspace,
|
|
448
|
-
space,
|
|
449
|
-
});
|
|
450
|
-
// Expose for dev tools
|
|
451
|
-
window.dev_domainI = evolvedParentKeystoneIbGib;
|
|
452
|
-
window.dev_domainIMasterSecret = masterSecret;
|
|
453
|
-
window.dev_syncDelegate = delegateKeystoneIbGib;
|
|
454
|
-
window.dev_syncDelegateSecret = delegateSecret;
|
|
455
|
-
// 6. Request to make the new identity active if the checkbox is checked
|
|
456
|
-
const makeActive = this.elements.checkboxMakeActive?.checked;
|
|
457
|
-
if (makeActive) {
|
|
458
|
-
window.dispatchEvent(new CustomEvent(EVENT_IBGIB_IDENTITY_REQUEST_CHANGE, {
|
|
459
|
-
detail: { activeIdentityAddr: evolvedParentAddr },
|
|
460
|
-
bubbles: true,
|
|
461
|
-
composed: true
|
|
462
|
-
}));
|
|
463
|
-
}
|
|
464
|
-
// 7. Success
|
|
465
|
-
const successMsg = "Identity successfully created and activated! Just a sec...";
|
|
466
|
-
this.showBusy({
|
|
467
|
-
isBusy: true,
|
|
468
|
-
title: 'Success!',
|
|
469
|
-
msg: successMsg,
|
|
470
|
-
animationEmoji: '✅'
|
|
740
|
+
// 5. Finalize onboarding: update keystones index, activate identity, and navigate to identity manager
|
|
741
|
+
await this.finalizeIdentityOnboarding({
|
|
742
|
+
primaryKeystone: evolvedParentKeystoneIbGib,
|
|
743
|
+
delegateKeystone: delegateKeystoneIbGib,
|
|
744
|
+
primaryTjpAddr: evolvedParentAddr,
|
|
745
|
+
masterSecret,
|
|
746
|
+
delegateSecret,
|
|
747
|
+
makeActive: this.elements.checkboxMakeActive?.checked ?? true
|
|
471
748
|
});
|
|
472
|
-
await delay(humanEyeDelay); // for human eyes, not needed for functionality, because we'll redirect
|
|
473
|
-
await delay(humanEyeDelay); // for human eyes, not needed for functionality, because we'll redirect
|
|
474
|
-
await delay(humanEyeDelay); // for human eyes, not needed for functionality, because we'll redirect
|
|
475
|
-
await delay(humanEyeDelay); // for human eyes, not needed for functionality, because we'll redirect
|
|
476
|
-
this.elements.keystoneAddrEl.textContent = evolvedParentAddr;
|
|
477
|
-
this.elements.statusArea.classList.remove('hidden');
|
|
478
|
-
this.setStatus(successMsg, "success");
|
|
479
|
-
console.log(`${lc} ✓ ${successMsg} (I: 3396d8261978d7027895b71e27e9ab26)`);
|
|
480
|
-
// Dispatch UI message to close keystone-creator and show identity-manager
|
|
481
|
-
window.dispatchEvent(new CustomEvent(EVENT_IBGIB_UI_MESSAGE, {
|
|
482
|
-
detail: { action: 'show-identity-manager', addr: evolvedParentAddr },
|
|
483
|
-
bubbles: true,
|
|
484
|
-
composed: true
|
|
485
|
-
}));
|
|
486
749
|
}
|
|
487
750
|
catch (error) {
|
|
488
751
|
const emsg = extractErrorMsg(error);
|
|
@@ -497,6 +760,115 @@ export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
|
|
|
497
760
|
this.setButtonLoading(false);
|
|
498
761
|
}
|
|
499
762
|
}
|
|
763
|
+
/**
|
|
764
|
+
* DRY helper to register keystones in the local keystones special index,
|
|
765
|
+
* set the active identity, and navigate to the Identity Manager component.
|
|
766
|
+
*/
|
|
767
|
+
async finalizeIdentityOnboarding({ primaryKeystone, primaryGraph, delegateKeystone, primaryTjpAddr, masterSecret, delegateSecret, makeActive = true, }) {
|
|
768
|
+
const lc = `${this.lc}[${this.finalizeIdentityOnboarding.name}]`;
|
|
769
|
+
const humanEyeDelay = 500;
|
|
770
|
+
const metaspace = await getGlobalMetaspace_waitIfNeeded();
|
|
771
|
+
if (!metaspace) {
|
|
772
|
+
throw new Error(`(UNEXPECTED) metaspace falsy? Expected active global metaspace instance. (E: 2e37a8b6fe987449c5d8693a7d4a2827)`);
|
|
773
|
+
}
|
|
774
|
+
const space = await metaspace.getLocalUserSpace({});
|
|
775
|
+
if (!space) {
|
|
776
|
+
throw new Error(`(UNEXPECTED) space falsy? Expected valid default local user space from metaspace. (E: 1e37a8b6fe987449c5d8693a7d4a2826)`);
|
|
777
|
+
}
|
|
778
|
+
// 0. Persist all frames of primary identity graph into local browser space
|
|
779
|
+
if (primaryGraph) {
|
|
780
|
+
const graphIbGibs = Object.values(primaryGraph);
|
|
781
|
+
for (const frame of graphIbGibs) {
|
|
782
|
+
await metaspace.put({ ibGibs: [frame], space });
|
|
783
|
+
await metaspace.registerNewIbGib({ ibGib: frame, space });
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
else if (primaryKeystone) {
|
|
787
|
+
await metaspace.put({ ibGibs: [primaryKeystone], space });
|
|
788
|
+
await metaspace.registerNewIbGib({ ibGib: primaryKeystone, space });
|
|
789
|
+
}
|
|
790
|
+
// 0.5 Verify primary keystone exists in local browser space via getLatestKeystone
|
|
791
|
+
const keystoneService = new KeystoneService_V1();
|
|
792
|
+
try {
|
|
793
|
+
await keystoneService.getLatestKeystone({
|
|
794
|
+
addr: primaryTjpAddr,
|
|
795
|
+
metaspace,
|
|
796
|
+
space
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
catch (error) {
|
|
800
|
+
const emsg = `Primary identity keystone ${primaryTjpAddr} not found in local browser space! Error: ${extractErrorMsg(error)}`;
|
|
801
|
+
console.error(`${lc} ${emsg} (E: a123456789abcdef0123456789c0001)`);
|
|
802
|
+
throw new Error(emsg);
|
|
803
|
+
}
|
|
804
|
+
// 1. Register with the local keystones special index
|
|
805
|
+
this.showBusy({
|
|
806
|
+
isBusy: true,
|
|
807
|
+
title: 'Registering Identity...',
|
|
808
|
+
msg: 'Updating local identity indexes and scope settings...',
|
|
809
|
+
animationEmoji: '💾'
|
|
810
|
+
});
|
|
811
|
+
const ibGibsToRegister = [toDto({ ibGib: delegateKeystone })];
|
|
812
|
+
if (!primaryKeystone && !primaryGraph) {
|
|
813
|
+
throw new Error(`(UNEXPECTED) primaryKeystone and primaryGraph both falsy? We should have gotten this back from the server. (E: e34cd882ac66633c75c53318a3b28826)`);
|
|
814
|
+
}
|
|
815
|
+
if (!delegateKeystone) {
|
|
816
|
+
throw new Error(`(UNEXPECTED) delegateKeystone falsy? We should have made this locally and even registered it with the server. (E: 56b1c396269e15f008b7e298c71bc826)`);
|
|
817
|
+
}
|
|
818
|
+
if (primaryKeystone) {
|
|
819
|
+
ibGibsToRegister.unshift(toDto({ ibGib: primaryKeystone }));
|
|
820
|
+
}
|
|
821
|
+
await updateSpecialIndex({
|
|
822
|
+
type: "keystones",
|
|
823
|
+
rel8nInfos: [
|
|
824
|
+
{
|
|
825
|
+
rel8nName: "keystone",
|
|
826
|
+
ibGibs: ibGibsToRegister,
|
|
827
|
+
},
|
|
828
|
+
],
|
|
829
|
+
metaspace,
|
|
830
|
+
space,
|
|
831
|
+
});
|
|
832
|
+
// Expose for dev tools debugging if available
|
|
833
|
+
if (masterSecret) {
|
|
834
|
+
window.dev_domainI = primaryKeystone;
|
|
835
|
+
window.dev_domainIMasterSecret = masterSecret;
|
|
836
|
+
window.dev_syncDelegate = delegateKeystone;
|
|
837
|
+
window.dev_syncDelegateSecret = delegateSecret;
|
|
838
|
+
}
|
|
839
|
+
// 2. Request to make the new identity active
|
|
840
|
+
if (makeActive) {
|
|
841
|
+
window.dispatchEvent(new CustomEvent(EVENT_IBGIB_IDENTITY_REQUEST_CHANGE, {
|
|
842
|
+
detail: { activeIdentityAddr: primaryTjpAddr },
|
|
843
|
+
bubbles: true,
|
|
844
|
+
composed: true
|
|
845
|
+
}));
|
|
846
|
+
}
|
|
847
|
+
// 3. Display success status
|
|
848
|
+
const successMsg = "Identity successfully created and activated!";
|
|
849
|
+
this.showBusy({
|
|
850
|
+
isBusy: true,
|
|
851
|
+
title: 'Success!',
|
|
852
|
+
msg: successMsg,
|
|
853
|
+
animationEmoji: '✅'
|
|
854
|
+
});
|
|
855
|
+
await delay(humanEyeDelay);
|
|
856
|
+
if (this.elements?.keystoneAddrEl) {
|
|
857
|
+
this.elements.keystoneAddrEl.textContent = primaryTjpAddr;
|
|
858
|
+
}
|
|
859
|
+
if (this.elements?.statusArea) {
|
|
860
|
+
this.elements.statusArea.classList.remove('hidden');
|
|
861
|
+
}
|
|
862
|
+
this.setStatus(successMsg, "success");
|
|
863
|
+
console.log(`${lc} ✓ ${successMsg}`);
|
|
864
|
+
// 4. Dispatch UI navigation message to close creator and open identity manager
|
|
865
|
+
window.dispatchEvent(new CustomEvent(EVENT_IBGIB_UI_MESSAGE, {
|
|
866
|
+
detail: { action: 'show-identity-manager', addr: primaryTjpAddr },
|
|
867
|
+
bubbles: true,
|
|
868
|
+
composed: true
|
|
869
|
+
}));
|
|
870
|
+
this.showBusy({ isBusy: false });
|
|
871
|
+
}
|
|
500
872
|
setFormDisabled(disabled) {
|
|
501
873
|
const root = this.shadowRoot;
|
|
502
874
|
const inputIds = [
|