@ibgib/web-gib 0.0.62 → 0.0.63
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 +17 -2
- package/dist/helpers.web.d.mts.map +1 -1
- package/dist/helpers.web.mjs +67 -2
- package/dist/helpers.web.mjs.map +1 -1
- package/dist/identity/passkey-helper.d.mts +7 -2
- package/dist/identity/passkey-helper.d.mts.map +1 -1
- package/dist/identity/passkey-helper.mjs +33 -12
- package/dist/identity/passkey-helper.mjs.map +1 -1
- package/dist/ui/component/identity/keystone-creator/keystone-creator.css +26 -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 +18 -1
- package/dist/ui/component/identity/keystone-creator/keystone-creator.mjs +10 -2
- package/dist/ui/component/identity/keystone-creator/keystone-creator.mjs.map +1 -1
- package/dist/ui/component/identity/keystone-details/keystone-details.d.mts +12 -0
- package/dist/ui/component/identity/keystone-details/keystone-details.d.mts.map +1 -1
- package/dist/ui/component/identity/keystone-details/keystone-details.html +8 -0
- package/dist/ui/component/identity/keystone-details/keystone-details.mjs +419 -305
- package/dist/ui/component/identity/keystone-details/keystone-details.mjs.map +1 -1
- package/dist/ui/ui-helpers.d.mts.map +1 -1
- package/dist/ui/ui-helpers.mjs +7 -2
- 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 +95 -5
- package/src/identity/passkey-helper.mts +43 -10
- package/src/ui/component/identity/keystone-creator/keystone-creator.css +26 -0
- package/src/ui/component/identity/keystone-creator/keystone-creator.html +18 -1
- package/src/ui/component/identity/keystone-creator/keystone-creator.mts +12 -2
- package/src/ui/component/identity/keystone-details/keystone-details.html +8 -0
- package/src/ui/component/identity/keystone-details/keystone-details.mts +474 -339
- package/src/ui/ui-helpers.mts +6 -2
- package/tools/auto-generated-agent-skills/skills/ibgib-create-app-server/SKILL.md +50 -0
- package/src/identity/custodian-delegate-helper.mts.bak +0 -7
- package/src/identity/sso/sso-config-helper.mts.bak +0 -52
- package/src/identity/sso/sso-custodian-service.mts.bak +0 -566
- package/src/identity/sso/sso-custodian-service.respec.mts.bak +0 -298
|
@@ -2,7 +2,7 @@ import thisCss from "./keystone-details.css";
|
|
|
2
2
|
import thisHtml from "./keystone-details.html";
|
|
3
3
|
import commonComponentStylesCss from "../../common-component-styles.css";
|
|
4
4
|
|
|
5
|
-
import { extractErrorMsg, pretty, getSaferSubstring, HashAlgorithm } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
|
|
5
|
+
import { extractErrorMsg, pretty, getSaferSubstring, HashAlgorithm, delay } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
|
|
6
6
|
import { IbGibAddr } from "@ibgib/ts-gib/dist/types.mjs";
|
|
7
7
|
import { IbGib_V1 } from "@ibgib/ts-gib/dist/V1/types.mjs";
|
|
8
8
|
import { getIbAndGib, getIbGibAddr } from "@ibgib/ts-gib/dist/helper.mjs";
|
|
@@ -24,7 +24,7 @@ import { getTjpAddr, toDto, getTimelinesGroupedByTjp } from "@ibgib/core-gib/dis
|
|
|
24
24
|
import { parseSpaceIb } from "@ibgib/core-gib/dist/witness/space/space-helper.mjs";
|
|
25
25
|
import { executeSsoOAuthPopup } from "../../../../identity/sso-popup-helper.mjs";
|
|
26
26
|
import { getComponentCtorArg } from "../../../../app-bootstrap/init-orchestration.mjs";
|
|
27
|
-
import { promptForSecret } from "../../../../helpers.web.mjs";
|
|
27
|
+
import { promptForSecret, promptForAuthoritySecret } from "../../../../helpers.web.mjs";
|
|
28
28
|
import { isSsoProviderLinked, getKeystoneCapabilities, isDelegateExpired, deriveDelegateSecret, createSyncDelegateKeystone, isCustodialDelegate } from "@ibgib/core-gib/dist/keystone/delegate/delegate-helpers.mjs";
|
|
29
29
|
import { generatePoolChallenges } from "@ibgib/core-gib/dist/keystone/keystone-helpers.mjs";
|
|
30
30
|
import { POOL_ID_MANAGE, KEYSTONE_VERB_SYNC, KEYSTONE_VERB_CONNECT, KEYSTONE_VERB_MANAGE } from "@ibgib/core-gib/dist/keystone/keystone-constants.mjs";
|
|
@@ -99,6 +99,7 @@ interface KeystoneDetailsElements extends ElementsBase {
|
|
|
99
99
|
passkeysCardEl: HTMLDivElement;
|
|
100
100
|
passkeysListEl: HTMLTableSectionElement;
|
|
101
101
|
btnAddPasskeyEl: HTMLButtonElement;
|
|
102
|
+
selectPasskeyAttachmentEl?: HTMLSelectElement;
|
|
102
103
|
passkeyStatusMsgEl: HTMLDivElement;
|
|
103
104
|
historicalWarningBannerEl: HTMLDivElement;
|
|
104
105
|
containerEl: HTMLDivElement;
|
|
@@ -260,6 +261,7 @@ export class KeystoneDetailsComponentInstance
|
|
|
260
261
|
this.elements.passkeysCardEl = shadow.getElementById('passkeys-card') as HTMLDivElement;
|
|
261
262
|
this.elements.passkeysListEl = shadow.getElementById('passkeys-list') as HTMLTableSectionElement;
|
|
262
263
|
this.elements.btnAddPasskeyEl = shadow.getElementById('btn-add-passkey') as HTMLButtonElement;
|
|
264
|
+
this.elements.selectPasskeyAttachmentEl = shadow.getElementById('select-passkey-attachment') as HTMLSelectElement;
|
|
263
265
|
this.elements.passkeyStatusMsgEl = shadow.getElementById('passkey-status-msg') as HTMLDivElement;
|
|
264
266
|
this.elements.containerEl = shadow.getElementById('container') as HTMLDivElement;
|
|
265
267
|
this.elements.historicalWarningBannerEl = shadow.getElementById('historical-warning-banner') as HTMLDivElement;
|
|
@@ -577,18 +579,16 @@ export class KeystoneDetailsComponentInstance
|
|
|
577
579
|
private updatePasswordCardView(): void {
|
|
578
580
|
const caps = !!this.ibGib ?
|
|
579
581
|
getKeystoneCapabilities({ keystone: this.ibGib, aggregatedDetails: this.aggregatedDetails }) :
|
|
580
|
-
|
|
582
|
+
undefined;
|
|
581
583
|
const cardTitleEl = this.elements?.changePasswordCardEl?.querySelector('h3');
|
|
582
584
|
const cardDescEl = this.elements?.changePasswordCardEl?.querySelector('.description');
|
|
583
585
|
const currentPasswordGroup = this.elements?.inputCurrentPasswordEl?.closest('.form-group') as HTMLElement | null;
|
|
584
586
|
const btnChangePassword = this.elements?.btnChangePasswordEl;
|
|
585
587
|
|
|
586
|
-
const
|
|
587
|
-
|
|
588
|
-
false;
|
|
589
|
-
if (isCustodialOnly) {
|
|
588
|
+
const hasPassword = !!caps?.hasPassword;
|
|
589
|
+
if (!hasPassword) {
|
|
590
590
|
if (cardTitleEl) cardTitleEl.textContent = "Add Master Password";
|
|
591
|
-
if (cardDescEl) cardDescEl.textContent = "Establish
|
|
591
|
+
if (cardDescEl) cardDescEl.textContent = "Establish a master password for this identity. This enables sovereign password authentication alongside your passkeys and linked providers.";
|
|
592
592
|
if (currentPasswordGroup) currentPasswordGroup.style.display = 'none';
|
|
593
593
|
if (btnChangePassword) btnChangePassword.textContent = "Add Master Password";
|
|
594
594
|
} else {
|
|
@@ -1091,12 +1091,17 @@ export class KeystoneDetailsComponentInstance
|
|
|
1091
1091
|
const lc = `${this.lc}[${this.handleRevokeDelegate.name}]`;
|
|
1092
1092
|
try {
|
|
1093
1093
|
let masterSecret: string;
|
|
1094
|
+
let matchedPoolId: string;
|
|
1094
1095
|
try {
|
|
1095
|
-
|
|
1096
|
+
const authResult = await promptForAuthoritySecret({
|
|
1097
|
+
keystone: this.ibGib,
|
|
1098
|
+
aggregatedDetails: this.aggregatedDetails,
|
|
1099
|
+
title: 'Revoke Delegate',
|
|
1096
1100
|
msg: `Enter Master Password or touch security key to revoke delegate '${deviceName}':`,
|
|
1097
|
-
|
|
1098
|
-
passkeys: this.aggregatedDetails?.passkeys,
|
|
1101
|
+
verb: KEYSTONE_VERB_MANAGE
|
|
1099
1102
|
});
|
|
1103
|
+
masterSecret = authResult.secret;
|
|
1104
|
+
matchedPoolId = authResult.poolId;
|
|
1100
1105
|
} catch (err) {
|
|
1101
1106
|
// User cancelled prompt
|
|
1102
1107
|
return;
|
|
@@ -1112,29 +1117,6 @@ export class KeystoneDetailsComponentInstance
|
|
|
1112
1117
|
|
|
1113
1118
|
const keystoneSvc = new KeystoneService_V1();
|
|
1114
1119
|
|
|
1115
|
-
// Resolve which manage pool matches the provided masterSecret
|
|
1116
|
-
let matchedPoolId: string | undefined;
|
|
1117
|
-
const candidatePools = this.ibGib!.data?.challengePools.filter(p =>
|
|
1118
|
-
p.id === 'manage' || p.id.startsWith('passkey-manage-') || (p.config.allowedVerbs && p.config.allowedVerbs.includes(KEYSTONE_VERB_MANAGE))
|
|
1119
|
-
) || [];
|
|
1120
|
-
|
|
1121
|
-
for (const pool of candidatePools) {
|
|
1122
|
-
const ok = await keystoneSvc.verifySigningSecret({
|
|
1123
|
-
keystoneIbGib: this.ibGib!,
|
|
1124
|
-
signingSecret: masterSecret,
|
|
1125
|
-
poolId: pool.id
|
|
1126
|
-
});
|
|
1127
|
-
if (ok) {
|
|
1128
|
-
matchedPoolId = pool.id;
|
|
1129
|
-
break;
|
|
1130
|
-
}
|
|
1131
|
-
}
|
|
1132
|
-
|
|
1133
|
-
if (!matchedPoolId) {
|
|
1134
|
-
this.setSyncStatus("Failed to authorize revocation (invalid secret/passkey).", "error");
|
|
1135
|
-
return;
|
|
1136
|
-
}
|
|
1137
|
-
|
|
1138
1120
|
// Unregister delegate from parent keystone
|
|
1139
1121
|
const evolvedParent = await keystoneSvc.unregisterDelegate({
|
|
1140
1122
|
parentKeystone: this.ibGib!,
|
|
@@ -1299,10 +1281,18 @@ export class KeystoneDetailsComponentInstance
|
|
|
1299
1281
|
const rawSalt = crypto.getRandomValues(new Uint8Array(32));
|
|
1300
1282
|
const saltHex = bufferToHex(rawSalt.buffer as ArrayBuffer);
|
|
1301
1283
|
|
|
1284
|
+
const attachmentVal = this.elements?.selectPasskeyAttachmentEl?.value;
|
|
1285
|
+
const authenticatorAttachment: AuthenticatorAttachment | undefined =
|
|
1286
|
+
(attachmentVal === 'cross-platform' || attachmentVal === 'platform')
|
|
1287
|
+
? attachmentVal
|
|
1288
|
+
: undefined;
|
|
1289
|
+
|
|
1302
1290
|
const regResult = await registerPasskeyCredential({
|
|
1303
1291
|
username,
|
|
1304
1292
|
displayName: username,
|
|
1305
|
-
salt: saltHex
|
|
1293
|
+
salt: saltHex,
|
|
1294
|
+
userVerification: 'preferred',
|
|
1295
|
+
authenticatorAttachment,
|
|
1306
1296
|
});
|
|
1307
1297
|
const { credentialId, transports } = regResult;
|
|
1308
1298
|
|
|
@@ -1311,7 +1301,8 @@ export class KeystoneDetailsComponentInstance
|
|
|
1311
1301
|
const prfResult = await evaluatePasskeyPrf({
|
|
1312
1302
|
credentialId,
|
|
1313
1303
|
salt: saltHex,
|
|
1314
|
-
transports
|
|
1304
|
+
transports,
|
|
1305
|
+
userVerification: 'preferred'
|
|
1315
1306
|
});
|
|
1316
1307
|
passkeySecret = prfResult.passkeySecret;
|
|
1317
1308
|
}
|
|
@@ -1338,6 +1329,36 @@ export class KeystoneDetailsComponentInstance
|
|
|
1338
1329
|
const keystoneService = new KeystoneService_V1();
|
|
1339
1330
|
let evolvedKeystone: KeystoneIbGib_V1;
|
|
1340
1331
|
|
|
1332
|
+
const poolConfig: KeystonePoolConfig_HashV1 = {
|
|
1333
|
+
id: poolId,
|
|
1334
|
+
type: KeystoneChallengeType.hash_reveal_v1,
|
|
1335
|
+
salt: poolId,
|
|
1336
|
+
algo: HashAlgorithm.sha_256,
|
|
1337
|
+
rounds: 1,
|
|
1338
|
+
allowedVerbs: [KEYSTONE_VERB_MANAGE, KEYSTONE_VERB_SYNC, KEYSTONE_VERB_CONNECT],
|
|
1339
|
+
behavior: {
|
|
1340
|
+
size: 10,
|
|
1341
|
+
replenish: KeystoneReplenishStrategy.topUp,
|
|
1342
|
+
selectSequentially: 1,
|
|
1343
|
+
selectRandomly: 0,
|
|
1344
|
+
targetBindingCount: 0,
|
|
1345
|
+
}
|
|
1346
|
+
};
|
|
1347
|
+
|
|
1348
|
+
const challenges = await generatePoolChallenges({
|
|
1349
|
+
config: poolConfig,
|
|
1350
|
+
masterSecret: passkeySecret
|
|
1351
|
+
});
|
|
1352
|
+
|
|
1353
|
+
const newPool: KeystoneChallengePool = {
|
|
1354
|
+
id: poolId,
|
|
1355
|
+
config: poolConfig,
|
|
1356
|
+
challenges
|
|
1357
|
+
};
|
|
1358
|
+
|
|
1359
|
+
let authSecret: string | undefined;
|
|
1360
|
+
let signingPoolId: string | undefined;
|
|
1361
|
+
|
|
1341
1362
|
if (isCustodialOnly) {
|
|
1342
1363
|
// Elevate custodial account with sovereign passkey
|
|
1343
1364
|
this.showBusy({
|
|
@@ -1347,37 +1368,13 @@ export class KeystoneDetailsComponentInstance
|
|
|
1347
1368
|
animationEmoji: '⚡'
|
|
1348
1369
|
});
|
|
1349
1370
|
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
id: poolId,
|
|
1353
|
-
type: KeystoneChallengeType.hash_reveal_v1,
|
|
1354
|
-
salt: poolId,
|
|
1355
|
-
algo: HashAlgorithm.sha_256,
|
|
1356
|
-
rounds: 1,
|
|
1357
|
-
allowedVerbs: [KEYSTONE_VERB_MANAGE, KEYSTONE_VERB_SYNC, KEYSTONE_VERB_CONNECT],
|
|
1358
|
-
behavior: {
|
|
1359
|
-
size: 10,
|
|
1360
|
-
replenish: KeystoneReplenishStrategy.topUp,
|
|
1361
|
-
selectSequentially: 1,
|
|
1362
|
-
selectRandomly: 0,
|
|
1363
|
-
targetBindingCount: 0,
|
|
1364
|
-
}
|
|
1365
|
-
};
|
|
1366
|
-
|
|
1367
|
-
const challenges = await generatePoolChallenges({
|
|
1368
|
-
config: poolConfig,
|
|
1369
|
-
masterSecret: passkeySecret
|
|
1370
|
-
});
|
|
1371
|
-
|
|
1372
|
-
const newPool: KeystoneChallengePool = {
|
|
1373
|
-
id: poolId,
|
|
1374
|
-
config: poolConfig,
|
|
1375
|
-
challenges
|
|
1376
|
-
};
|
|
1371
|
+
authSecret = passkeySecret;
|
|
1372
|
+
signingPoolId = poolId;
|
|
1377
1373
|
|
|
1378
1374
|
evolvedKeystone = await keystoneService.addPools({
|
|
1379
1375
|
latestKeystone: this.ibGib,
|
|
1380
|
-
masterSecret:
|
|
1376
|
+
masterSecret: authSecret,
|
|
1377
|
+
signingPoolId,
|
|
1381
1378
|
newPools: [newPool],
|
|
1382
1379
|
frameDetails: {
|
|
1383
1380
|
passkeys: {
|
|
@@ -1392,39 +1389,18 @@ export class KeystoneDetailsComponentInstance
|
|
|
1392
1389
|
});
|
|
1393
1390
|
} else {
|
|
1394
1391
|
// Secondary passkey registration on existing sovereign identity:
|
|
1395
|
-
// Prompt user to authorize with current master password
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
const promptMsg = tries === 0
|
|
1404
|
-
? 'Enter your current Master Secret (Password) or touch security key to authorize adding this passkey:'
|
|
1405
|
-
: `Incorrect password. Please try again (${maxTries - tries} tries remaining):`;
|
|
1406
|
-
authSecret = await promptForSecret({
|
|
1407
|
-
msg: promptMsg,
|
|
1408
|
-
confirm: false,
|
|
1409
|
-
passkeys: this.aggregatedDetails?.passkeys,
|
|
1410
|
-
});
|
|
1411
|
-
} catch (err) {
|
|
1412
|
-
authSecret = undefined;
|
|
1413
|
-
break;
|
|
1414
|
-
}
|
|
1415
|
-
|
|
1416
|
-
if (!authSecret) break;
|
|
1417
|
-
|
|
1418
|
-
// Verify secret against manage/passkey pools
|
|
1419
|
-
isCorrect = await keystoneService.verifySigningSecret({
|
|
1420
|
-
keystoneIbGib: this.ibGib,
|
|
1421
|
-
signingSecret: authSecret
|
|
1392
|
+
// Prompt user to authorize with current master password or passkey
|
|
1393
|
+
try {
|
|
1394
|
+
const authResult = await promptForAuthoritySecret({
|
|
1395
|
+
keystone: this.ibGib,
|
|
1396
|
+
aggregatedDetails: this.aggregatedDetails,
|
|
1397
|
+
title: 'Authorize New Passkey',
|
|
1398
|
+
msg: 'Enter Master Password or touch security key to authorize adding this passkey:',
|
|
1399
|
+
verb: KEYSTONE_VERB_MANAGE
|
|
1422
1400
|
});
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
}
|
|
1426
|
-
|
|
1427
|
-
if (!isCorrect || !authSecret) {
|
|
1401
|
+
authSecret = authResult.secret;
|
|
1402
|
+
signingPoolId = authResult.poolId;
|
|
1403
|
+
} catch (err) {
|
|
1428
1404
|
this.setPasskeyStatus("Registration cancelled (authorization required).", "error");
|
|
1429
1405
|
return;
|
|
1430
1406
|
}
|
|
@@ -1436,36 +1412,10 @@ export class KeystoneDetailsComponentInstance
|
|
|
1436
1412
|
animationEmoji: '🛡️'
|
|
1437
1413
|
});
|
|
1438
1414
|
|
|
1439
|
-
const poolConfig: KeystonePoolConfig_HashV1 = {
|
|
1440
|
-
id: poolId,
|
|
1441
|
-
type: KeystoneChallengeType.hash_reveal_v1,
|
|
1442
|
-
salt: poolId,
|
|
1443
|
-
algo: HashAlgorithm.sha_256,
|
|
1444
|
-
rounds: 1,
|
|
1445
|
-
allowedVerbs: [KEYSTONE_VERB_MANAGE, KEYSTONE_VERB_SYNC, KEYSTONE_VERB_CONNECT],
|
|
1446
|
-
behavior: {
|
|
1447
|
-
size: 10,
|
|
1448
|
-
replenish: KeystoneReplenishStrategy.topUp,
|
|
1449
|
-
selectSequentially: 1,
|
|
1450
|
-
selectRandomly: 0,
|
|
1451
|
-
targetBindingCount: 0,
|
|
1452
|
-
}
|
|
1453
|
-
};
|
|
1454
|
-
|
|
1455
|
-
const challenges = await generatePoolChallenges({
|
|
1456
|
-
config: poolConfig,
|
|
1457
|
-
masterSecret: passkeySecret
|
|
1458
|
-
});
|
|
1459
|
-
|
|
1460
|
-
const newPool: KeystoneChallengePool = {
|
|
1461
|
-
id: poolId,
|
|
1462
|
-
config: poolConfig,
|
|
1463
|
-
challenges
|
|
1464
|
-
};
|
|
1465
|
-
|
|
1466
1415
|
evolvedKeystone = await keystoneService.addPools({
|
|
1467
1416
|
latestKeystone: this.ibGib,
|
|
1468
1417
|
masterSecret: authSecret,
|
|
1418
|
+
signingPoolId,
|
|
1469
1419
|
newPools: [newPool],
|
|
1470
1420
|
frameDetails: {
|
|
1471
1421
|
passkeys: {
|
|
@@ -1478,25 +1428,75 @@ export class KeystoneDetailsComponentInstance
|
|
|
1478
1428
|
metaspace,
|
|
1479
1429
|
space
|
|
1480
1430
|
});
|
|
1431
|
+
|
|
1432
|
+
// Sync pool addition frame to server
|
|
1433
|
+
const primaryTjpAddr = getTjpAddr({ ibGib: evolvedKeystone, defaultIfNone: 'incomingAddr' })!;
|
|
1434
|
+
const resSyncPool = await getApiBridge().putEvolveKeystone({
|
|
1435
|
+
domainAddr: primaryTjpAddr,
|
|
1436
|
+
keystoneIbGib: evolvedKeystone,
|
|
1437
|
+
relatedKeystoneIbGibs: []
|
|
1438
|
+
});
|
|
1439
|
+
if (!resSyncPool.success) {
|
|
1440
|
+
throw new Error(`Server synchronization of passkey pool failed: ${resSyncPool.message}`);
|
|
1441
|
+
}
|
|
1481
1442
|
}
|
|
1482
1443
|
|
|
1483
|
-
//
|
|
1444
|
+
// Construct outward device sync delegate for this passkey
|
|
1445
|
+
this.showBusy({
|
|
1446
|
+
isBusy: true,
|
|
1447
|
+
title: 'Creating Sync Delegate...',
|
|
1448
|
+
msg: 'Constructing outward passkey device sync delegate...',
|
|
1449
|
+
animationEmoji: '📱'
|
|
1450
|
+
});
|
|
1451
|
+
|
|
1452
|
+
const primaryTjpAddr = getTjpAddr({ ibGib: evolvedKeystone, defaultIfNone: 'incomingAddr' })!;
|
|
1453
|
+
const { delegateKeystone, deviceTag } = await createPasskeyDeviceSyncDelegate({
|
|
1454
|
+
username,
|
|
1455
|
+
primaryTjpAddr,
|
|
1456
|
+
passkeySecret,
|
|
1457
|
+
transports,
|
|
1458
|
+
metaspace,
|
|
1459
|
+
space
|
|
1460
|
+
});
|
|
1461
|
+
|
|
1462
|
+
// Register outward sync delegate on primary keystone
|
|
1463
|
+
this.showBusy({
|
|
1464
|
+
isBusy: true,
|
|
1465
|
+
title: 'Registering Delegate...',
|
|
1466
|
+
msg: 'Registering outward passkey delegate on primary identity...',
|
|
1467
|
+
animationEmoji: '📝'
|
|
1468
|
+
});
|
|
1469
|
+
|
|
1470
|
+
const delegateSigningPoolId = isCustodialOnly ? poolId : signingPoolId!;
|
|
1471
|
+
const delegateAuthSecret = isCustodialOnly ? passkeySecret : authSecret!;
|
|
1472
|
+
|
|
1473
|
+
evolvedKeystone = await keystoneService.registerDelegate({
|
|
1474
|
+
parentKeystone: evolvedKeystone,
|
|
1475
|
+
delegateKeystone,
|
|
1476
|
+
masterSecret: delegateAuthSecret,
|
|
1477
|
+
signingPoolId: delegateSigningPoolId,
|
|
1478
|
+
metaspace,
|
|
1479
|
+
space,
|
|
1480
|
+
allowedVerbs: [KEYSTONE_VERB_SYNC, KEYSTONE_VERB_CONNECT],
|
|
1481
|
+
deviceName: deviceTag
|
|
1482
|
+
});
|
|
1483
|
+
|
|
1484
|
+
// Sync evolved keystone and delegate to server
|
|
1484
1485
|
this.showBusy({
|
|
1485
1486
|
isBusy: true,
|
|
1486
1487
|
title: 'Syncing to Server...',
|
|
1487
|
-
msg: 'Publishing evolved identity keystone to server...',
|
|
1488
|
+
msg: 'Publishing evolved identity keystone and delegate to server...',
|
|
1488
1489
|
animationEmoji: '⚡'
|
|
1489
1490
|
});
|
|
1490
1491
|
|
|
1491
|
-
const primaryTjpAddr = getTjpAddr({ ibGib: evolvedKeystone, defaultIfNone: 'incomingAddr' })!;
|
|
1492
1492
|
const resSync = await getApiBridge().putEvolveKeystone({
|
|
1493
1493
|
domainAddr: primaryTjpAddr,
|
|
1494
1494
|
keystoneIbGib: evolvedKeystone,
|
|
1495
|
-
relatedKeystoneIbGibs: []
|
|
1495
|
+
relatedKeystoneIbGibs: [delegateKeystone]
|
|
1496
1496
|
});
|
|
1497
1497
|
|
|
1498
1498
|
if (!resSync.success) {
|
|
1499
|
-
|
|
1499
|
+
throw new Error(`Server synchronization of delegate failed: ${resSync.message}`);
|
|
1500
1500
|
}
|
|
1501
1501
|
|
|
1502
1502
|
// Update Special Index
|
|
@@ -1505,7 +1505,7 @@ export class KeystoneDetailsComponentInstance
|
|
|
1505
1505
|
rel8nInfos: [
|
|
1506
1506
|
{
|
|
1507
1507
|
rel8nName: "keystone",
|
|
1508
|
-
ibGibs: [toDto({ ibGib: evolvedKeystone })]
|
|
1508
|
+
ibGibs: [toDto({ ibGib: evolvedKeystone }), toDto({ ibGib: delegateKeystone })]
|
|
1509
1509
|
}
|
|
1510
1510
|
],
|
|
1511
1511
|
metaspace,
|
|
@@ -1535,36 +1535,19 @@ export class KeystoneDetailsComponentInstance
|
|
|
1535
1535
|
try {
|
|
1536
1536
|
const keystoneService = new KeystoneService_V1();
|
|
1537
1537
|
let masterSecret: string | undefined;
|
|
1538
|
-
let
|
|
1539
|
-
let tries = 0;
|
|
1540
|
-
const maxTries = 5;
|
|
1538
|
+
let signingPoolId: string | undefined;
|
|
1541
1539
|
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
confirm: false,
|
|
1550
|
-
passkeys: this.aggregatedDetails?.passkeys,
|
|
1551
|
-
});
|
|
1552
|
-
} catch (err) {
|
|
1553
|
-
masterSecret = undefined;
|
|
1554
|
-
break;
|
|
1555
|
-
}
|
|
1556
|
-
|
|
1557
|
-
if (!masterSecret) break;
|
|
1558
|
-
|
|
1559
|
-
isCorrect = await keystoneService.verifySigningSecret({
|
|
1560
|
-
keystoneIbGib: this.ibGib,
|
|
1561
|
-
signingSecret: masterSecret
|
|
1540
|
+
try {
|
|
1541
|
+
const authResult = await promptForAuthoritySecret({
|
|
1542
|
+
keystone: this.ibGib,
|
|
1543
|
+
aggregatedDetails: this.aggregatedDetails,
|
|
1544
|
+
title: 'Authorize Removal',
|
|
1545
|
+
msg: `Are you sure you want to remove "${passkeyName}"? Enter your Master Secret (Password) or touch security key to authorize:`,
|
|
1546
|
+
verb: KEYSTONE_VERB_MANAGE
|
|
1562
1547
|
});
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
}
|
|
1566
|
-
|
|
1567
|
-
if (!isCorrect || !masterSecret) {
|
|
1548
|
+
masterSecret = authResult.secret;
|
|
1549
|
+
signingPoolId = authResult.poolId;
|
|
1550
|
+
} catch (err) {
|
|
1568
1551
|
this.setPasskeyStatus("Removal cancelled or unauthorized.", "error");
|
|
1569
1552
|
return;
|
|
1570
1553
|
}
|
|
@@ -1590,7 +1573,8 @@ export class KeystoneDetailsComponentInstance
|
|
|
1590
1573
|
if (poolExists) {
|
|
1591
1574
|
evolvedKeystone = await keystoneService.removePools({
|
|
1592
1575
|
latestKeystone: this.ibGib,
|
|
1593
|
-
masterSecret
|
|
1576
|
+
masterSecret: masterSecret!,
|
|
1577
|
+
signingPoolId: signingPoolId!,
|
|
1594
1578
|
poolIds: [poolIdToRemove],
|
|
1595
1579
|
frameDetails: {
|
|
1596
1580
|
passkeys: {
|
|
@@ -1604,11 +1588,12 @@ export class KeystoneDetailsComponentInstance
|
|
|
1604
1588
|
space
|
|
1605
1589
|
});
|
|
1606
1590
|
} else {
|
|
1591
|
+
// If pool doesn't exist, just update frameDetails
|
|
1607
1592
|
evolvedKeystone = await keystoneService.sign({
|
|
1608
1593
|
latestKeystone: this.ibGib,
|
|
1609
|
-
masterSecret
|
|
1594
|
+
masterSecret: masterSecret!,
|
|
1595
|
+
poolId: signingPoolId,
|
|
1610
1596
|
claim: {
|
|
1611
|
-
verb: POOL_ID_MANAGE,
|
|
1612
1597
|
target: getIbGibAddr({ ibGib: this.ibGib })
|
|
1613
1598
|
},
|
|
1614
1599
|
frameDetails: {
|
|
@@ -1783,48 +1768,23 @@ export class KeystoneDetailsComponentInstance
|
|
|
1783
1768
|
|
|
1784
1769
|
const custodianPool = linkData.custodianPool;
|
|
1785
1770
|
|
|
1786
|
-
// 2. Prompt user for
|
|
1787
|
-
this.showBusy({ isBusy: false });
|
|
1771
|
+
// 2. Prompt user for authorization secret
|
|
1788
1772
|
const keystoneService = new KeystoneService_V1();
|
|
1789
1773
|
let masterSecret: string | undefined;
|
|
1790
|
-
let
|
|
1791
|
-
let tries = 0;
|
|
1792
|
-
const maxTries = 8;
|
|
1793
|
-
|
|
1794
|
-
while (tries < maxTries && !isCorrect) {
|
|
1795
|
-
try {
|
|
1796
|
-
const promptMsg = tries === 0
|
|
1797
|
-
? "Link successful! Enter your local Master Secret (Password) to sign and register the custodian pool"
|
|
1798
|
-
: `Incorrect password. Please try again (${maxTries - tries} tries remaining):`;
|
|
1799
|
-
masterSecret = await promptForSecret({
|
|
1800
|
-
msg: promptMsg,
|
|
1801
|
-
confirm: false
|
|
1802
|
-
});
|
|
1803
|
-
} catch (error) {
|
|
1804
|
-
// cancelled
|
|
1805
|
-
if (logalot) { console.log(`${lc}[promptForSecret] user cancelled (I: 911ff1fb0234949228481968e9087626)`); }
|
|
1806
|
-
masterSecret = undefined;
|
|
1807
|
-
break;
|
|
1808
|
-
}
|
|
1809
|
-
|
|
1810
|
-
if (!masterSecret) {
|
|
1811
|
-
break;
|
|
1812
|
-
}
|
|
1774
|
+
let signingPoolId: string | undefined;
|
|
1813
1775
|
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1776
|
+
try {
|
|
1777
|
+
const authResult = await promptForAuthoritySecret({
|
|
1778
|
+
keystone: this.ibGib,
|
|
1779
|
+
aggregatedDetails: this.aggregatedDetails,
|
|
1780
|
+
title: `Link ${providerName}`,
|
|
1781
|
+
msg: `Enter Master Password or touch security key to authorize linking ${providerName}:`,
|
|
1782
|
+
verb: KEYSTONE_VERB_MANAGE
|
|
1819
1783
|
});
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
}
|
|
1825
|
-
|
|
1826
|
-
if (!isCorrect) {
|
|
1827
|
-
this.setSSOStatus("Link cancelled or failed (incorrect password).", "error");
|
|
1784
|
+
masterSecret = authResult.secret;
|
|
1785
|
+
signingPoolId = authResult.poolId;
|
|
1786
|
+
} catch (error) {
|
|
1787
|
+
this.setSSOStatus("Link cancelled or failed.", "error");
|
|
1828
1788
|
return;
|
|
1829
1789
|
}
|
|
1830
1790
|
|
|
@@ -1845,6 +1805,7 @@ export class KeystoneDetailsComponentInstance
|
|
|
1845
1805
|
const evolvedParent = await keystoneService.addPools({
|
|
1846
1806
|
latestKeystone: this.ibGib!,
|
|
1847
1807
|
masterSecret: masterSecret!,
|
|
1808
|
+
signingPoolId: signingPoolId!,
|
|
1848
1809
|
newPools: [custodianPool],
|
|
1849
1810
|
metaspace,
|
|
1850
1811
|
space
|
|
@@ -1875,7 +1836,7 @@ export class KeystoneDetailsComponentInstance
|
|
|
1875
1836
|
parentKeystone: evolvedParent,
|
|
1876
1837
|
delegateKeystone,
|
|
1877
1838
|
masterSecret: masterSecret!,
|
|
1878
|
-
signingPoolId:
|
|
1839
|
+
signingPoolId: signingPoolId!,
|
|
1879
1840
|
metaspace,
|
|
1880
1841
|
space,
|
|
1881
1842
|
allowedVerbs: [KEYSTONE_VERB_SYNC, KEYSTONE_VERB_CONNECT],
|
|
@@ -1949,45 +1910,23 @@ export class KeystoneDetailsComponentInstance
|
|
|
1949
1910
|
this.setSSOStatus(`Unlinking ${providerName}...`, "info");
|
|
1950
1911
|
|
|
1951
1912
|
try {
|
|
1952
|
-
// 1. Prompt user for
|
|
1913
|
+
// 1. Prompt user for authorization secret
|
|
1953
1914
|
const keystoneService = new KeystoneService_V1();
|
|
1954
1915
|
let masterSecret: string | undefined;
|
|
1955
|
-
let
|
|
1956
|
-
let tries = 0;
|
|
1957
|
-
const maxTries = 8;
|
|
1958
|
-
|
|
1959
|
-
while (tries < maxTries && !isCorrect) {
|
|
1960
|
-
try {
|
|
1961
|
-
const promptMsg = tries === 0
|
|
1962
|
-
? `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.`
|
|
1963
|
-
: `Incorrect password. Please try again (${maxTries - tries} tries remaining):`;
|
|
1964
|
-
masterSecret = await promptForSecret({
|
|
1965
|
-
msg: promptMsg,
|
|
1966
|
-
confirm: false,
|
|
1967
|
-
passkeys: this.aggregatedDetails?.passkeys,
|
|
1968
|
-
});
|
|
1969
|
-
} catch (error) {
|
|
1970
|
-
masterSecret = undefined;
|
|
1971
|
-
break;
|
|
1972
|
-
}
|
|
1973
|
-
|
|
1974
|
-
if (!masterSecret) {
|
|
1975
|
-
break;
|
|
1976
|
-
}
|
|
1916
|
+
let signingPoolId: string | undefined;
|
|
1977
1917
|
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1918
|
+
try {
|
|
1919
|
+
const authResult = await promptForAuthoritySecret({
|
|
1920
|
+
keystone: this.ibGib,
|
|
1921
|
+
aggregatedDetails: this.aggregatedDetails,
|
|
1922
|
+
title: `Unlink ${providerName}`,
|
|
1923
|
+
msg: `Are you sure you want to unlink your ${providerName} account? If so, enter your Master Password or touch security key to authorize.`,
|
|
1924
|
+
verb: KEYSTONE_VERB_MANAGE
|
|
1982
1925
|
});
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
}
|
|
1988
|
-
|
|
1989
|
-
if (!isCorrect) {
|
|
1990
|
-
this.setSSOStatus("Unlink cancelled or failed (incorrect password).", "error");
|
|
1926
|
+
masterSecret = authResult.secret;
|
|
1927
|
+
signingPoolId = authResult.poolId;
|
|
1928
|
+
} catch (error) {
|
|
1929
|
+
this.setSSOStatus("Unlink cancelled or failed.", "error");
|
|
1991
1930
|
return;
|
|
1992
1931
|
}
|
|
1993
1932
|
|
|
@@ -2009,6 +1948,7 @@ export class KeystoneDetailsComponentInstance
|
|
|
2009
1948
|
let evolvedParent = await keystoneService.removePools({
|
|
2010
1949
|
latestKeystone: this.ibGib!,
|
|
2011
1950
|
masterSecret: masterSecret!,
|
|
1951
|
+
signingPoolId: signingPoolId!,
|
|
2012
1952
|
poolIds: [targetPoolId],
|
|
2013
1953
|
metaspace,
|
|
2014
1954
|
space
|
|
@@ -2293,113 +2233,14 @@ export class KeystoneDetailsComponentInstance
|
|
|
2293
2233
|
await metaspace.registerNewIbGib({ ibGib: evolvedParent, space });
|
|
2294
2234
|
}
|
|
2295
2235
|
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
title: 'Creating Sovereign Sync Delegate...',
|
|
2300
|
-
msg: 'Deriving local device sync delegate keys from Master Password...',
|
|
2301
|
-
animationEmoji: '🔑'
|
|
2302
|
-
});
|
|
2303
|
-
|
|
2304
|
-
const rawUsername = this.aggregatedDetails?.username
|
|
2305
|
-
|| this.ibGib?.data?.username
|
|
2306
|
-
|| this.aggregatedDetails?.emails?.[0]?.split('@')[0]
|
|
2307
|
-
|| this.ibGib?.data?.emails?.[0]?.split('@')[0]
|
|
2308
|
-
|| 'user';
|
|
2309
|
-
|
|
2310
|
-
const { delegateKeystone: userSyncDelegate, deviceTag } = await createSovereignDeviceSyncDelegate({
|
|
2311
|
-
username: rawUsername,
|
|
2236
|
+
await this.registerAndSyncSovereignPasswordDelegate({
|
|
2237
|
+
evolvedParent,
|
|
2238
|
+
newPassword,
|
|
2312
2239
|
primaryTjpAddr,
|
|
2313
|
-
masterSecret: newPassword,
|
|
2314
|
-
metaspace,
|
|
2315
|
-
space,
|
|
2316
|
-
});
|
|
2317
|
-
|
|
2318
|
-
// 2. Register the new user sync delegate on the evolved parent keystone
|
|
2319
|
-
this.showBusy({
|
|
2320
|
-
isBusy: true,
|
|
2321
|
-
title: 'Registering Sovereign Delegate...',
|
|
2322
|
-
msg: 'Registering user sync delegate on primary keystone...',
|
|
2323
|
-
animationEmoji: '📝'
|
|
2324
|
-
});
|
|
2325
|
-
|
|
2326
|
-
const keystoneService = new KeystoneService_V1();
|
|
2327
|
-
const parentWithUserDelegate = await keystoneService.registerDelegate({
|
|
2328
|
-
parentKeystone: evolvedParent,
|
|
2329
|
-
delegateKeystone: userSyncDelegate,
|
|
2330
|
-
masterSecret: newPassword,
|
|
2331
|
-
signingPoolId: 'manage',
|
|
2332
|
-
metaspace,
|
|
2333
|
-
space,
|
|
2334
|
-
allowedVerbs: [KEYSTONE_VERB_SYNC, KEYSTONE_VERB_CONNECT],
|
|
2335
|
-
deviceName: deviceTag
|
|
2336
|
-
});
|
|
2337
|
-
|
|
2338
|
-
// 3. Post evolved keystone with new delegate to server
|
|
2339
|
-
this.showBusy({
|
|
2340
|
-
isBusy: true,
|
|
2341
|
-
title: 'Syncing to Server...',
|
|
2342
|
-
msg: 'Publishing updated domain identity and sovereign delegate to server...',
|
|
2343
|
-
animationEmoji: '⚡'
|
|
2344
|
-
});
|
|
2345
|
-
|
|
2346
|
-
const resSync = await getApiBridge().putEvolveKeystone({
|
|
2347
|
-
domainAddr: primaryTjpAddr,
|
|
2348
|
-
keystoneIbGib: parentWithUserDelegate,
|
|
2349
|
-
relatedKeystoneIbGibs: [userSyncDelegate]
|
|
2350
|
-
});
|
|
2351
|
-
if (!resSync.success) {
|
|
2352
|
-
console.warn(`${lc} Warning syncing user delegate to server: ${resSync.message}`);
|
|
2353
|
-
}
|
|
2354
|
-
|
|
2355
|
-
// 4. Save parentWithUserDelegate and userSyncDelegate to local space & metaspace
|
|
2356
|
-
await metaspace.put({ ibGibs: [parentWithUserDelegate, userSyncDelegate], space });
|
|
2357
|
-
await metaspace.registerNewIbGib({ ibGib: parentWithUserDelegate, space });
|
|
2358
|
-
await metaspace.registerNewIbGib({ ibGib: userSyncDelegate, space });
|
|
2359
|
-
|
|
2360
|
-
await updateSpecialIndex({
|
|
2361
|
-
type: "keystones",
|
|
2362
|
-
rel8nInfos: [
|
|
2363
|
-
{
|
|
2364
|
-
rel8nName: "keystone",
|
|
2365
|
-
ibGibs: [toDto({ ibGib: parentWithUserDelegate }), toDto({ ibGib: userSyncDelegate })]
|
|
2366
|
-
}
|
|
2367
|
-
],
|
|
2368
2240
|
metaspace,
|
|
2369
2241
|
space
|
|
2370
2242
|
});
|
|
2371
2243
|
|
|
2372
|
-
const evolvedAddr = getIbGibAddr({ ibGib: parentWithUserDelegate });
|
|
2373
|
-
|
|
2374
|
-
// Dispatch EVENT_IBGIB_IDENTITY_CHANGED
|
|
2375
|
-
window.dispatchEvent(new CustomEvent(EVENT_IBGIB_IDENTITY_CHANGED, {
|
|
2376
|
-
detail: { activeIdentityAddr: evolvedAddr },
|
|
2377
|
-
bubbles: true,
|
|
2378
|
-
composed: true
|
|
2379
|
-
}));
|
|
2380
|
-
|
|
2381
|
-
this.setChangePasswordStatus("Successfully added Master Password! Identity is now sovereign.", "success");
|
|
2382
|
-
|
|
2383
|
-
// Reset OTP section and button text
|
|
2384
|
-
if (this.elements?.sectionAddPasswordOtpEl) {
|
|
2385
|
-
this.elements.sectionAddPasswordOtpEl.classList.add('hidden');
|
|
2386
|
-
if (this.elements.inputAddPasswordOtpEl) {
|
|
2387
|
-
this.elements.inputAddPasswordOtpEl.value = '';
|
|
2388
|
-
}
|
|
2389
|
-
}
|
|
2390
|
-
if (this.elements?.btnChangePasswordEl) {
|
|
2391
|
-
this.elements.btnChangePasswordEl.textContent = "Update Password";
|
|
2392
|
-
}
|
|
2393
|
-
|
|
2394
|
-
// Refresh the view
|
|
2395
|
-
this.ibGibAddr = evolvedAddr;
|
|
2396
|
-
this.staticIbGib = parentWithUserDelegate;
|
|
2397
|
-
await this.loadIbGib({ getLatest: true });
|
|
2398
|
-
|
|
2399
|
-
// Clear inputs
|
|
2400
|
-
this.elements!.inputNewPasswordEl.value = '';
|
|
2401
|
-
this.elements!.inputConfirmPasswordEl.value = '';
|
|
2402
|
-
|
|
2403
2244
|
} catch (error) {
|
|
2404
2245
|
console.error(`${lc} Elevation failed: ${extractErrorMsg(error)}`);
|
|
2405
2246
|
this.setChangePasswordStatus(`Failed to add master password: ${extractErrorMsg(error)}`, "error");
|
|
@@ -2460,6 +2301,295 @@ export class KeystoneDetailsComponentInstance
|
|
|
2460
2301
|
}
|
|
2461
2302
|
}
|
|
2462
2303
|
|
|
2304
|
+
/**
|
|
2305
|
+
* Common DRY post-elevation workflow: creates and registers the password-based
|
|
2306
|
+
* device sync delegate, synchronizes the evolved identity with the delegate to the server,
|
|
2307
|
+
* updates the local space and special index, and dispatches the identity change event.
|
|
2308
|
+
*/
|
|
2309
|
+
private async registerAndSyncSovereignPasswordDelegate({
|
|
2310
|
+
evolvedParent,
|
|
2311
|
+
newPassword,
|
|
2312
|
+
primaryTjpAddr,
|
|
2313
|
+
metaspace,
|
|
2314
|
+
space,
|
|
2315
|
+
}: {
|
|
2316
|
+
evolvedParent: KeystoneIbGib_V1;
|
|
2317
|
+
newPassword: string;
|
|
2318
|
+
primaryTjpAddr: string;
|
|
2319
|
+
metaspace: any;
|
|
2320
|
+
space: any;
|
|
2321
|
+
}): Promise<KeystoneIbGib_V1> {
|
|
2322
|
+
const lc = `${this.lc}[${this.registerAndSyncSovereignPasswordDelegate.name}]`;
|
|
2323
|
+
|
|
2324
|
+
// 1. Create a password-based sync delegate for this device
|
|
2325
|
+
this.showBusy({
|
|
2326
|
+
isBusy: true,
|
|
2327
|
+
title: 'Creating Sovereign Sync Delegate...',
|
|
2328
|
+
msg: 'Deriving local device sync delegate keys from Master Password...',
|
|
2329
|
+
animationEmoji: '🔑'
|
|
2330
|
+
});
|
|
2331
|
+
await delay(50);
|
|
2332
|
+
|
|
2333
|
+
const rawUsername = this.aggregatedDetails?.username
|
|
2334
|
+
|| this.ibGib?.data?.username
|
|
2335
|
+
|| this.aggregatedDetails?.emails?.[0]?.split('@')[0]
|
|
2336
|
+
|| this.ibGib?.data?.emails?.[0]?.split('@')[0]
|
|
2337
|
+
|| 'user';
|
|
2338
|
+
|
|
2339
|
+
const { delegateKeystone: userSyncDelegate, deviceTag } = await createSovereignDeviceSyncDelegate({
|
|
2340
|
+
username: rawUsername,
|
|
2341
|
+
primaryTjpAddr,
|
|
2342
|
+
masterSecret: newPassword,
|
|
2343
|
+
metaspace,
|
|
2344
|
+
space,
|
|
2345
|
+
});
|
|
2346
|
+
|
|
2347
|
+
// 2. Register the new user sync delegate on the evolved parent keystone
|
|
2348
|
+
this.showBusy({
|
|
2349
|
+
isBusy: true,
|
|
2350
|
+
title: 'Registering Sovereign Delegate...',
|
|
2351
|
+
msg: 'Registering user sync delegate on primary keystone...',
|
|
2352
|
+
animationEmoji: '📝'
|
|
2353
|
+
});
|
|
2354
|
+
await delay(50);
|
|
2355
|
+
|
|
2356
|
+
const keystoneService = new KeystoneService_V1();
|
|
2357
|
+
const parentWithUserDelegate = await keystoneService.registerDelegate({
|
|
2358
|
+
parentKeystone: evolvedParent,
|
|
2359
|
+
delegateKeystone: userSyncDelegate,
|
|
2360
|
+
masterSecret: newPassword,
|
|
2361
|
+
signingPoolId: POOL_ID_MANAGE,
|
|
2362
|
+
metaspace,
|
|
2363
|
+
space,
|
|
2364
|
+
allowedVerbs: [KEYSTONE_VERB_SYNC, KEYSTONE_VERB_CONNECT],
|
|
2365
|
+
deviceName: deviceTag
|
|
2366
|
+
});
|
|
2367
|
+
|
|
2368
|
+
// 3. Post evolved keystone with new delegate to server
|
|
2369
|
+
this.showBusy({
|
|
2370
|
+
isBusy: true,
|
|
2371
|
+
title: 'Syncing to Server...',
|
|
2372
|
+
msg: 'Publishing updated domain identity and sovereign delegate to server...',
|
|
2373
|
+
animationEmoji: '⚡'
|
|
2374
|
+
});
|
|
2375
|
+
|
|
2376
|
+
const resSync = await getApiBridge().putEvolveKeystone({
|
|
2377
|
+
domainAddr: primaryTjpAddr,
|
|
2378
|
+
keystoneIbGib: parentWithUserDelegate,
|
|
2379
|
+
relatedKeystoneIbGibs: [userSyncDelegate]
|
|
2380
|
+
});
|
|
2381
|
+
if (!resSync.success) {
|
|
2382
|
+
throw new Error(`Server sync of password delegate failed: ${resSync.message}`);
|
|
2383
|
+
}
|
|
2384
|
+
|
|
2385
|
+
// 4. Save parentWithUserDelegate and userSyncDelegate to local space & metaspace
|
|
2386
|
+
await metaspace.put({ ibGibs: [parentWithUserDelegate, userSyncDelegate], space });
|
|
2387
|
+
await metaspace.registerNewIbGib({ ibGib: parentWithUserDelegate, space });
|
|
2388
|
+
await metaspace.registerNewIbGib({ ibGib: userSyncDelegate, space });
|
|
2389
|
+
|
|
2390
|
+
await updateSpecialIndex({
|
|
2391
|
+
type: "keystones",
|
|
2392
|
+
rel8nInfos: [
|
|
2393
|
+
{
|
|
2394
|
+
rel8nName: "keystone",
|
|
2395
|
+
ibGibs: [toDto({ ibGib: parentWithUserDelegate }), toDto({ ibGib: userSyncDelegate })]
|
|
2396
|
+
}
|
|
2397
|
+
],
|
|
2398
|
+
metaspace,
|
|
2399
|
+
space
|
|
2400
|
+
});
|
|
2401
|
+
|
|
2402
|
+
const evolvedAddr = getIbGibAddr({ ibGib: parentWithUserDelegate });
|
|
2403
|
+
|
|
2404
|
+
// Dispatch EVENT_IBGIB_IDENTITY_CHANGED
|
|
2405
|
+
window.dispatchEvent(new CustomEvent(EVENT_IBGIB_IDENTITY_CHANGED, {
|
|
2406
|
+
detail: { activeIdentityAddr: evolvedAddr },
|
|
2407
|
+
bubbles: true,
|
|
2408
|
+
composed: true
|
|
2409
|
+
}));
|
|
2410
|
+
|
|
2411
|
+
this.setChangePasswordStatus("Successfully added Master Password! Identity is now sovereign.", "success");
|
|
2412
|
+
|
|
2413
|
+
// Reset OTP section and button text
|
|
2414
|
+
if (this.elements?.sectionAddPasswordOtpEl) {
|
|
2415
|
+
this.elements.sectionAddPasswordOtpEl.classList.add('hidden');
|
|
2416
|
+
if (this.elements.inputAddPasswordOtpEl) {
|
|
2417
|
+
this.elements.inputAddPasswordOtpEl.value = '';
|
|
2418
|
+
}
|
|
2419
|
+
}
|
|
2420
|
+
if (this.elements?.btnChangePasswordEl) {
|
|
2421
|
+
this.elements.btnChangePasswordEl.textContent = "Update Password";
|
|
2422
|
+
}
|
|
2423
|
+
|
|
2424
|
+
// Refresh the view
|
|
2425
|
+
this.ibGibAddr = evolvedAddr;
|
|
2426
|
+
this.staticIbGib = parentWithUserDelegate;
|
|
2427
|
+
await this.loadIbGib({ getLatest: true });
|
|
2428
|
+
|
|
2429
|
+
// Clear inputs
|
|
2430
|
+
this.elements!.inputNewPasswordEl.value = '';
|
|
2431
|
+
this.elements!.inputConfirmPasswordEl.value = '';
|
|
2432
|
+
|
|
2433
|
+
return parentWithUserDelegate;
|
|
2434
|
+
}
|
|
2435
|
+
|
|
2436
|
+
/**
|
|
2437
|
+
* Handles adding a master password to a sovereign identity that was created with passkeys only.
|
|
2438
|
+
* Authorizes via passkey biometric/hardware assertion and cryptographically adds the manage pool.
|
|
2439
|
+
*/
|
|
2440
|
+
protected async handleAddPasswordToPasskeyIdentity(): Promise<void> {
|
|
2441
|
+
const lc = `${this.lc}[${this.handleAddPasswordToPasskeyIdentity.name}]`;
|
|
2442
|
+
this.setChangePasswordStatus("Validating entries...", "info");
|
|
2443
|
+
|
|
2444
|
+
try {
|
|
2445
|
+
const newPassword = this.elements!.inputNewPasswordEl.value;
|
|
2446
|
+
const confirmPassword = this.elements!.inputConfirmPasswordEl.value;
|
|
2447
|
+
|
|
2448
|
+
if (!newPassword) {
|
|
2449
|
+
this.setChangePasswordStatus("Master password is required.", "error");
|
|
2450
|
+
return;
|
|
2451
|
+
}
|
|
2452
|
+
if (newPassword !== confirmPassword) {
|
|
2453
|
+
this.setChangePasswordStatus("Master password and confirmation do not match.", "error");
|
|
2454
|
+
return;
|
|
2455
|
+
}
|
|
2456
|
+
|
|
2457
|
+
const passkeysMap = this.aggregatedDetails?.passkeys
|
|
2458
|
+
|| (this.ibGib?.data as any)?.frameDetails?.passkeys
|
|
2459
|
+
|| (this.ibGib?.data as any)?.passkeys
|
|
2460
|
+
|| {};
|
|
2461
|
+
const passkeysList: KeystonePasskeyEntry[] = Array.isArray(passkeysMap)
|
|
2462
|
+
? passkeysMap
|
|
2463
|
+
: Object.values(passkeysMap);
|
|
2464
|
+
|
|
2465
|
+
const passkeyPool = this.ibGib?.data?.challengePools?.find(p => p.id.startsWith('passkey-manage-'));
|
|
2466
|
+
if (!passkeyPool) {
|
|
2467
|
+
this.setChangePasswordStatus("No active passkey manage pool found on this identity.", "error");
|
|
2468
|
+
return;
|
|
2469
|
+
}
|
|
2470
|
+
|
|
2471
|
+
let passkeySecret: string | undefined;
|
|
2472
|
+
let signingPoolId: string | undefined;
|
|
2473
|
+
|
|
2474
|
+
try {
|
|
2475
|
+
const authResult = await promptForAuthoritySecret({
|
|
2476
|
+
keystone: this.ibGib,
|
|
2477
|
+
aggregatedDetails: this.aggregatedDetails,
|
|
2478
|
+
title: 'Authorize Master Password',
|
|
2479
|
+
msg: 'Please touch your fingerprint sensor, Face ID, or security key to authorize adding a Master Password...',
|
|
2480
|
+
verb: KEYSTONE_VERB_MANAGE
|
|
2481
|
+
});
|
|
2482
|
+
passkeySecret = authResult.secret;
|
|
2483
|
+
signingPoolId = authResult.poolId;
|
|
2484
|
+
} catch (err) {
|
|
2485
|
+
this.setChangePasswordStatus("Authorization cancelled.", "error");
|
|
2486
|
+
return;
|
|
2487
|
+
}
|
|
2488
|
+
|
|
2489
|
+
this.showBusy({
|
|
2490
|
+
isBusy: true,
|
|
2491
|
+
title: 'Adding Master Password...',
|
|
2492
|
+
msg: 'Compiling cryptographic master password pool and evolving identity...',
|
|
2493
|
+
animationEmoji: '🔑'
|
|
2494
|
+
});
|
|
2495
|
+
await delay(50);
|
|
2496
|
+
this.setChangePasswordStatus("Generating sovereign master password challenge pool...", "info");
|
|
2497
|
+
|
|
2498
|
+
const metaspace = await getGlobalMetaspace_waitIfNeeded();
|
|
2499
|
+
if (!metaspace) throw new Error("Global metaspace not available");
|
|
2500
|
+
const space = await metaspace.getLocalUserSpace({ lock: false });
|
|
2501
|
+
if (!space) throw new Error("Local user space not available");
|
|
2502
|
+
|
|
2503
|
+
const poolConfig: KeystonePoolConfig_HashV1 = {
|
|
2504
|
+
id: POOL_ID_MANAGE,
|
|
2505
|
+
type: KeystoneChallengeType.hash_reveal_v1,
|
|
2506
|
+
salt: POOL_ID_MANAGE,
|
|
2507
|
+
algo: HashAlgorithm.sha_256,
|
|
2508
|
+
rounds: 1,
|
|
2509
|
+
allowedVerbs: [KEYSTONE_VERB_MANAGE, KEYSTONE_VERB_SYNC, KEYSTONE_VERB_CONNECT],
|
|
2510
|
+
behavior: {
|
|
2511
|
+
size: 10,
|
|
2512
|
+
replenish: KeystoneReplenishStrategy.topUp,
|
|
2513
|
+
selectSequentially: 1,
|
|
2514
|
+
selectRandomly: 0,
|
|
2515
|
+
targetBindingCount: 0,
|
|
2516
|
+
}
|
|
2517
|
+
};
|
|
2518
|
+
|
|
2519
|
+
const newChallenges = await generatePoolChallenges({
|
|
2520
|
+
config: poolConfig,
|
|
2521
|
+
masterSecret: newPassword,
|
|
2522
|
+
});
|
|
2523
|
+
|
|
2524
|
+
const newManagePool = {
|
|
2525
|
+
id: POOL_ID_MANAGE,
|
|
2526
|
+
config: poolConfig,
|
|
2527
|
+
challenges: newChallenges,
|
|
2528
|
+
};
|
|
2529
|
+
|
|
2530
|
+
const primaryTjpAddr = (this.ibGib ? getTjpAddr({ ibGib: this.ibGib }) : undefined)
|
|
2531
|
+
|| (this.ibGibAddr ? (() => {
|
|
2532
|
+
const { ib } = getIbAndGib({ ibGibAddr: this.ibGibAddr });
|
|
2533
|
+
const { tjpGib } = getGibInfo({ ibGibAddr: this.ibGibAddr });
|
|
2534
|
+
return tjpGib && ib ? `${ib}^${tjpGib}` : this.ibGibAddr;
|
|
2535
|
+
})() : undefined)
|
|
2536
|
+
|| getIbGibAddr({ ibGib: this.ibGib! });
|
|
2537
|
+
|
|
2538
|
+
const keystoneService = new KeystoneService_V1();
|
|
2539
|
+
const evolvedParent = await keystoneService.sign({
|
|
2540
|
+
latestKeystone: this.ibGib!,
|
|
2541
|
+
claim: {
|
|
2542
|
+
target: primaryTjpAddr,
|
|
2543
|
+
verb: KEYSTONE_VERB_MANAGE
|
|
2544
|
+
},
|
|
2545
|
+
masterSecret: passkeySecret,
|
|
2546
|
+
poolId: signingPoolId,
|
|
2547
|
+
dataToPatch: {
|
|
2548
|
+
challengePools: [...(this.ibGib!.data!.challengePools || []), newManagePool]
|
|
2549
|
+
},
|
|
2550
|
+
metaspace,
|
|
2551
|
+
space
|
|
2552
|
+
});
|
|
2553
|
+
|
|
2554
|
+
// Sync pool addition to server
|
|
2555
|
+
this.showBusy({
|
|
2556
|
+
isBusy: true,
|
|
2557
|
+
title: 'Syncing to Server...',
|
|
2558
|
+
msg: 'Publishing updated identity to server...',
|
|
2559
|
+
animationEmoji: '⚡'
|
|
2560
|
+
});
|
|
2561
|
+
|
|
2562
|
+
const resSync = await getApiBridge().putEvolveKeystone({
|
|
2563
|
+
domainAddr: primaryTjpAddr,
|
|
2564
|
+
keystoneIbGib: evolvedParent,
|
|
2565
|
+
relatedKeystoneIbGibs: []
|
|
2566
|
+
});
|
|
2567
|
+
|
|
2568
|
+
if (!resSync.success) {
|
|
2569
|
+
throw new Error(`Server sync failed: ${resSync.message}`);
|
|
2570
|
+
}
|
|
2571
|
+
|
|
2572
|
+
// Save locally
|
|
2573
|
+
await metaspace.put({ ibGibs: [evolvedParent], space });
|
|
2574
|
+
await metaspace.registerNewIbGib({ ibGib: evolvedParent, space });
|
|
2575
|
+
|
|
2576
|
+
// Delegate registration & final sync via DRY helper
|
|
2577
|
+
await this.registerAndSyncSovereignPasswordDelegate({
|
|
2578
|
+
evolvedParent,
|
|
2579
|
+
newPassword,
|
|
2580
|
+
primaryTjpAddr,
|
|
2581
|
+
metaspace,
|
|
2582
|
+
space
|
|
2583
|
+
});
|
|
2584
|
+
|
|
2585
|
+
} catch (error) {
|
|
2586
|
+
console.error(`${lc} Failed to add password to passkey identity: ${extractErrorMsg(error)}`, error);
|
|
2587
|
+
this.setChangePasswordStatus(`Failed to add master password: ${extractErrorMsg(error)}`, "error");
|
|
2588
|
+
} finally {
|
|
2589
|
+
this.showBusy({ isBusy: false });
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2592
|
+
|
|
2463
2593
|
protected async handleChangePassword(): Promise<void> {
|
|
2464
2594
|
const lc = `${this.lc}[${this.handleChangePassword.name}]`;
|
|
2465
2595
|
|
|
@@ -2467,9 +2597,14 @@ export class KeystoneDetailsComponentInstance
|
|
|
2467
2597
|
getKeystoneCapabilities({ keystone: this.ibGib, aggregatedDetails: this.aggregatedDetails }) :
|
|
2468
2598
|
undefined;
|
|
2469
2599
|
|
|
2470
|
-
const
|
|
2471
|
-
if (
|
|
2472
|
-
|
|
2600
|
+
const hasPassword = !!caps?.hasPassword;
|
|
2601
|
+
if (!hasPassword) {
|
|
2602
|
+
const isCustodialOnly = !!caps ? caps.isCustodial && !caps.isSovereign : false;
|
|
2603
|
+
if (isCustodialOnly) {
|
|
2604
|
+
await this.handleAddMasterPassword();
|
|
2605
|
+
} else {
|
|
2606
|
+
await this.handleAddPasswordToPasskeyIdentity();
|
|
2607
|
+
}
|
|
2473
2608
|
return;
|
|
2474
2609
|
}
|
|
2475
2610
|
|