@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-details.css";
|
|
2
2
|
import thisHtml from "./keystone-details.html";
|
|
3
3
|
import commonComponentStylesCss from "../../common-component-styles.css";
|
|
4
|
-
import { extractErrorMsg, pretty } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
|
|
4
|
+
import { extractErrorMsg, pretty, getSaferSubstring, HashAlgorithm } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
|
|
5
5
|
import { getIbAndGib, getIbGibAddr } from "@ibgib/ts-gib/dist/helper.mjs";
|
|
6
6
|
import { getGibInfo } from "@ibgib/ts-gib/dist/V1/transforms/transform-helper.mjs";
|
|
7
7
|
import { KeystoneService_V1 } from "@ibgib/core-gib/dist/keystone/keystone-service-v1.mjs";
|
|
@@ -10,15 +10,19 @@ import { EVENT_IBGIB_IDENTITY_REQUEST_CHANGE, EVENT_IBGIB_IDENTITY_CHANGED } fro
|
|
|
10
10
|
import { getApiBridge } from "../../../../api/ibgib-api-bridge.mjs";
|
|
11
11
|
import { getKeystoneSyncService } from "../../../../identity/keystone-sync-service.mjs";
|
|
12
12
|
import { updateSpecialIndex } from "@ibgib/core-gib/dist/timeline/timeline-api.mjs";
|
|
13
|
-
import { getTjpAddr, toDto } from "@ibgib/core-gib/dist/common/other/ibgib-helper.mjs";
|
|
13
|
+
import { getTjpAddr, toDto, getTimelinesGroupedByTjp } from "@ibgib/core-gib/dist/common/other/ibgib-helper.mjs";
|
|
14
14
|
import { parseSpaceIb } from "@ibgib/core-gib/dist/witness/space/space-helper.mjs";
|
|
15
15
|
import { executeSsoOAuthPopup } from "../../../../identity/sso-popup-helper.mjs";
|
|
16
16
|
import { getComponentCtorArg } from "../../../../app-bootstrap/init-orchestration.mjs";
|
|
17
17
|
import { promptForSecret } from "../../../../helpers.web.mjs";
|
|
18
|
-
import { isSsoProviderLinked, getKeystoneCapabilities, isDelegateExpired } from "@ibgib/core-gib/dist/keystone/delegate/delegate-helpers.mjs";
|
|
18
|
+
import { isSsoProviderLinked, getKeystoneCapabilities, isDelegateExpired, isCustodialDelegate } from "@ibgib/core-gib/dist/keystone/delegate/delegate-helpers.mjs";
|
|
19
19
|
import { generatePoolChallenges } from "@ibgib/core-gib/dist/keystone/keystone-helpers.mjs";
|
|
20
|
-
import { POOL_ID_MANAGE } from "@ibgib/core-gib/dist/keystone/keystone-constants.mjs";
|
|
20
|
+
import { POOL_ID_MANAGE, KEYSTONE_VERB_SYNC, KEYSTONE_VERB_CONNECT, KEYSTONE_VERB_MANAGE } from "@ibgib/core-gib/dist/keystone/keystone-constants.mjs";
|
|
21
|
+
import { generateDefaultDeviceTag } from "../../../../util/device-helper.mjs";
|
|
21
22
|
import { getGlobalMetaspace_waitIfNeeded } from "../../../../helpers.mjs";
|
|
23
|
+
import { createSovereignDeviceSyncDelegate, createOAuth2DeviceSyncDelegate } from "../../../../identity/device-delegate-helper.mjs";
|
|
24
|
+
import { isPasskeyPrfSupported, registerPasskeyCredential, evaluatePasskeyPrf, bufferToHex } from "../../../../identity/passkey-helper.mjs";
|
|
25
|
+
import { KeystoneChallengeType, KeystoneReplenishStrategy } from "@ibgib/core-gib/dist/keystone/keystone-types.mjs";
|
|
22
26
|
const logalot = false;
|
|
23
27
|
export const KEYSTONE_DETAILS_COMPONENT_NAME = 'ibgib-keystone-details';
|
|
24
28
|
export class KeystoneDetailsComponentMeta extends IbGibDynamicComponentMetaBase {
|
|
@@ -54,6 +58,7 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
|
|
|
54
58
|
_onIdentityChanged = null;
|
|
55
59
|
keystoneCache = new Map();
|
|
56
60
|
aggregatedDetails;
|
|
61
|
+
resendCooldownInterval = null;
|
|
57
62
|
constructor() {
|
|
58
63
|
super();
|
|
59
64
|
}
|
|
@@ -77,6 +82,39 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
|
|
|
77
82
|
}
|
|
78
83
|
}
|
|
79
84
|
}
|
|
85
|
+
async disconnected() {
|
|
86
|
+
if (this._onIdentityChanged) {
|
|
87
|
+
window.removeEventListener(EVENT_IBGIB_IDENTITY_CHANGED, this._onIdentityChanged);
|
|
88
|
+
}
|
|
89
|
+
if (this.resendCooldownInterval) {
|
|
90
|
+
clearInterval(this.resendCooldownInterval);
|
|
91
|
+
this.resendCooldownInterval = null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
startResendCooldownTimer(durationSecs = 30) {
|
|
95
|
+
const btn = this.elements?.btnResendAddPasswordOtpEl;
|
|
96
|
+
if (!btn)
|
|
97
|
+
return;
|
|
98
|
+
if (this.resendCooldownInterval) {
|
|
99
|
+
clearInterval(this.resendCooldownInterval);
|
|
100
|
+
this.resendCooldownInterval = null;
|
|
101
|
+
}
|
|
102
|
+
let remaining = durationSecs;
|
|
103
|
+
btn.disabled = true;
|
|
104
|
+
btn.textContent = `Resend Code (${remaining}s)`;
|
|
105
|
+
this.resendCooldownInterval = setInterval(() => {
|
|
106
|
+
remaining -= 1;
|
|
107
|
+
if (remaining <= 0) {
|
|
108
|
+
clearInterval(this.resendCooldownInterval);
|
|
109
|
+
this.resendCooldownInterval = null;
|
|
110
|
+
btn.disabled = false;
|
|
111
|
+
btn.textContent = "Resend Code";
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
btn.textContent = `Resend Code (${remaining}s)`;
|
|
115
|
+
}
|
|
116
|
+
}, 1000);
|
|
117
|
+
}
|
|
80
118
|
async getKeystoneDetails(addr) {
|
|
81
119
|
const cached = this.keystoneCache.get(addr);
|
|
82
120
|
if (cached)
|
|
@@ -142,6 +180,10 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
|
|
|
142
180
|
this.elements.rawKeystoneJsonEl = shadow.getElementById('raw-keystone-json');
|
|
143
181
|
this.elements.delegatesCardEl = shadow.getElementById('delegates-card');
|
|
144
182
|
this.elements.delegatesListEl = shadow.getElementById('delegates-list');
|
|
183
|
+
this.elements.passkeysCardEl = shadow.getElementById('passkeys-card');
|
|
184
|
+
this.elements.passkeysListEl = shadow.getElementById('passkeys-list');
|
|
185
|
+
this.elements.btnAddPasskeyEl = shadow.getElementById('btn-add-passkey');
|
|
186
|
+
this.elements.passkeyStatusMsgEl = shadow.getElementById('passkey-status-msg');
|
|
145
187
|
this.elements.containerEl = shadow.getElementById('container');
|
|
146
188
|
this.elements.historicalWarningBannerEl = shadow.getElementById('historical-warning-banner');
|
|
147
189
|
this.elements.ssoLinkCardEl = shadow.getElementById('sso-link-card');
|
|
@@ -154,6 +196,10 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
|
|
|
154
196
|
this.elements.btnChangePasswordEl = shadow.getElementById('btn-change-password');
|
|
155
197
|
this.elements.changePasswordStatusEl = shadow.getElementById('change-password-status');
|
|
156
198
|
this.elements.changePasswordCardEl = shadow.getElementById('change-password-card');
|
|
199
|
+
this.elements.sectionAddPasswordOtpEl = shadow.getElementById('section-add-password-otp');
|
|
200
|
+
this.elements.inputAddPasswordOtpEl = shadow.getElementById('input-add-password-otp');
|
|
201
|
+
this.elements.btnResendAddPasswordOtpEl = shadow.getElementById('btn-resend-add-password-otp');
|
|
202
|
+
this.elements.descAddPasswordOtpEl = shadow.getElementById('desc-add-password-otp');
|
|
157
203
|
// ElementBase requires contentEl
|
|
158
204
|
this.elements.contentEl = shadow.getElementById('identity-details-view');
|
|
159
205
|
this.initHandlers();
|
|
@@ -178,11 +224,6 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
|
|
|
178
224
|
}
|
|
179
225
|
}
|
|
180
226
|
}
|
|
181
|
-
async disconnected() {
|
|
182
|
-
if (this._onIdentityChanged) {
|
|
183
|
-
window.removeEventListener(EVENT_IBGIB_IDENTITY_CHANGED, this._onIdentityChanged);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
227
|
initHandlers() {
|
|
187
228
|
// Setup raw JSON accordion toggle
|
|
188
229
|
this.elements.rawAccordionHeaderEl.addEventListener('click', () => this.toggleAccordion());
|
|
@@ -191,9 +232,11 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
|
|
|
191
232
|
this.elements.btnSetActiveEl.addEventListener('click', () => this.handleSetActiveIdentity());
|
|
192
233
|
this.elements.btnCheckUpdatesEl.addEventListener('click', () => this.handleCheckForUpdates());
|
|
193
234
|
this.elements.btnUpdateIdentityEl.addEventListener('click', () => this.handleUpdateIdentity());
|
|
235
|
+
this.elements.btnAddPasskeyEl?.addEventListener('click', () => this.handleAddPasskey());
|
|
194
236
|
this.elements.btnLinkGoogleEl.addEventListener('click', () => this.handleSSOClick('google'));
|
|
195
237
|
this.elements.btnLinkGithubEl.addEventListener('click', () => this.handleSSOClick('github'));
|
|
196
238
|
this.elements.btnChangePasswordEl.addEventListener('click', () => this.handleChangePassword());
|
|
239
|
+
this.elements.btnResendAddPasswordOtpEl?.addEventListener('click', () => this.handleResendAddPasswordOtp());
|
|
197
240
|
}
|
|
198
241
|
toggleAccordion() {
|
|
199
242
|
const header = this.elements.rawAccordionHeaderEl;
|
|
@@ -399,13 +442,22 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
|
|
|
399
442
|
this.elements.btnLinkGithubEl.classList.remove('disabled');
|
|
400
443
|
// Check if origin provider or linked
|
|
401
444
|
const originProvider = this.aggregatedDetails?.custodianProvider || this.ibGib?.data?.frameDetails?.custodianProvider;
|
|
402
|
-
const isGoogleLinked =
|
|
403
|
-
|
|
404
|
-
|
|
445
|
+
const isGoogleLinked = !!this.ibGib ?
|
|
446
|
+
isSsoProviderLinked({ keystone: this.ibGib, aggregatedDetails: this.aggregatedDetails, providerId: 'google' }) :
|
|
447
|
+
false;
|
|
448
|
+
const isGithubLinked = !!this.ibGib ?
|
|
449
|
+
isSsoProviderLinked({ keystone: this.ibGib, aggregatedDetails: this.aggregatedDetails, providerId: 'github' }) :
|
|
450
|
+
false;
|
|
451
|
+
const caps = !!this.ibGib ?
|
|
452
|
+
getKeystoneCapabilities({ keystone: this.ibGib, aggregatedDetails: this.aggregatedDetails }) :
|
|
453
|
+
undefined;
|
|
405
454
|
// Google Card States
|
|
406
455
|
const googleCard = this.elements.btnLinkGoogleEl;
|
|
407
456
|
const googleLabel = googleCard.querySelector('.toggle-state-text');
|
|
408
457
|
if (isGoogleLinked) {
|
|
458
|
+
if (!caps) {
|
|
459
|
+
throw new Error(`(UNEXPECTED) isGoogleLinked but capabilities falsy? (E: cc62e8357b66860dd2cb8148c833b726)`);
|
|
460
|
+
}
|
|
409
461
|
googleCard.classList.remove('unlinked');
|
|
410
462
|
googleCard.classList.add('linked');
|
|
411
463
|
const isOrigin = originProvider === 'google';
|
|
@@ -432,6 +484,9 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
|
|
|
432
484
|
const githubCard = this.elements.btnLinkGithubEl;
|
|
433
485
|
const githubLabel = githubCard.querySelector('.toggle-state-text');
|
|
434
486
|
if (isGithubLinked) {
|
|
487
|
+
if (!caps) {
|
|
488
|
+
throw new Error(`(UNEXPECTED) isGithubLinked but capabilities falsy? (E: e94688210538e0e82878088873a46826)`);
|
|
489
|
+
}
|
|
435
490
|
githubCard.classList.remove('unlinked');
|
|
436
491
|
githubCard.classList.add('linked');
|
|
437
492
|
const isOrigin = originProvider === 'github';
|
|
@@ -457,12 +512,16 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
|
|
|
457
512
|
}
|
|
458
513
|
}
|
|
459
514
|
updatePasswordCardView() {
|
|
460
|
-
const caps =
|
|
515
|
+
const caps = !!this.ibGib ?
|
|
516
|
+
getKeystoneCapabilities({ keystone: this.ibGib, aggregatedDetails: this.aggregatedDetails }) :
|
|
517
|
+
false;
|
|
461
518
|
const cardTitleEl = this.elements?.changePasswordCardEl?.querySelector('h3');
|
|
462
519
|
const cardDescEl = this.elements?.changePasswordCardEl?.querySelector('.description');
|
|
463
520
|
const currentPasswordGroup = this.elements?.inputCurrentPasswordEl?.closest('.form-group');
|
|
464
521
|
const btnChangePassword = this.elements?.btnChangePasswordEl;
|
|
465
|
-
const isCustodialOnly = caps
|
|
522
|
+
const isCustodialOnly = !!caps ?
|
|
523
|
+
caps.isCustodial && !caps.isSovereign :
|
|
524
|
+
false;
|
|
466
525
|
if (isCustodialOnly) {
|
|
467
526
|
if (cardTitleEl)
|
|
468
527
|
cardTitleEl.textContent = "Add Master Password";
|
|
@@ -483,6 +542,12 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
|
|
|
483
542
|
if (btnChangePassword)
|
|
484
543
|
btnChangePassword.textContent = "Update Password";
|
|
485
544
|
}
|
|
545
|
+
if (this.elements?.sectionAddPasswordOtpEl) {
|
|
546
|
+
this.elements.sectionAddPasswordOtpEl.classList.add('hidden');
|
|
547
|
+
if (this.elements.inputAddPasswordOtpEl) {
|
|
548
|
+
this.elements.inputAddPasswordOtpEl.value = '';
|
|
549
|
+
}
|
|
550
|
+
}
|
|
486
551
|
}
|
|
487
552
|
async updateFrameDetailsView() {
|
|
488
553
|
const lc = `${this.lc}[${this.updateFrameDetailsView.name}]`;
|
|
@@ -588,17 +653,20 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
|
|
|
588
653
|
this.elements.rawKeystoneJsonEl.textContent = pretty(keystone);
|
|
589
654
|
// Render Challenge Pools
|
|
590
655
|
this.renderChallengePools(keystone.data?.challengePools || []);
|
|
591
|
-
// Render Delegates, SSO link, and Password Change cards (only if this is a primary keystone, hide them for delegate/session keystones)
|
|
656
|
+
// Render Delegates, Passkeys, SSO link, and Password Change cards (only if this is a primary keystone, hide them for delegate/session keystones)
|
|
592
657
|
const details = await this.getKeystoneDetails(this.ibGibAddr);
|
|
593
658
|
if (details.isPrimary) {
|
|
594
659
|
this.elements.delegatesCardEl.classList.remove('hidden');
|
|
595
660
|
await this.renderDelegates(keystone.data?.delegates);
|
|
661
|
+
this.elements.passkeysCardEl.classList.remove('hidden');
|
|
662
|
+
await this.renderPasskeys(this.aggregatedDetails?.passkeys);
|
|
596
663
|
this.elements.ssoLinkCardEl.classList.remove('hidden');
|
|
597
664
|
this.elements.changePasswordCardEl.classList.remove('hidden');
|
|
598
665
|
this.updatePasswordCardView();
|
|
599
666
|
}
|
|
600
667
|
else {
|
|
601
668
|
this.elements.delegatesCardEl.classList.add('hidden');
|
|
669
|
+
this.elements.passkeysCardEl.classList.add('hidden');
|
|
602
670
|
this.elements.ssoLinkCardEl.classList.add('hidden');
|
|
603
671
|
this.elements.changePasswordCardEl.classList.add('hidden');
|
|
604
672
|
}
|
|
@@ -637,6 +705,15 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
|
|
|
637
705
|
if (this.elements.delegatesListEl) {
|
|
638
706
|
this.elements.delegatesListEl.innerHTML = '';
|
|
639
707
|
}
|
|
708
|
+
if (this.elements.passkeysListEl) {
|
|
709
|
+
this.elements.passkeysListEl.innerHTML = '';
|
|
710
|
+
}
|
|
711
|
+
if (this.elements.sectionAddPasswordOtpEl) {
|
|
712
|
+
this.elements.sectionAddPasswordOtpEl.classList.add('hidden');
|
|
713
|
+
if (this.elements.inputAddPasswordOtpEl) {
|
|
714
|
+
this.elements.inputAddPasswordOtpEl.value = '';
|
|
715
|
+
}
|
|
716
|
+
}
|
|
640
717
|
}
|
|
641
718
|
renderChallengePools(pools) {
|
|
642
719
|
if (!this.elements) {
|
|
@@ -936,8 +1013,9 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
|
|
|
936
1013
|
let masterSecret;
|
|
937
1014
|
try {
|
|
938
1015
|
masterSecret = await promptForSecret({
|
|
939
|
-
msg: `Enter Master Password to revoke delegate '${deviceName}':`,
|
|
940
|
-
confirm: false
|
|
1016
|
+
msg: `Enter Master Password or touch security key to revoke delegate '${deviceName}':`,
|
|
1017
|
+
confirm: false,
|
|
1018
|
+
passkeys: this.aggregatedDetails?.passkeys,
|
|
941
1019
|
});
|
|
942
1020
|
}
|
|
943
1021
|
catch (err) {
|
|
@@ -956,11 +1034,30 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
|
|
|
956
1034
|
throw new Error("Local space not found");
|
|
957
1035
|
}
|
|
958
1036
|
const keystoneSvc = new KeystoneService_V1();
|
|
1037
|
+
// Resolve which manage pool matches the provided masterSecret
|
|
1038
|
+
let matchedPoolId;
|
|
1039
|
+
const candidatePools = this.ibGib.data?.challengePools.filter(p => p.id === 'manage' || p.id.startsWith('passkey-manage-') || (p.config.allowedVerbs && p.config.allowedVerbs.includes(KEYSTONE_VERB_MANAGE))) || [];
|
|
1040
|
+
for (const pool of candidatePools) {
|
|
1041
|
+
const ok = await keystoneSvc.verifySigningSecret({
|
|
1042
|
+
keystoneIbGib: this.ibGib,
|
|
1043
|
+
signingSecret: masterSecret,
|
|
1044
|
+
poolId: pool.id
|
|
1045
|
+
});
|
|
1046
|
+
if (ok) {
|
|
1047
|
+
matchedPoolId = pool.id;
|
|
1048
|
+
break;
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
if (!matchedPoolId) {
|
|
1052
|
+
this.setSyncStatus("Failed to authorize revocation (invalid secret/passkey).", "error");
|
|
1053
|
+
return;
|
|
1054
|
+
}
|
|
959
1055
|
// Unregister delegate from parent keystone
|
|
960
1056
|
const evolvedParent = await keystoneSvc.unregisterDelegate({
|
|
961
1057
|
parentKeystone: this.ibGib,
|
|
962
1058
|
delegateTjpAddr,
|
|
963
1059
|
masterSecret,
|
|
1060
|
+
signingPoolId: matchedPoolId,
|
|
964
1061
|
metaspace,
|
|
965
1062
|
space
|
|
966
1063
|
});
|
|
@@ -1006,6 +1103,452 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
|
|
|
1006
1103
|
this.setSyncStatus(`Revocation failed: ${extractErrorMsg(error)}`, "error");
|
|
1007
1104
|
}
|
|
1008
1105
|
}
|
|
1106
|
+
setPasskeyStatus(msg, type) {
|
|
1107
|
+
const statusEl = this.elements?.passkeyStatusMsgEl;
|
|
1108
|
+
if (!statusEl)
|
|
1109
|
+
return;
|
|
1110
|
+
statusEl.textContent = msg;
|
|
1111
|
+
statusEl.className = `status-msg ${type}`;
|
|
1112
|
+
statusEl.classList.remove('hidden');
|
|
1113
|
+
}
|
|
1114
|
+
async renderPasskeys(passkeys) {
|
|
1115
|
+
if (!this.elements) {
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
1118
|
+
const listEl = this.elements.passkeysListEl;
|
|
1119
|
+
listEl.innerHTML = '';
|
|
1120
|
+
const caps = !!this.ibGib ?
|
|
1121
|
+
getKeystoneCapabilities({ keystone: this.ibGib, aggregatedDetails: this.aggregatedDetails }) :
|
|
1122
|
+
undefined;
|
|
1123
|
+
// Update button text depending on whether the account is custodial-only (elevation) or sovereign/hybrid
|
|
1124
|
+
const isCustodialOnly = !!caps ? caps.isCustodial && !caps.isSovereign : false;
|
|
1125
|
+
if (this.elements.btnAddPasskeyEl) {
|
|
1126
|
+
this.elements.btnAddPasskeyEl.textContent = isCustodialOnly
|
|
1127
|
+
? "🛡️ Add Passkey (Passwordless Sovereign Elevation)"
|
|
1128
|
+
: "🛡️ Register New Passkey / Security Key";
|
|
1129
|
+
}
|
|
1130
|
+
const validPasskeyEntries = Object.entries(passkeys || {}).filter(([_, entry]) => entry !== null && entry !== undefined);
|
|
1131
|
+
if (validPasskeyEntries.length === 0) {
|
|
1132
|
+
listEl.innerHTML = `
|
|
1133
|
+
<tr>
|
|
1134
|
+
<td colspan="4" style="text-align: center; color: var(--clr-text-secondary, #b3b3b3); padding: 1rem;">
|
|
1135
|
+
No hardware passkeys or biometrics registered.
|
|
1136
|
+
</td>
|
|
1137
|
+
</tr>
|
|
1138
|
+
`;
|
|
1139
|
+
return;
|
|
1140
|
+
}
|
|
1141
|
+
for (const [credId, entry] of validPasskeyEntries) {
|
|
1142
|
+
const row = document.createElement('tr');
|
|
1143
|
+
const createdText = entry.createdUTC ? new Date(entry.createdUTC).toLocaleString() : 'N/A';
|
|
1144
|
+
const nameText = entry.name || 'Hardware Key';
|
|
1145
|
+
// Format transport badges
|
|
1146
|
+
let transportBadgesHtml = '';
|
|
1147
|
+
if (entry.transports && entry.transports.length > 0) {
|
|
1148
|
+
transportBadgesHtml = entry.transports.map(t => {
|
|
1149
|
+
let icon = '🔑';
|
|
1150
|
+
let label = t;
|
|
1151
|
+
if (t === 'internal') {
|
|
1152
|
+
icon = '💻';
|
|
1153
|
+
label = 'Touch ID / Built-in';
|
|
1154
|
+
}
|
|
1155
|
+
else if (t === 'usb') {
|
|
1156
|
+
icon = '🔑';
|
|
1157
|
+
label = 'USB Key';
|
|
1158
|
+
}
|
|
1159
|
+
else if (t === 'nfc') {
|
|
1160
|
+
icon = '📶';
|
|
1161
|
+
label = 'NFC';
|
|
1162
|
+
}
|
|
1163
|
+
else if (t === 'ble' || t === 'hybrid') {
|
|
1164
|
+
icon = '📲';
|
|
1165
|
+
label = 'Mobile / Bluetooth';
|
|
1166
|
+
}
|
|
1167
|
+
return `<span class="badge badge-remote-device" style="background-color: #1b5e20; color: #a5d6a7; font-size: 0.75rem; padding: 0.2rem 0.5rem; border-radius: 4px; font-weight: bold; margin-right: 0.3rem;">${icon} ${label}</span>`;
|
|
1168
|
+
}).join(' ');
|
|
1169
|
+
}
|
|
1170
|
+
else {
|
|
1171
|
+
transportBadgesHtml = `<span class="badge badge-remote-device" style="background-color: #2e7d32; color: #c8e6c9; font-size: 0.75rem; padding: 0.2rem 0.5rem; border-radius: 4px; font-weight: bold;">🛡️ Resident Passkey</span>`;
|
|
1172
|
+
}
|
|
1173
|
+
row.innerHTML = `
|
|
1174
|
+
<td>
|
|
1175
|
+
<div style="font-weight: 500;">${nameText}</div>
|
|
1176
|
+
<code style="font-size: 0.75rem; color: var(--clr-text-secondary, #b3b3b3); word-break: break-all;">${credId.substring(0, 16)}...</code>
|
|
1177
|
+
</td>
|
|
1178
|
+
<td>${transportBadgesHtml}</td>
|
|
1179
|
+
<td style="font-size: 0.85rem; color: var(--clr-text-secondary, #b3b3b3);">${createdText}</td>
|
|
1180
|
+
<td>
|
|
1181
|
+
<button class="action-btn secondary btn-remove-passkey" style="font-size: 0.8rem; padding: 0.3rem 0.6rem; background: rgba(239, 83, 80, 0.1); color: #ef5350; border: 1px solid rgba(239, 83, 80, 0.3);">
|
|
1182
|
+
Remove
|
|
1183
|
+
</button>
|
|
1184
|
+
</td>
|
|
1185
|
+
`;
|
|
1186
|
+
const btnRemove = row.querySelector('.btn-remove-passkey');
|
|
1187
|
+
btnRemove?.addEventListener('click', () => this.handleRemovePasskey(credId));
|
|
1188
|
+
listEl.appendChild(row);
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
async handleAddPasskey() {
|
|
1192
|
+
const lc = `${this.lc}[${this.handleAddPasskey.name}]`;
|
|
1193
|
+
if (!this.ibGib || !this.ibGibAddr) {
|
|
1194
|
+
return;
|
|
1195
|
+
}
|
|
1196
|
+
try {
|
|
1197
|
+
const caps = getKeystoneCapabilities({ keystone: this.ibGib, aggregatedDetails: this.aggregatedDetails });
|
|
1198
|
+
const isCustodialOnly = caps.isCustodial && !caps.isSovereign;
|
|
1199
|
+
const supported = await isPasskeyPrfSupported();
|
|
1200
|
+
if (!supported) {
|
|
1201
|
+
this.setPasskeyStatus("WebAuthn PRF extension is not supported in this browser environment.", "error");
|
|
1202
|
+
return;
|
|
1203
|
+
}
|
|
1204
|
+
this.showBusy({
|
|
1205
|
+
isBusy: true,
|
|
1206
|
+
title: 'Touch Biometrics or Security Key',
|
|
1207
|
+
msg: 'Please touch your fingerprint sensor, Face ID, or insert your USB security key when prompted...',
|
|
1208
|
+
animationEmoji: '🛡️'
|
|
1209
|
+
});
|
|
1210
|
+
const username = this.aggregatedDetails?.username || this.ibGib.data?.frameDetails?.username || 'user';
|
|
1211
|
+
const rawSalt = crypto.getRandomValues(new Uint8Array(32));
|
|
1212
|
+
const saltHex = bufferToHex(rawSalt.buffer);
|
|
1213
|
+
const regResult = await registerPasskeyCredential({
|
|
1214
|
+
username,
|
|
1215
|
+
displayName: username,
|
|
1216
|
+
salt: saltHex
|
|
1217
|
+
});
|
|
1218
|
+
const { credentialId, transports } = regResult;
|
|
1219
|
+
let passkeySecret = regResult.passkeySecret;
|
|
1220
|
+
if (!passkeySecret) {
|
|
1221
|
+
const prfResult = await evaluatePasskeyPrf({
|
|
1222
|
+
credentialId,
|
|
1223
|
+
salt: saltHex,
|
|
1224
|
+
transports
|
|
1225
|
+
});
|
|
1226
|
+
passkeySecret = prfResult.passkeySecret;
|
|
1227
|
+
}
|
|
1228
|
+
const defaultDeviceTag = generateDefaultDeviceTag();
|
|
1229
|
+
const passkeyName = defaultDeviceTag;
|
|
1230
|
+
const passkeyTag = getSaferSubstring({ text: passkeyName, length: 12, keepLiterals: ['-'] }) || 'key';
|
|
1231
|
+
const poolId = `passkey-manage-${passkeyTag}`;
|
|
1232
|
+
const passkeyEntry = {
|
|
1233
|
+
credentialId,
|
|
1234
|
+
poolId,
|
|
1235
|
+
name: passkeyName,
|
|
1236
|
+
salt: saltHex,
|
|
1237
|
+
createdUTC: new Date().toISOString(),
|
|
1238
|
+
...(transports && transports.length > 0 ? { transports } : {})
|
|
1239
|
+
};
|
|
1240
|
+
const metaspace = await getGlobalMetaspace_waitIfNeeded();
|
|
1241
|
+
if (!metaspace)
|
|
1242
|
+
throw new Error("Metaspace not found");
|
|
1243
|
+
const space = await metaspace.getLocalUserSpace({ lock: false });
|
|
1244
|
+
if (!space)
|
|
1245
|
+
throw new Error("Local space not found");
|
|
1246
|
+
const keystoneService = new KeystoneService_V1();
|
|
1247
|
+
let evolvedKeystone;
|
|
1248
|
+
if (isCustodialOnly) {
|
|
1249
|
+
// Elevate custodial account with sovereign passkey
|
|
1250
|
+
this.showBusy({
|
|
1251
|
+
isBusy: true,
|
|
1252
|
+
title: 'Elevating Account Ownership...',
|
|
1253
|
+
msg: 'Establishing sovereign passkey pool on identity...',
|
|
1254
|
+
animationEmoji: '⚡'
|
|
1255
|
+
});
|
|
1256
|
+
// Generate new manage pool configuration using passkeySecret
|
|
1257
|
+
const poolConfig = {
|
|
1258
|
+
id: poolId,
|
|
1259
|
+
type: KeystoneChallengeType.hash_reveal_v1,
|
|
1260
|
+
salt: poolId,
|
|
1261
|
+
algo: HashAlgorithm.sha_256,
|
|
1262
|
+
rounds: 1,
|
|
1263
|
+
allowedVerbs: [KEYSTONE_VERB_MANAGE, KEYSTONE_VERB_SYNC, KEYSTONE_VERB_CONNECT],
|
|
1264
|
+
behavior: {
|
|
1265
|
+
size: 10,
|
|
1266
|
+
replenish: KeystoneReplenishStrategy.topUp,
|
|
1267
|
+
selectSequentially: 1,
|
|
1268
|
+
selectRandomly: 0,
|
|
1269
|
+
targetBindingCount: 0,
|
|
1270
|
+
}
|
|
1271
|
+
};
|
|
1272
|
+
const challenges = await generatePoolChallenges({
|
|
1273
|
+
config: poolConfig,
|
|
1274
|
+
masterSecret: passkeySecret
|
|
1275
|
+
});
|
|
1276
|
+
const newPool = {
|
|
1277
|
+
id: poolId,
|
|
1278
|
+
config: poolConfig,
|
|
1279
|
+
challenges
|
|
1280
|
+
};
|
|
1281
|
+
evolvedKeystone = await keystoneService.addPools({
|
|
1282
|
+
latestKeystone: this.ibGib,
|
|
1283
|
+
masterSecret: passkeySecret,
|
|
1284
|
+
newPools: [newPool],
|
|
1285
|
+
frameDetails: {
|
|
1286
|
+
passkeys: {
|
|
1287
|
+
[credentialId]: passkeyEntry
|
|
1288
|
+
}
|
|
1289
|
+
},
|
|
1290
|
+
dataToPatch: {
|
|
1291
|
+
credentialIds: [...(this.ibGib.data?.credentialIds || []), credentialId]
|
|
1292
|
+
},
|
|
1293
|
+
metaspace,
|
|
1294
|
+
space
|
|
1295
|
+
});
|
|
1296
|
+
}
|
|
1297
|
+
else {
|
|
1298
|
+
// Secondary passkey registration on existing sovereign identity:
|
|
1299
|
+
// Prompt user to authorize with current master password
|
|
1300
|
+
let authSecret;
|
|
1301
|
+
let isCorrect = false;
|
|
1302
|
+
let tries = 0;
|
|
1303
|
+
const maxTries = 5;
|
|
1304
|
+
while (tries < maxTries && !isCorrect) {
|
|
1305
|
+
try {
|
|
1306
|
+
const promptMsg = tries === 0
|
|
1307
|
+
? 'Enter your current Master Secret (Password) or touch security key to authorize adding this passkey:'
|
|
1308
|
+
: `Incorrect password. Please try again (${maxTries - tries} tries remaining):`;
|
|
1309
|
+
authSecret = await promptForSecret({
|
|
1310
|
+
msg: promptMsg,
|
|
1311
|
+
confirm: false,
|
|
1312
|
+
passkeys: this.aggregatedDetails?.passkeys,
|
|
1313
|
+
});
|
|
1314
|
+
}
|
|
1315
|
+
catch (err) {
|
|
1316
|
+
authSecret = undefined;
|
|
1317
|
+
break;
|
|
1318
|
+
}
|
|
1319
|
+
if (!authSecret)
|
|
1320
|
+
break;
|
|
1321
|
+
// Verify secret against manage/passkey pools
|
|
1322
|
+
isCorrect = await keystoneService.verifySigningSecret({
|
|
1323
|
+
keystoneIbGib: this.ibGib,
|
|
1324
|
+
signingSecret: authSecret
|
|
1325
|
+
});
|
|
1326
|
+
if (!isCorrect)
|
|
1327
|
+
tries++;
|
|
1328
|
+
}
|
|
1329
|
+
if (!isCorrect || !authSecret) {
|
|
1330
|
+
this.setPasskeyStatus("Registration cancelled (authorization required).", "error");
|
|
1331
|
+
return;
|
|
1332
|
+
}
|
|
1333
|
+
this.showBusy({
|
|
1334
|
+
isBusy: true,
|
|
1335
|
+
title: 'Registering Passkey...',
|
|
1336
|
+
msg: 'Adding cryptographic challenge pool and evolving identity...',
|
|
1337
|
+
animationEmoji: '🛡️'
|
|
1338
|
+
});
|
|
1339
|
+
const poolConfig = {
|
|
1340
|
+
id: poolId,
|
|
1341
|
+
type: KeystoneChallengeType.hash_reveal_v1,
|
|
1342
|
+
salt: poolId,
|
|
1343
|
+
algo: HashAlgorithm.sha_256,
|
|
1344
|
+
rounds: 1,
|
|
1345
|
+
allowedVerbs: [KEYSTONE_VERB_MANAGE, KEYSTONE_VERB_SYNC, KEYSTONE_VERB_CONNECT],
|
|
1346
|
+
behavior: {
|
|
1347
|
+
size: 10,
|
|
1348
|
+
replenish: KeystoneReplenishStrategy.topUp,
|
|
1349
|
+
selectSequentially: 1,
|
|
1350
|
+
selectRandomly: 0,
|
|
1351
|
+
targetBindingCount: 0,
|
|
1352
|
+
}
|
|
1353
|
+
};
|
|
1354
|
+
const challenges = await generatePoolChallenges({
|
|
1355
|
+
config: poolConfig,
|
|
1356
|
+
masterSecret: passkeySecret
|
|
1357
|
+
});
|
|
1358
|
+
const newPool = {
|
|
1359
|
+
id: poolId,
|
|
1360
|
+
config: poolConfig,
|
|
1361
|
+
challenges
|
|
1362
|
+
};
|
|
1363
|
+
evolvedKeystone = await keystoneService.addPools({
|
|
1364
|
+
latestKeystone: this.ibGib,
|
|
1365
|
+
masterSecret: authSecret,
|
|
1366
|
+
newPools: [newPool],
|
|
1367
|
+
frameDetails: {
|
|
1368
|
+
passkeys: {
|
|
1369
|
+
[credentialId]: passkeyEntry
|
|
1370
|
+
}
|
|
1371
|
+
},
|
|
1372
|
+
dataToPatch: {
|
|
1373
|
+
credentialIds: [...(this.ibGib.data?.credentialIds || []), credentialId]
|
|
1374
|
+
},
|
|
1375
|
+
metaspace,
|
|
1376
|
+
space
|
|
1377
|
+
});
|
|
1378
|
+
}
|
|
1379
|
+
// Sync evolved keystone to server
|
|
1380
|
+
this.showBusy({
|
|
1381
|
+
isBusy: true,
|
|
1382
|
+
title: 'Syncing to Server...',
|
|
1383
|
+
msg: 'Publishing evolved identity keystone to server...',
|
|
1384
|
+
animationEmoji: '⚡'
|
|
1385
|
+
});
|
|
1386
|
+
const primaryTjpAddr = getTjpAddr({ ibGib: evolvedKeystone, defaultIfNone: 'incomingAddr' });
|
|
1387
|
+
const resSync = await getApiBridge().putEvolveKeystone({
|
|
1388
|
+
domainAddr: primaryTjpAddr,
|
|
1389
|
+
keystoneIbGib: evolvedKeystone,
|
|
1390
|
+
relatedKeystoneIbGibs: []
|
|
1391
|
+
});
|
|
1392
|
+
if (!resSync.success) {
|
|
1393
|
+
console.warn(`${lc} Server sync note: ${resSync.message}`);
|
|
1394
|
+
}
|
|
1395
|
+
// Update Special Index
|
|
1396
|
+
await updateSpecialIndex({
|
|
1397
|
+
type: "keystones",
|
|
1398
|
+
rel8nInfos: [
|
|
1399
|
+
{
|
|
1400
|
+
rel8nName: "keystone",
|
|
1401
|
+
ibGibs: [toDto({ ibGib: evolvedKeystone })]
|
|
1402
|
+
}
|
|
1403
|
+
],
|
|
1404
|
+
metaspace,
|
|
1405
|
+
space
|
|
1406
|
+
});
|
|
1407
|
+
this.setPasskeyStatus("Passkey successfully registered and bound to identity!", "success");
|
|
1408
|
+
this.ibGibAddr = getIbGibAddr({ ibGib: evolvedKeystone });
|
|
1409
|
+
this.staticIbGib = evolvedKeystone;
|
|
1410
|
+
await this.loadIbGib({ getLatest: false });
|
|
1411
|
+
}
|
|
1412
|
+
catch (error) {
|
|
1413
|
+
console.error(`${lc} ${extractErrorMsg(error)}`, error);
|
|
1414
|
+
this.setPasskeyStatus(`Failed to register passkey: ${extractErrorMsg(error)}`, "error");
|
|
1415
|
+
}
|
|
1416
|
+
finally {
|
|
1417
|
+
this.showBusy({ isBusy: false });
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
async handleRemovePasskey(credentialId) {
|
|
1421
|
+
const lc = `${this.lc}[${this.handleRemovePasskey.name}]`;
|
|
1422
|
+
if (!this.ibGib || !this.ibGibAddr) {
|
|
1423
|
+
return;
|
|
1424
|
+
}
|
|
1425
|
+
const passkeyEntry = this.aggregatedDetails?.passkeys?.[credentialId];
|
|
1426
|
+
const passkeyName = passkeyEntry?.name || 'this passkey';
|
|
1427
|
+
try {
|
|
1428
|
+
const keystoneService = new KeystoneService_V1();
|
|
1429
|
+
let masterSecret;
|
|
1430
|
+
let isCorrect = false;
|
|
1431
|
+
let tries = 0;
|
|
1432
|
+
const maxTries = 5;
|
|
1433
|
+
while (tries < maxTries && !isCorrect) {
|
|
1434
|
+
try {
|
|
1435
|
+
const promptMsg = tries === 0
|
|
1436
|
+
? `Are you sure you want to remove "${passkeyName}"? Enter your Master Secret (Password) or touch security key to authorize:`
|
|
1437
|
+
: `Incorrect password. Please try again (${maxTries - tries} tries remaining):`;
|
|
1438
|
+
masterSecret = await promptForSecret({
|
|
1439
|
+
msg: promptMsg,
|
|
1440
|
+
confirm: false,
|
|
1441
|
+
passkeys: this.aggregatedDetails?.passkeys,
|
|
1442
|
+
});
|
|
1443
|
+
}
|
|
1444
|
+
catch (err) {
|
|
1445
|
+
masterSecret = undefined;
|
|
1446
|
+
break;
|
|
1447
|
+
}
|
|
1448
|
+
if (!masterSecret)
|
|
1449
|
+
break;
|
|
1450
|
+
isCorrect = await keystoneService.verifySigningSecret({
|
|
1451
|
+
keystoneIbGib: this.ibGib,
|
|
1452
|
+
signingSecret: masterSecret
|
|
1453
|
+
});
|
|
1454
|
+
if (!isCorrect)
|
|
1455
|
+
tries++;
|
|
1456
|
+
}
|
|
1457
|
+
if (!isCorrect || !masterSecret) {
|
|
1458
|
+
this.setPasskeyStatus("Removal cancelled or unauthorized.", "error");
|
|
1459
|
+
return;
|
|
1460
|
+
}
|
|
1461
|
+
this.showBusy({
|
|
1462
|
+
isBusy: true,
|
|
1463
|
+
title: 'Revoking Passkey...',
|
|
1464
|
+
msg: 'Tombstoning passkey record and removing challenge pool...',
|
|
1465
|
+
animationEmoji: '🗑️'
|
|
1466
|
+
});
|
|
1467
|
+
const metaspace = await getGlobalMetaspace_waitIfNeeded();
|
|
1468
|
+
if (!metaspace)
|
|
1469
|
+
throw new Error("Metaspace not found");
|
|
1470
|
+
const space = await metaspace.getLocalUserSpace({ lock: false });
|
|
1471
|
+
if (!space)
|
|
1472
|
+
throw new Error("Local space not found");
|
|
1473
|
+
const poolIdToRemove = passkeyEntry?.poolId || `passkey-manage-${credentialId.substring(0, 8)}`;
|
|
1474
|
+
const poolExists = this.ibGib.data?.challengePools.some(p => p.id === poolIdToRemove);
|
|
1475
|
+
const filteredCredentialIds = (this.ibGib.data?.credentialIds || []).filter(id => id !== credentialId);
|
|
1476
|
+
let evolvedKeystone;
|
|
1477
|
+
if (poolExists) {
|
|
1478
|
+
evolvedKeystone = await keystoneService.removePools({
|
|
1479
|
+
latestKeystone: this.ibGib,
|
|
1480
|
+
masterSecret,
|
|
1481
|
+
poolIds: [poolIdToRemove],
|
|
1482
|
+
frameDetails: {
|
|
1483
|
+
passkeys: {
|
|
1484
|
+
[credentialId]: null
|
|
1485
|
+
}
|
|
1486
|
+
},
|
|
1487
|
+
dataToPatch: {
|
|
1488
|
+
credentialIds: filteredCredentialIds
|
|
1489
|
+
},
|
|
1490
|
+
metaspace,
|
|
1491
|
+
space
|
|
1492
|
+
});
|
|
1493
|
+
}
|
|
1494
|
+
else {
|
|
1495
|
+
evolvedKeystone = await keystoneService.sign({
|
|
1496
|
+
latestKeystone: this.ibGib,
|
|
1497
|
+
masterSecret,
|
|
1498
|
+
claim: {
|
|
1499
|
+
verb: POOL_ID_MANAGE,
|
|
1500
|
+
target: getIbGibAddr({ ibGib: this.ibGib })
|
|
1501
|
+
},
|
|
1502
|
+
frameDetails: {
|
|
1503
|
+
passkeys: {
|
|
1504
|
+
[credentialId]: null
|
|
1505
|
+
}
|
|
1506
|
+
},
|
|
1507
|
+
dataToPatch: {
|
|
1508
|
+
credentialIds: filteredCredentialIds
|
|
1509
|
+
},
|
|
1510
|
+
metaspace,
|
|
1511
|
+
space
|
|
1512
|
+
});
|
|
1513
|
+
}
|
|
1514
|
+
// Sync to server
|
|
1515
|
+
this.showBusy({
|
|
1516
|
+
isBusy: true,
|
|
1517
|
+
title: 'Syncing to Server...',
|
|
1518
|
+
msg: 'Publishing updated keystone to server...',
|
|
1519
|
+
animationEmoji: '⚡'
|
|
1520
|
+
});
|
|
1521
|
+
const primaryTjpAddr = getTjpAddr({ ibGib: evolvedKeystone, defaultIfNone: 'incomingAddr' });
|
|
1522
|
+
await getApiBridge().putEvolveKeystone({
|
|
1523
|
+
domainAddr: primaryTjpAddr,
|
|
1524
|
+
keystoneIbGib: evolvedKeystone,
|
|
1525
|
+
relatedKeystoneIbGibs: []
|
|
1526
|
+
});
|
|
1527
|
+
// Update Special Index
|
|
1528
|
+
await updateSpecialIndex({
|
|
1529
|
+
type: "keystones",
|
|
1530
|
+
rel8nInfos: [
|
|
1531
|
+
{
|
|
1532
|
+
rel8nName: "keystone",
|
|
1533
|
+
ibGibs: [toDto({ ibGib: evolvedKeystone })]
|
|
1534
|
+
}
|
|
1535
|
+
],
|
|
1536
|
+
metaspace,
|
|
1537
|
+
space
|
|
1538
|
+
});
|
|
1539
|
+
this.setPasskeyStatus(`Passkey "${passkeyName}" successfully removed.`, "success");
|
|
1540
|
+
this.ibGibAddr = getIbGibAddr({ ibGib: evolvedKeystone });
|
|
1541
|
+
this.staticIbGib = evolvedKeystone;
|
|
1542
|
+
await this.loadIbGib({ getLatest: false });
|
|
1543
|
+
}
|
|
1544
|
+
catch (error) {
|
|
1545
|
+
console.error(`${lc} ${extractErrorMsg(error)}`, error);
|
|
1546
|
+
this.setPasskeyStatus(`Failed to remove passkey: ${extractErrorMsg(error)}`, "error");
|
|
1547
|
+
}
|
|
1548
|
+
finally {
|
|
1549
|
+
this.showBusy({ isBusy: false });
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1009
1552
|
async handleContextUpdated() {
|
|
1010
1553
|
const lc = `${this.lc}[${this.handleContextUpdated.name}]`;
|
|
1011
1554
|
try {
|
|
@@ -1050,10 +1593,24 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
|
|
|
1050
1593
|
async handleSSOLink(providerId) {
|
|
1051
1594
|
const lc = `${this.lc}[${this.handleSSOLink.name}]`;
|
|
1052
1595
|
this.setSSOStatus(`Linking ${providerId}...`, "info");
|
|
1596
|
+
const providerName = providerId === 'google' ? 'Google' : providerId === 'github' ? 'GitHub' : providerId;
|
|
1597
|
+
this.setSSOStatus(`Opening ${providerName} SSO authorization window...`, "info");
|
|
1598
|
+
this.showBusy({
|
|
1599
|
+
isBusy: true,
|
|
1600
|
+
title: `Connecting to ${providerName}...`,
|
|
1601
|
+
msg: `Opening ${providerName} SSO authorization window...\nPlease complete authorization in the popup window.`,
|
|
1602
|
+
animationEmoji: '🌐'
|
|
1603
|
+
});
|
|
1053
1604
|
try {
|
|
1605
|
+
const primaryTjpAddr = (this.ibGib ? getTjpAddr({ ibGib: this.ibGib }) : undefined)
|
|
1606
|
+
|| (this.ibGibAddr ? (() => {
|
|
1607
|
+
const { ib } = getIbAndGib({ ibGibAddr: this.ibGibAddr });
|
|
1608
|
+
const { tjpGib } = getGibInfo({ ibGibAddr: this.ibGibAddr });
|
|
1609
|
+
return tjpGib && ib ? `${ib}^${tjpGib}` : this.ibGibAddr;
|
|
1610
|
+
})() : undefined);
|
|
1054
1611
|
const resPopup = await executeSsoOAuthPopup({
|
|
1055
1612
|
providerId,
|
|
1056
|
-
parentTjpAddr:
|
|
1613
|
+
parentTjpAddr: primaryTjpAddr
|
|
1057
1614
|
});
|
|
1058
1615
|
if (!resPopup.success || !resPopup.code) {
|
|
1059
1616
|
this.setSSOStatus(resPopup.message || `Linking cancelled.`, "error");
|
|
@@ -1065,6 +1622,9 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
|
|
|
1065
1622
|
console.error(`${lc} SSO link initiation failed: ${extractErrorMsg(error)}`);
|
|
1066
1623
|
this.setSSOStatus(`Link failed: ${extractErrorMsg(error)}`, "error");
|
|
1067
1624
|
}
|
|
1625
|
+
finally {
|
|
1626
|
+
this.showBusy({ isBusy: false });
|
|
1627
|
+
}
|
|
1068
1628
|
}
|
|
1069
1629
|
setSSOStatus(msg, type) {
|
|
1070
1630
|
const statusEl = this.elements.ssoStatusMsgEl;
|
|
@@ -1074,7 +1634,14 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
|
|
|
1074
1634
|
}
|
|
1075
1635
|
async handleSSOLinkCallback(code, stateObj, redirectUri) {
|
|
1076
1636
|
const lc = `${this.lc}[${this.handleSSOLinkCallback.name}]`;
|
|
1077
|
-
|
|
1637
|
+
const providerName = stateObj.providerId === 'google' ? 'Google' : stateObj.providerId === 'github' ? 'GitHub' : stateObj.providerId;
|
|
1638
|
+
this.setSSOStatus(`Retrieving challenge pool configuration from server...`, "info");
|
|
1639
|
+
this.showBusy({
|
|
1640
|
+
isBusy: true,
|
|
1641
|
+
title: `Linking ${providerName}...`,
|
|
1642
|
+
msg: `Validating ${providerName} authorization with server...`,
|
|
1643
|
+
animationEmoji: '🔐'
|
|
1644
|
+
});
|
|
1078
1645
|
try {
|
|
1079
1646
|
// 1. Exchange OAuth code for custodian public pool config
|
|
1080
1647
|
const res = await fetch('/api/identity/sso/link', {
|
|
@@ -1098,6 +1665,7 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
|
|
|
1098
1665
|
}
|
|
1099
1666
|
const custodianPool = linkData.custodianPool;
|
|
1100
1667
|
// 2. Prompt user for master secret and verify it
|
|
1668
|
+
this.showBusy({ isBusy: false });
|
|
1101
1669
|
const keystoneService = new KeystoneService_V1();
|
|
1102
1670
|
let masterSecret;
|
|
1103
1671
|
let isCorrect = false;
|
|
@@ -1138,6 +1706,12 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
|
|
|
1138
1706
|
this.setSSOStatus("Link cancelled or failed (incorrect password).", "error");
|
|
1139
1707
|
return;
|
|
1140
1708
|
}
|
|
1709
|
+
this.showBusy({
|
|
1710
|
+
isBusy: true,
|
|
1711
|
+
title: 'Evolving Identity...',
|
|
1712
|
+
msg: `Installing ${providerName} custodian pool and signing evolution...`,
|
|
1713
|
+
animationEmoji: '📝'
|
|
1714
|
+
});
|
|
1141
1715
|
this.setSSOStatus("Evolving identity locally and generating cryptographic signatures...", "info");
|
|
1142
1716
|
const metaspace = await getGlobalMetaspace_waitIfNeeded();
|
|
1143
1717
|
if (!metaspace)
|
|
@@ -1146,7 +1720,7 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
|
|
|
1146
1720
|
if (!space) {
|
|
1147
1721
|
throw new Error("Local space not found");
|
|
1148
1722
|
}
|
|
1149
|
-
// 3. Evolve parent keystone to include the custodian pool
|
|
1723
|
+
// 3. Evolve parent keystone to include the custodian pool (n -> n+1)
|
|
1150
1724
|
const evolvedParent = await keystoneService.addPools({
|
|
1151
1725
|
latestKeystone: this.ibGib,
|
|
1152
1726
|
masterSecret: masterSecret,
|
|
@@ -1154,38 +1728,81 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
|
|
|
1154
1728
|
metaspace,
|
|
1155
1729
|
space
|
|
1156
1730
|
});
|
|
1157
|
-
//
|
|
1158
|
-
|
|
1159
|
-
const syncRes = await getApiBridge().putEvolveKeystone({
|
|
1731
|
+
// Sync evolved parent (n+1) to server first
|
|
1732
|
+
const syncRes1 = await getApiBridge().putEvolveKeystone({
|
|
1160
1733
|
domainAddr: stateObj.parentTjpAddr,
|
|
1161
1734
|
keystoneIbGib: evolvedParent,
|
|
1162
|
-
relatedKeystoneIbGibs: []
|
|
1735
|
+
relatedKeystoneIbGibs: []
|
|
1163
1736
|
});
|
|
1164
|
-
if (!
|
|
1165
|
-
throw new Error(`Server synchronization rejected: ${
|
|
1737
|
+
if (!syncRes1.success) {
|
|
1738
|
+
throw new Error(`Server synchronization of linked pool rejected: ${syncRes1.message}`);
|
|
1739
|
+
}
|
|
1740
|
+
// 4. Create and register OAuth2 sync delegate for this provider (n+1 -> n+2)
|
|
1741
|
+
const username = this.aggregatedDetails?.username || this.ibGib?.data?.username || 'user';
|
|
1742
|
+
const { delegateKeystone, deviceTag } = await createOAuth2DeviceSyncDelegate({
|
|
1743
|
+
providerId: stateObj.providerId,
|
|
1744
|
+
username,
|
|
1745
|
+
primaryTjpAddr: stateObj.parentTjpAddr,
|
|
1746
|
+
custodianReqs: linkData.custodianReqs,
|
|
1747
|
+
metaspace,
|
|
1748
|
+
space,
|
|
1749
|
+
});
|
|
1750
|
+
const parentWithDelegate = await keystoneService.registerDelegate({
|
|
1751
|
+
parentKeystone: evolvedParent,
|
|
1752
|
+
delegateKeystone,
|
|
1753
|
+
masterSecret: masterSecret,
|
|
1754
|
+
signingPoolId: 'manage',
|
|
1755
|
+
metaspace,
|
|
1756
|
+
space,
|
|
1757
|
+
allowedVerbs: [KEYSTONE_VERB_SYNC, KEYSTONE_VERB_CONNECT],
|
|
1758
|
+
deviceName: deviceTag,
|
|
1759
|
+
});
|
|
1760
|
+
await metaspace.put({ ibGibs: [delegateKeystone, parentWithDelegate], space });
|
|
1761
|
+
// 5. Sync parent with registered delegate (n+2) to server
|
|
1762
|
+
this.showBusy({
|
|
1763
|
+
isBusy: true,
|
|
1764
|
+
title: 'Syncing to Server...',
|
|
1765
|
+
msg: 'Publishing linked identity and delegate to server...',
|
|
1766
|
+
animationEmoji: '⚡'
|
|
1767
|
+
});
|
|
1768
|
+
this.setSSOStatus("Publishing evolved identity keystone to server...", "info");
|
|
1769
|
+
const syncRes2 = await getApiBridge().putEvolveKeystone({
|
|
1770
|
+
domainAddr: stateObj.parentTjpAddr,
|
|
1771
|
+
keystoneIbGib: parentWithDelegate,
|
|
1772
|
+
relatedKeystoneIbGibs: [delegateKeystone]
|
|
1773
|
+
});
|
|
1774
|
+
if (!syncRes2.success) {
|
|
1775
|
+
throw new Error(`Server synchronization of delegate rejected: ${syncRes2.message}`);
|
|
1166
1776
|
}
|
|
1167
|
-
//
|
|
1777
|
+
// 6. Update index locally
|
|
1168
1778
|
await updateSpecialIndex({
|
|
1169
1779
|
type: "keystones",
|
|
1170
1780
|
rel8nInfos: [
|
|
1171
1781
|
{
|
|
1172
1782
|
rel8nName: "keystone",
|
|
1173
|
-
ibGibs: [toDto({ ibGib:
|
|
1783
|
+
ibGibs: [toDto({ ibGib: parentWithDelegate }), toDto({ ibGib: delegateKeystone })]
|
|
1174
1784
|
}
|
|
1175
1785
|
],
|
|
1176
1786
|
metaspace,
|
|
1177
1787
|
space
|
|
1178
1788
|
});
|
|
1179
1789
|
this.setSSOStatus(`Successfully linked with ${stateObj.providerId}!`, "success");
|
|
1790
|
+
this.ibGibAddr = getIbGibAddr({ ibGib: parentWithDelegate });
|
|
1791
|
+
this.staticIbGib = parentWithDelegate;
|
|
1180
1792
|
await this.loadIbGib({ getLatest: false });
|
|
1181
1793
|
}
|
|
1182
1794
|
catch (error) {
|
|
1183
1795
|
console.error(`${lc} SSO callback processing failed: ${extractErrorMsg(error)}`);
|
|
1184
1796
|
this.setSSOStatus(`Link failed: ${extractErrorMsg(error)}`, "error");
|
|
1185
1797
|
}
|
|
1798
|
+
finally {
|
|
1799
|
+
this.showBusy({ isBusy: false });
|
|
1800
|
+
}
|
|
1186
1801
|
}
|
|
1187
1802
|
async handleSSOClick(providerId) {
|
|
1188
|
-
const isLinked =
|
|
1803
|
+
const isLinked = !!this.ibGib ?
|
|
1804
|
+
isSsoProviderLinked({ keystone: this.ibGib, aggregatedDetails: this.aggregatedDetails, providerId }) :
|
|
1805
|
+
false;
|
|
1189
1806
|
if (isLinked) {
|
|
1190
1807
|
const caps = getKeystoneCapabilities({ keystone: this.ibGib, aggregatedDetails: this.aggregatedDetails });
|
|
1191
1808
|
if (!caps.isSovereign) {
|
|
@@ -1200,7 +1817,8 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
|
|
|
1200
1817
|
}
|
|
1201
1818
|
async handleSSOUnlink(providerId) {
|
|
1202
1819
|
const lc = `${this.lc}[${this.handleSSOUnlink.name}]`;
|
|
1203
|
-
|
|
1820
|
+
const providerName = providerId === 'google' ? 'Google' : providerId === 'github' ? 'GitHub' : providerId;
|
|
1821
|
+
this.setSSOStatus(`Unlinking ${providerName}...`, "info");
|
|
1204
1822
|
try {
|
|
1205
1823
|
// 1. Prompt user for password
|
|
1206
1824
|
const keystoneService = new KeystoneService_V1();
|
|
@@ -1211,11 +1829,12 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
|
|
|
1211
1829
|
while (tries < maxTries && !isCorrect) {
|
|
1212
1830
|
try {
|
|
1213
1831
|
const promptMsg = tries === 0
|
|
1214
|
-
? `Are you sure you want to unlink your ${
|
|
1832
|
+
? `Are you sure you want to unlink your ${providerName} account? If so, enter your local Master Secret (Password) or touch security key to authorize. Otherwise, click Cancel.`
|
|
1215
1833
|
: `Incorrect password. Please try again (${maxTries - tries} tries remaining):`;
|
|
1216
1834
|
masterSecret = await promptForSecret({
|
|
1217
1835
|
msg: promptMsg,
|
|
1218
|
-
confirm: false
|
|
1836
|
+
confirm: false,
|
|
1837
|
+
passkeys: this.aggregatedDetails?.passkeys,
|
|
1219
1838
|
});
|
|
1220
1839
|
}
|
|
1221
1840
|
catch (error) {
|
|
@@ -1225,11 +1844,10 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
|
|
|
1225
1844
|
if (!masterSecret) {
|
|
1226
1845
|
break;
|
|
1227
1846
|
}
|
|
1228
|
-
// Verify secret using verifySigningSecret
|
|
1847
|
+
// Verify secret using verifySigningSecret
|
|
1229
1848
|
isCorrect = await keystoneService.verifySigningSecret({
|
|
1230
1849
|
keystoneIbGib: this.ibGib,
|
|
1231
|
-
signingSecret: masterSecret
|
|
1232
|
-
poolId: 'manage'
|
|
1850
|
+
signingSecret: masterSecret
|
|
1233
1851
|
});
|
|
1234
1852
|
if (!isCorrect) {
|
|
1235
1853
|
tries++;
|
|
@@ -1239,6 +1857,12 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
|
|
|
1239
1857
|
this.setSSOStatus("Unlink cancelled or failed (incorrect password).", "error");
|
|
1240
1858
|
return;
|
|
1241
1859
|
}
|
|
1860
|
+
this.showBusy({
|
|
1861
|
+
isBusy: true,
|
|
1862
|
+
title: 'Unlinking Provider...',
|
|
1863
|
+
msg: `Removing ${providerName} custodian pool and delegate keys...`,
|
|
1864
|
+
animationEmoji: '🔓'
|
|
1865
|
+
});
|
|
1242
1866
|
this.setSSOStatus("Evolving identity locally and removing custodian pool...", "info");
|
|
1243
1867
|
const metaspace = await getGlobalMetaspace_waitIfNeeded();
|
|
1244
1868
|
if (!metaspace)
|
|
@@ -1256,31 +1880,59 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
|
|
|
1256
1880
|
metaspace,
|
|
1257
1881
|
space
|
|
1258
1882
|
});
|
|
1259
|
-
|
|
1260
|
-
|
|
1883
|
+
const primaryTjpAddr = (this.ibGib ? getTjpAddr({ ibGib: this.ibGib }) : undefined)
|
|
1884
|
+
|| (this.ibGibAddr ? (() => {
|
|
1885
|
+
const { ib } = getIbAndGib({ ibGibAddr: this.ibGibAddr });
|
|
1886
|
+
const { tjpGib } = getGibInfo({ ibGibAddr: this.ibGibAddr });
|
|
1887
|
+
return tjpGib && ib ? `${ib}^${tjpGib}` : this.ibGibAddr;
|
|
1888
|
+
})() : undefined)
|
|
1889
|
+
|| getIbGibAddr({ ibGib: this.ibGib });
|
|
1890
|
+
// 3. Sync pool removal frame to server
|
|
1891
|
+
this.showBusy({
|
|
1892
|
+
isBusy: true,
|
|
1893
|
+
title: 'Syncing to Server...',
|
|
1894
|
+
msg: `Publishing ${providerName} pool removal to server...`,
|
|
1895
|
+
animationEmoji: '⚡'
|
|
1896
|
+
});
|
|
1897
|
+
this.setSSOStatus("Publishing evolved identity keystone to server...", "info");
|
|
1898
|
+
let syncRes = await getApiBridge().putEvolveKeystone({
|
|
1899
|
+
domainAddr: primaryTjpAddr,
|
|
1900
|
+
keystoneIbGib: evolvedParent,
|
|
1901
|
+
relatedKeystoneIbGibs: [] // No outward delegate timelines
|
|
1902
|
+
});
|
|
1903
|
+
if (!syncRes.success) {
|
|
1904
|
+
throw new Error(`Server synchronization rejected: ${syncRes.message}`);
|
|
1905
|
+
}
|
|
1906
|
+
// 4. Unregister any corresponding outward delegate for this provider if present
|
|
1907
|
+
const delegatesMap = evolvedParent.data?.delegates || this.ibGib?.data?.delegates;
|
|
1261
1908
|
if (delegatesMap) {
|
|
1262
1909
|
for (const [delegateTjpAddr, info] of Object.entries(delegatesMap)) {
|
|
1263
|
-
if (info
|
|
1910
|
+
if (isCustodialDelegate({ delegateInfo: info, providerId })) {
|
|
1264
1911
|
evolvedParent = await keystoneService.unregisterDelegate({
|
|
1265
1912
|
parentKeystone: evolvedParent,
|
|
1266
1913
|
delegateTjpAddr,
|
|
1267
1914
|
masterSecret: masterSecret,
|
|
1915
|
+
signingPoolId: 'manage',
|
|
1268
1916
|
metaspace,
|
|
1269
1917
|
space,
|
|
1270
1918
|
});
|
|
1919
|
+
this.showBusy({
|
|
1920
|
+
isBusy: true,
|
|
1921
|
+
title: 'Syncing to Server...',
|
|
1922
|
+
msg: `Publishing ${providerName} delegate unregistration to server...`,
|
|
1923
|
+
animationEmoji: '⚡'
|
|
1924
|
+
});
|
|
1925
|
+
syncRes = await getApiBridge().putEvolveKeystone({
|
|
1926
|
+
domainAddr: primaryTjpAddr,
|
|
1927
|
+
keystoneIbGib: evolvedParent,
|
|
1928
|
+
relatedKeystoneIbGibs: []
|
|
1929
|
+
});
|
|
1930
|
+
if (!syncRes.success) {
|
|
1931
|
+
throw new Error(`Server synchronization rejected: ${syncRes.message}`);
|
|
1932
|
+
}
|
|
1271
1933
|
}
|
|
1272
1934
|
}
|
|
1273
1935
|
}
|
|
1274
|
-
// 3. Sync evolved parent keystone back to server
|
|
1275
|
-
this.setSSOStatus("Publishing evolved identity keystone to server...", "info");
|
|
1276
|
-
const syncRes = await getApiBridge().putEvolveKeystone({
|
|
1277
|
-
domainAddr: getIbGibAddr({ ibGib: this.ibGib }),
|
|
1278
|
-
keystoneIbGib: evolvedParent,
|
|
1279
|
-
relatedKeystoneIbGibs: [] // No outward delegate timelines
|
|
1280
|
-
});
|
|
1281
|
-
if (!syncRes.success) {
|
|
1282
|
-
throw new Error(`Server synchronization rejected: ${syncRes.message}`);
|
|
1283
|
-
}
|
|
1284
1936
|
// 4. Update index locally
|
|
1285
1937
|
await updateSpecialIndex({
|
|
1286
1938
|
type: "keystones",
|
|
@@ -1293,12 +1945,17 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
|
|
|
1293
1945
|
metaspace,
|
|
1294
1946
|
space
|
|
1295
1947
|
});
|
|
1296
|
-
this.setSSOStatus(`Successfully unlinked ${
|
|
1297
|
-
|
|
1948
|
+
this.setSSOStatus(`Successfully unlinked ${providerName}!`, "success");
|
|
1949
|
+
this.ibGibAddr = getIbGibAddr({ ibGib: evolvedParent });
|
|
1950
|
+
this.staticIbGib = evolvedParent;
|
|
1951
|
+
await this.loadIbGib({ getLatest: false });
|
|
1298
1952
|
}
|
|
1299
1953
|
catch (error) {
|
|
1300
|
-
console.error(`${lc}
|
|
1301
|
-
this.setSSOStatus(`
|
|
1954
|
+
console.error(`${lc} ${extractErrorMsg(error)}`, error);
|
|
1955
|
+
this.setSSOStatus(`Failed to unlink ${providerName}: ${extractErrorMsg(error)}`, "error");
|
|
1956
|
+
}
|
|
1957
|
+
finally {
|
|
1958
|
+
this.showBusy({ isBusy: false });
|
|
1302
1959
|
}
|
|
1303
1960
|
}
|
|
1304
1961
|
setChangePasswordStatus(msg, type) {
|
|
@@ -1342,8 +1999,16 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
|
|
|
1342
1999
|
config: initialManagePool.config,
|
|
1343
2000
|
challenges: newChallenges,
|
|
1344
2001
|
};
|
|
1345
|
-
const primaryTjpAddr =
|
|
1346
|
-
|
|
2002
|
+
const primaryTjpAddr = (this.ibGib ? getTjpAddr({ ibGib: this.ibGib }) : undefined)
|
|
2003
|
+
|| (this.ibGibAddr ? (() => {
|
|
2004
|
+
const { ib } = getIbAndGib({ ibGibAddr: this.ibGibAddr });
|
|
2005
|
+
const { tjpGib } = getGibInfo({ ibGibAddr: this.ibGibAddr });
|
|
2006
|
+
return tjpGib && ib ? `${ib}^${tjpGib}` : this.ibGibAddr;
|
|
2007
|
+
})() : undefined)
|
|
2008
|
+
|| getIbGibAddr({ ibGib: this.ibGib });
|
|
2009
|
+
const custodianProvider = this.aggregatedDetails?.custodianProvider
|
|
2010
|
+
|| this.ibGib?.data?.frameDetails?.custodianProvider
|
|
2011
|
+
|| this.ibGib?.data?.custodianProvider;
|
|
1347
2012
|
let requestBody = {
|
|
1348
2013
|
primaryTjpAddr,
|
|
1349
2014
|
newManagePool,
|
|
@@ -1370,15 +2035,60 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
|
|
|
1370
2035
|
requestBody.redirectUri = resPopup.redirectUri;
|
|
1371
2036
|
}
|
|
1372
2037
|
else {
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
2038
|
+
// Email OTP flow
|
|
2039
|
+
const email = this.aggregatedDetails?.emails?.[0]
|
|
2040
|
+
|| this.ibGib?.data?.emails?.[0]
|
|
2041
|
+
|| this.ibGib?.data?.frameDetails?.emails?.[0];
|
|
2042
|
+
if (!email) {
|
|
2043
|
+
this.showBusy({ isBusy: false });
|
|
2044
|
+
this.setChangePasswordStatus("No email address found for this identity to send verification code.", "error");
|
|
2045
|
+
return;
|
|
2046
|
+
}
|
|
2047
|
+
const otpSection = this.elements?.sectionAddPasswordOtpEl;
|
|
2048
|
+
const otpInput = this.elements?.inputAddPasswordOtpEl;
|
|
2049
|
+
const isOtpVisible = otpSection && !otpSection.classList.contains('hidden');
|
|
2050
|
+
// Step 1: If OTP section is not visible yet, send code and show the OTP input inline in the card
|
|
2051
|
+
if (!isOtpVisible) {
|
|
2052
|
+
this.showBusy({
|
|
2053
|
+
isBusy: true,
|
|
2054
|
+
title: 'Sending Verification Code...',
|
|
2055
|
+
msg: `Sending 6-digit verification code to ${email}...`,
|
|
2056
|
+
animationEmoji: '📧'
|
|
2057
|
+
});
|
|
2058
|
+
this.setChangePasswordStatus(`Sending verification code to ${email}...`, "info");
|
|
2059
|
+
const resCode = await getApiBridge().postVerificationCode({
|
|
2060
|
+
email,
|
|
2061
|
+
primaryTjpAddr,
|
|
2062
|
+
});
|
|
2063
|
+
this.showBusy({ isBusy: false });
|
|
2064
|
+
if (!resCode.success) {
|
|
2065
|
+
this.setChangePasswordStatus(resCode.message || "Failed to send verification code.", "error");
|
|
2066
|
+
return;
|
|
2067
|
+
}
|
|
2068
|
+
if (otpSection) {
|
|
2069
|
+
otpSection.classList.remove('hidden');
|
|
2070
|
+
}
|
|
2071
|
+
if (this.elements?.descAddPasswordOtpEl) {
|
|
2072
|
+
this.elements.descAddPasswordOtpEl.textContent = `A 6-digit verification code was sent to ${email}. Enter it below to establish sovereign ownership:`;
|
|
2073
|
+
}
|
|
2074
|
+
if (this.elements?.btnChangePasswordEl) {
|
|
2075
|
+
this.elements.btnChangePasswordEl.textContent = "Verify Code & Add Master Password";
|
|
2076
|
+
}
|
|
2077
|
+
this.setChangePasswordStatus(`✉️ 6-digit verification code sent to ${email}. Enter the code and click "Verify Code & Add Master Password".`, "info");
|
|
2078
|
+
this.startResendCooldownTimer(30);
|
|
2079
|
+
if (otpInput) {
|
|
2080
|
+
otpInput.value = '';
|
|
2081
|
+
otpInput.focus();
|
|
2082
|
+
}
|
|
2083
|
+
return;
|
|
2084
|
+
}
|
|
2085
|
+
// Step 2: OTP section is visible - validate entered code
|
|
2086
|
+
const otpCode = (otpInput?.value || '').trim();
|
|
2087
|
+
if (!otpCode || otpCode.length < 6) {
|
|
2088
|
+
this.setChangePasswordStatus("Please enter the complete 6-digit verification code sent to your email.", "error");
|
|
2089
|
+
if (otpInput) {
|
|
2090
|
+
otpInput.focus();
|
|
2091
|
+
}
|
|
1382
2092
|
return;
|
|
1383
2093
|
}
|
|
1384
2094
|
requestBody.email = email;
|
|
@@ -1410,20 +2120,88 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
|
|
|
1410
2120
|
if (!space) {
|
|
1411
2121
|
throw new Error("Local user space not available");
|
|
1412
2122
|
}
|
|
1413
|
-
|
|
1414
|
-
|
|
2123
|
+
if (resJson.primaryGraph) {
|
|
2124
|
+
const graphIbGibs = Object.values(resJson.primaryGraph);
|
|
2125
|
+
await metaspace.put({ ibGibs: graphIbGibs, space });
|
|
2126
|
+
const timelinesMap = getTimelinesGroupedByTjp({ ibGibs: graphIbGibs });
|
|
2127
|
+
for (const tjpKey of Object.keys(timelinesMap)) {
|
|
2128
|
+
for (const frame of timelinesMap[tjpKey]) {
|
|
2129
|
+
await metaspace.registerNewIbGib({ ibGib: frame, space });
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
else {
|
|
2134
|
+
await metaspace.put({ ibGibs: [evolvedParent], space });
|
|
2135
|
+
await metaspace.registerNewIbGib({ ibGib: evolvedParent, space });
|
|
2136
|
+
}
|
|
2137
|
+
// 1. Create a password-based sync delegate for this device
|
|
2138
|
+
this.showBusy({
|
|
2139
|
+
isBusy: true,
|
|
2140
|
+
title: 'Creating Sovereign Sync Delegate...',
|
|
2141
|
+
msg: 'Deriving local device sync delegate keys from Master Password...',
|
|
2142
|
+
animationEmoji: '🔑'
|
|
2143
|
+
});
|
|
2144
|
+
const rawUsername = this.aggregatedDetails?.username
|
|
2145
|
+
|| this.ibGib?.data?.username
|
|
2146
|
+
|| this.aggregatedDetails?.emails?.[0]?.split('@')[0]
|
|
2147
|
+
|| this.ibGib?.data?.emails?.[0]?.split('@')[0]
|
|
2148
|
+
|| 'user';
|
|
2149
|
+
const { delegateKeystone: userSyncDelegate, deviceTag } = await createSovereignDeviceSyncDelegate({
|
|
2150
|
+
username: rawUsername,
|
|
2151
|
+
primaryTjpAddr,
|
|
2152
|
+
masterSecret: newPassword,
|
|
2153
|
+
metaspace,
|
|
2154
|
+
space,
|
|
2155
|
+
});
|
|
2156
|
+
// 2. Register the new user sync delegate on the evolved parent keystone
|
|
2157
|
+
this.showBusy({
|
|
2158
|
+
isBusy: true,
|
|
2159
|
+
title: 'Registering Sovereign Delegate...',
|
|
2160
|
+
msg: 'Registering user sync delegate on primary keystone...',
|
|
2161
|
+
animationEmoji: '📝'
|
|
2162
|
+
});
|
|
2163
|
+
const keystoneService = new KeystoneService_V1();
|
|
2164
|
+
const parentWithUserDelegate = await keystoneService.registerDelegate({
|
|
2165
|
+
parentKeystone: evolvedParent,
|
|
2166
|
+
delegateKeystone: userSyncDelegate,
|
|
2167
|
+
masterSecret: newPassword,
|
|
2168
|
+
signingPoolId: 'manage',
|
|
2169
|
+
metaspace,
|
|
2170
|
+
space,
|
|
2171
|
+
allowedVerbs: [KEYSTONE_VERB_SYNC, KEYSTONE_VERB_CONNECT],
|
|
2172
|
+
deviceName: deviceTag
|
|
2173
|
+
});
|
|
2174
|
+
// 3. Post evolved keystone with new delegate to server
|
|
2175
|
+
this.showBusy({
|
|
2176
|
+
isBusy: true,
|
|
2177
|
+
title: 'Syncing to Server...',
|
|
2178
|
+
msg: 'Publishing updated domain identity and sovereign delegate to server...',
|
|
2179
|
+
animationEmoji: '⚡'
|
|
2180
|
+
});
|
|
2181
|
+
const resSync = await getApiBridge().putEvolveKeystone({
|
|
2182
|
+
domainAddr: primaryTjpAddr,
|
|
2183
|
+
keystoneIbGib: parentWithUserDelegate,
|
|
2184
|
+
relatedKeystoneIbGibs: [userSyncDelegate]
|
|
2185
|
+
});
|
|
2186
|
+
if (!resSync.success) {
|
|
2187
|
+
console.warn(`${lc} Warning syncing user delegate to server: ${resSync.message}`);
|
|
2188
|
+
}
|
|
2189
|
+
// 4. Save parentWithUserDelegate and userSyncDelegate to local space & metaspace
|
|
2190
|
+
await metaspace.put({ ibGibs: [parentWithUserDelegate, userSyncDelegate], space });
|
|
2191
|
+
await metaspace.registerNewIbGib({ ibGib: parentWithUserDelegate, space });
|
|
2192
|
+
await metaspace.registerNewIbGib({ ibGib: userSyncDelegate, space });
|
|
1415
2193
|
await updateSpecialIndex({
|
|
1416
2194
|
type: "keystones",
|
|
1417
2195
|
rel8nInfos: [
|
|
1418
2196
|
{
|
|
1419
2197
|
rel8nName: "keystone",
|
|
1420
|
-
ibGibs: [toDto({ ibGib:
|
|
2198
|
+
ibGibs: [toDto({ ibGib: parentWithUserDelegate }), toDto({ ibGib: userSyncDelegate })]
|
|
1421
2199
|
}
|
|
1422
2200
|
],
|
|
1423
2201
|
metaspace,
|
|
1424
2202
|
space
|
|
1425
2203
|
});
|
|
1426
|
-
const evolvedAddr = getIbGibAddr({ ibGib:
|
|
2204
|
+
const evolvedAddr = getIbGibAddr({ ibGib: parentWithUserDelegate });
|
|
1427
2205
|
// Dispatch EVENT_IBGIB_IDENTITY_CHANGED
|
|
1428
2206
|
window.dispatchEvent(new CustomEvent(EVENT_IBGIB_IDENTITY_CHANGED, {
|
|
1429
2207
|
detail: { activeIdentityAddr: evolvedAddr },
|
|
@@ -1431,9 +2209,19 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
|
|
|
1431
2209
|
composed: true
|
|
1432
2210
|
}));
|
|
1433
2211
|
this.setChangePasswordStatus("Successfully added Master Password! Identity is now sovereign.", "success");
|
|
2212
|
+
// Reset OTP section and button text
|
|
2213
|
+
if (this.elements?.sectionAddPasswordOtpEl) {
|
|
2214
|
+
this.elements.sectionAddPasswordOtpEl.classList.add('hidden');
|
|
2215
|
+
if (this.elements.inputAddPasswordOtpEl) {
|
|
2216
|
+
this.elements.inputAddPasswordOtpEl.value = '';
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
if (this.elements?.btnChangePasswordEl) {
|
|
2220
|
+
this.elements.btnChangePasswordEl.textContent = "Update Password";
|
|
2221
|
+
}
|
|
1434
2222
|
// Refresh the view
|
|
1435
2223
|
this.ibGibAddr = evolvedAddr;
|
|
1436
|
-
this.staticIbGib =
|
|
2224
|
+
this.staticIbGib = parentWithUserDelegate;
|
|
1437
2225
|
await this.loadIbGib({ getLatest: true });
|
|
1438
2226
|
// Clear inputs
|
|
1439
2227
|
this.elements.inputNewPasswordEl.value = '';
|
|
@@ -1442,20 +2230,80 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
|
|
|
1442
2230
|
catch (error) {
|
|
1443
2231
|
console.error(`${lc} Elevation failed: ${extractErrorMsg(error)}`);
|
|
1444
2232
|
this.setChangePasswordStatus(`Failed to add master password: ${extractErrorMsg(error)}`, "error");
|
|
2233
|
+
if (this.elements?.inputAddPasswordOtpEl && !this.elements.sectionAddPasswordOtpEl?.classList.contains('hidden')) {
|
|
2234
|
+
this.elements.inputAddPasswordOtpEl.select();
|
|
2235
|
+
this.elements.inputAddPasswordOtpEl.focus();
|
|
2236
|
+
}
|
|
1445
2237
|
}
|
|
1446
2238
|
finally {
|
|
1447
2239
|
this.showBusy({ isBusy: false });
|
|
1448
2240
|
}
|
|
1449
2241
|
}
|
|
2242
|
+
async handleResendAddPasswordOtp() {
|
|
2243
|
+
const lc = `${this.lc}[${this.handleResendAddPasswordOtp.name}]`;
|
|
2244
|
+
const email = this.aggregatedDetails?.emails?.[0]
|
|
2245
|
+
|| this.ibGib?.data?.emails?.[0]
|
|
2246
|
+
|| this.ibGib?.data?.frameDetails?.emails?.[0];
|
|
2247
|
+
if (!email) {
|
|
2248
|
+
this.setChangePasswordStatus("No email address found for this identity to send verification code.", "error");
|
|
2249
|
+
return;
|
|
2250
|
+
}
|
|
2251
|
+
const primaryTjpAddr = (this.ibGib ? getTjpAddr({ ibGib: this.ibGib }) : undefined)
|
|
2252
|
+
|| (this.ibGibAddr ? (() => {
|
|
2253
|
+
const { ib } = getIbAndGib({ ibGibAddr: this.ibGibAddr });
|
|
2254
|
+
const { tjpGib } = getGibInfo({ ibGibAddr: this.ibGibAddr });
|
|
2255
|
+
return tjpGib && ib ? `${ib}^${tjpGib}` : this.ibGibAddr;
|
|
2256
|
+
})() : undefined)
|
|
2257
|
+
|| getIbGibAddr({ ibGib: this.ibGib });
|
|
2258
|
+
const btn = this.elements.btnResendAddPasswordOtpEl;
|
|
2259
|
+
if (btn) {
|
|
2260
|
+
btn.disabled = true;
|
|
2261
|
+
btn.textContent = "Sending...";
|
|
2262
|
+
}
|
|
2263
|
+
this.setChangePasswordStatus(`Resending verification code to ${email}...`, "info");
|
|
2264
|
+
try {
|
|
2265
|
+
const resCode = await getApiBridge().postVerificationCode({ email, primaryTjpAddr });
|
|
2266
|
+
if (resCode.success) {
|
|
2267
|
+
this.setChangePasswordStatus(`✉️ Fresh 6-digit verification code sent to ${email}!`, "success");
|
|
2268
|
+
this.startResendCooldownTimer(30);
|
|
2269
|
+
if (this.elements?.inputAddPasswordOtpEl) {
|
|
2270
|
+
this.elements.inputAddPasswordOtpEl.value = '';
|
|
2271
|
+
this.elements.inputAddPasswordOtpEl.focus();
|
|
2272
|
+
}
|
|
2273
|
+
}
|
|
2274
|
+
else {
|
|
2275
|
+
this.setChangePasswordStatus(resCode.message || "Failed to resend verification code.", "error");
|
|
2276
|
+
if (btn) {
|
|
2277
|
+
btn.disabled = false;
|
|
2278
|
+
btn.textContent = "Resend Code";
|
|
2279
|
+
}
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
catch (err) {
|
|
2283
|
+
this.setChangePasswordStatus(`Error resending verification code: ${extractErrorMsg(err)}`, "error");
|
|
2284
|
+
if (btn) {
|
|
2285
|
+
btn.disabled = false;
|
|
2286
|
+
btn.textContent = "Resend Code";
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
1450
2290
|
async handleChangePassword() {
|
|
1451
2291
|
const lc = `${this.lc}[${this.handleChangePassword.name}]`;
|
|
1452
|
-
const caps =
|
|
1453
|
-
|
|
2292
|
+
const caps = !!this.ibGib ?
|
|
2293
|
+
getKeystoneCapabilities({ keystone: this.ibGib, aggregatedDetails: this.aggregatedDetails }) :
|
|
2294
|
+
undefined;
|
|
2295
|
+
const isCustodialOnly = !!caps ? caps.isCustodial && !caps.isSovereign : false;
|
|
1454
2296
|
if (isCustodialOnly) {
|
|
1455
2297
|
await this.handleAddMasterPassword();
|
|
1456
2298
|
return;
|
|
1457
2299
|
}
|
|
1458
2300
|
this.setChangePasswordStatus("Validating entries...", "info");
|
|
2301
|
+
this.showBusy({
|
|
2302
|
+
isBusy: true,
|
|
2303
|
+
title: 'Changing Master Password...',
|
|
2304
|
+
msg: 'Validating current password...',
|
|
2305
|
+
animationEmoji: '🔐'
|
|
2306
|
+
});
|
|
1459
2307
|
try {
|
|
1460
2308
|
const currentPassword = this.elements.inputCurrentPasswordEl.value;
|
|
1461
2309
|
const newPassword = this.elements.inputNewPasswordEl.value;
|
|
@@ -1483,6 +2331,12 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
|
|
|
1483
2331
|
this.setChangePasswordStatus("Incorrect current password.", "error");
|
|
1484
2332
|
return;
|
|
1485
2333
|
}
|
|
2334
|
+
this.showBusy({
|
|
2335
|
+
isBusy: true,
|
|
2336
|
+
title: 'Evolving Master Password...',
|
|
2337
|
+
msg: 'Generating new challenge proofs and rotating local user delegates...',
|
|
2338
|
+
animationEmoji: '⚙️'
|
|
2339
|
+
});
|
|
1486
2340
|
this.setChangePasswordStatus("Evolving primary identity keystone...", "info");
|
|
1487
2341
|
const metaspace = await getGlobalMetaspace_waitIfNeeded();
|
|
1488
2342
|
if (!metaspace) {
|
|
@@ -1492,8 +2346,7 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
|
|
|
1492
2346
|
if (!space) {
|
|
1493
2347
|
throw new Error(`(UNEXPECTED) couldn't get default local space? (E: 2fc1281c28351fd378a1d7f88eaa0526)`);
|
|
1494
2348
|
}
|
|
1495
|
-
// 2. Rotate parent identity
|
|
1496
|
-
debugger; // walk through this...looking for delegate failure // hmm...I thought we got change password going?
|
|
2349
|
+
// 2. Rotate parent identity and sovereign sync delegates
|
|
1497
2350
|
const { newKeystone: evolvedParent, newDelegates: evolvedDelegates } = await keystoneService.changePassword({
|
|
1498
2351
|
keystoneIbGib: this.ibGib,
|
|
1499
2352
|
newMasterSecret: newPassword,
|
|
@@ -1502,56 +2355,21 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
|
|
|
1502
2355
|
metaspace,
|
|
1503
2356
|
space
|
|
1504
2357
|
});
|
|
1505
|
-
|
|
1506
|
-
this.
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
// const delegateKeystone = await keystoneService.getLatestKeystone({
|
|
1513
|
-
// addr: delegateAddr,
|
|
1514
|
-
// metaspace,
|
|
1515
|
-
// space,
|
|
1516
|
-
// });
|
|
1517
|
-
// // Identify user-owned challenge pools in the delegate that match the old password
|
|
1518
|
-
// const poolsToRotate: string[] = [];
|
|
1519
|
-
// for (const pool of delegateKeystone.data?.challengePools ?? []) {
|
|
1520
|
-
// if (!pool.isForeign) {
|
|
1521
|
-
// /**
|
|
1522
|
-
// * WARNING: There is an extreme edge case that if two pools
|
|
1523
|
-
// * happen to be owned by different parties but share the
|
|
1524
|
-
// * same secret, this will change the wrong pool's secret
|
|
1525
|
-
// * also. For V1, this shouldn't matter. But we need a
|
|
1526
|
-
// * better way of tracking pool ownership for V2 (or even
|
|
1527
|
-
// * V1 if we have multi-party support). (DO NOT REMOVE
|
|
1528
|
-
// * THIS COMMENT UNTIL THIS IS ADDRESSED)
|
|
1529
|
-
// */
|
|
1530
|
-
// const isPoolMatch = await keystoneService.verifySigningSecret({
|
|
1531
|
-
// keystoneIbGib: delegateKeystone,
|
|
1532
|
-
// signingSecret: currentPassword,
|
|
1533
|
-
// poolId: pool.id
|
|
1534
|
-
// });
|
|
1535
|
-
// if (isPoolMatch) { poolsToRotate.push(pool.id); }
|
|
1536
|
-
// }
|
|
1537
|
-
// }
|
|
1538
|
-
// if (poolsToRotate.length > 0) {
|
|
1539
|
-
// console.log(`${lc} Rotating pools in delegate ${delegateInfo.delegateTjpAddr}: ${poolsToRotate.join(', ')}`);
|
|
1540
|
-
// const {newKeystone: evolvedDelegate} = await keystoneService.changePassword({
|
|
1541
|
-
// keystoneIbGib: delegateKeystone,
|
|
1542
|
-
// newMasterSecret: newPassword,
|
|
1543
|
-
// signingSecret: currentPassword,
|
|
1544
|
-
// signingPoolId: 'manage',
|
|
1545
|
-
// poolIds: poolsToRotate,
|
|
1546
|
-
// metaspace,
|
|
1547
|
-
// space
|
|
1548
|
-
// });
|
|
1549
|
-
// evolvedDelegates.push(evolvedDelegate);
|
|
1550
|
-
// }
|
|
1551
|
-
// }
|
|
1552
|
-
// 4. Sync evolved parent and all evolved delegate keystones atomically
|
|
2358
|
+
// 3. Sync evolved parent and all evolved delegate keystones atomically
|
|
2359
|
+
this.showBusy({
|
|
2360
|
+
isBusy: true,
|
|
2361
|
+
title: 'Syncing to Server...',
|
|
2362
|
+
msg: 'Publishing rotated identity and delegates to server...',
|
|
2363
|
+
animationEmoji: '⚡'
|
|
2364
|
+
});
|
|
1553
2365
|
this.setChangePasswordStatus("Syncing evolved keystones to server...", "info");
|
|
1554
|
-
const parentTjpAddr =
|
|
2366
|
+
const parentTjpAddr = (this.ibGib ? getTjpAddr({ ibGib: this.ibGib }) : undefined)
|
|
2367
|
+
|| (this.ibGibAddr ? (() => {
|
|
2368
|
+
const { ib } = getIbAndGib({ ibGibAddr: this.ibGibAddr });
|
|
2369
|
+
const { tjpGib } = getGibInfo({ ibGibAddr: this.ibGibAddr });
|
|
2370
|
+
return tjpGib && ib ? `${ib}^${tjpGib}` : this.ibGibAddr;
|
|
2371
|
+
})() : undefined)
|
|
2372
|
+
|| getIbGibAddr({ ibGib: this.ibGib });
|
|
1555
2373
|
const syncRes = await getApiBridge().putEvolveKeystone({
|
|
1556
2374
|
domainAddr: parentTjpAddr,
|
|
1557
2375
|
keystoneIbGib: evolvedParent,
|
|
@@ -1560,7 +2378,7 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
|
|
|
1560
2378
|
if (!syncRes.success) {
|
|
1561
2379
|
throw new Error(`Server synchronization rejected: ${syncRes.message}`);
|
|
1562
2380
|
}
|
|
1563
|
-
//
|
|
2381
|
+
// 4. Update index locally
|
|
1564
2382
|
this.setChangePasswordStatus("Updating local indexes...", "info");
|
|
1565
2383
|
const newKeystoneIbGibs = [toDto({ ibGib: evolvedParent })];
|
|
1566
2384
|
if (evolvedDelegates) {
|
|
@@ -1585,12 +2403,18 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
|
|
|
1585
2403
|
this.elements.inputNewPasswordEl.value = "";
|
|
1586
2404
|
this.elements.inputConfirmPasswordEl.value = "";
|
|
1587
2405
|
this.setChangePasswordStatus("Password updated successfully!", "success");
|
|
2406
|
+
this.ibGibAddr = getIbGibAddr({ ibGib: evolvedParent });
|
|
2407
|
+
this.staticIbGib = evolvedParent;
|
|
1588
2408
|
await this.loadIbGib({ getLatest: true });
|
|
1589
2409
|
}
|
|
1590
2410
|
catch (error) {
|
|
2411
|
+
debugger; // error in handleChangePassword
|
|
1591
2412
|
console.error(`${lc} Password rotation failed: ${extractErrorMsg(error)}`);
|
|
1592
2413
|
this.setChangePasswordStatus(`Update failed: ${extractErrorMsg(error)}`, "error");
|
|
1593
2414
|
}
|
|
2415
|
+
finally {
|
|
2416
|
+
this.showBusy({ isBusy: false });
|
|
2417
|
+
}
|
|
1594
2418
|
}
|
|
1595
2419
|
}
|
|
1596
2420
|
//# sourceMappingURL=keystone-details.mjs.map
|