@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.
Files changed (38) hide show
  1. package/dist/AUTO-GENERATED-version.d.mts +1 -1
  2. package/dist/AUTO-GENERATED-version.mjs +1 -1
  3. package/dist/helpers.web.d.mts +17 -2
  4. package/dist/helpers.web.d.mts.map +1 -1
  5. package/dist/helpers.web.mjs +67 -2
  6. package/dist/helpers.web.mjs.map +1 -1
  7. package/dist/identity/passkey-helper.d.mts +7 -2
  8. package/dist/identity/passkey-helper.d.mts.map +1 -1
  9. package/dist/identity/passkey-helper.mjs +33 -12
  10. package/dist/identity/passkey-helper.mjs.map +1 -1
  11. package/dist/ui/component/identity/keystone-creator/keystone-creator.css +26 -0
  12. package/dist/ui/component/identity/keystone-creator/keystone-creator.d.mts.map +1 -1
  13. package/dist/ui/component/identity/keystone-creator/keystone-creator.html +18 -1
  14. package/dist/ui/component/identity/keystone-creator/keystone-creator.mjs +10 -2
  15. package/dist/ui/component/identity/keystone-creator/keystone-creator.mjs.map +1 -1
  16. package/dist/ui/component/identity/keystone-details/keystone-details.d.mts +12 -0
  17. package/dist/ui/component/identity/keystone-details/keystone-details.d.mts.map +1 -1
  18. package/dist/ui/component/identity/keystone-details/keystone-details.html +8 -0
  19. package/dist/ui/component/identity/keystone-details/keystone-details.mjs +419 -305
  20. package/dist/ui/component/identity/keystone-details/keystone-details.mjs.map +1 -1
  21. package/dist/ui/ui-helpers.d.mts.map +1 -1
  22. package/dist/ui/ui-helpers.mjs +7 -2
  23. package/dist/ui/ui-helpers.mjs.map +1 -1
  24. package/package.json +2 -2
  25. package/src/AUTO-GENERATED-version.mts +1 -1
  26. package/src/helpers.web.mts +95 -5
  27. package/src/identity/passkey-helper.mts +43 -10
  28. package/src/ui/component/identity/keystone-creator/keystone-creator.css +26 -0
  29. package/src/ui/component/identity/keystone-creator/keystone-creator.html +18 -1
  30. package/src/ui/component/identity/keystone-creator/keystone-creator.mts +12 -2
  31. package/src/ui/component/identity/keystone-details/keystone-details.html +8 -0
  32. package/src/ui/component/identity/keystone-details/keystone-details.mts +474 -339
  33. package/src/ui/ui-helpers.mts +6 -2
  34. package/tools/auto-generated-agent-skills/skills/ibgib-create-app-server/SKILL.md +50 -0
  35. package/src/identity/custodian-delegate-helper.mts.bak +0 -7
  36. package/src/identity/sso/sso-config-helper.mts.bak +0 -52
  37. package/src/identity/sso/sso-custodian-service.mts.bak +0 -566
  38. package/src/identity/sso/sso-custodian-service.respec.mts.bak +0 -298
@@ -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, getSaferSubstring, HashAlgorithm } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
4
+ import { extractErrorMsg, pretty, getSaferSubstring, HashAlgorithm, delay } 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";
@@ -14,14 +14,14 @@ import { getTjpAddr, toDto, getTimelinesGroupedByTjp } from "@ibgib/core-gib/dis
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
- import { promptForSecret } from "../../../../helpers.web.mjs";
17
+ import { promptForAuthoritySecret } from "../../../../helpers.web.mjs";
18
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
20
  import { POOL_ID_MANAGE, KEYSTONE_VERB_SYNC, KEYSTONE_VERB_CONNECT, KEYSTONE_VERB_MANAGE } from "@ibgib/core-gib/dist/keystone/keystone-constants.mjs";
21
21
  import { generateDefaultDeviceTag } from "../../../../util/device-helper.mjs";
22
22
  import { getGlobalMetaspace_waitIfNeeded } from "../../../../helpers.mjs";
23
23
  import { createSovereignDeviceSyncDelegate, createOAuth2DeviceSyncDelegate } from "../../../../identity/device-delegate-helper.mjs";
24
- import { isPasskeyPrfSupported, registerPasskeyCredential, evaluatePasskeyPrf, bufferToHex } from "../../../../identity/passkey-helper.mjs";
24
+ import { isPasskeyPrfSupported, registerPasskeyCredential, evaluatePasskeyPrf, createPasskeyDeviceSyncDelegate, bufferToHex } from "../../../../identity/passkey-helper.mjs";
25
25
  import { KeystoneChallengeType, KeystoneReplenishStrategy } from "@ibgib/core-gib/dist/keystone/keystone-types.mjs";
26
26
  const logalot = false;
27
27
  export const KEYSTONE_DETAILS_COMPONENT_NAME = 'ibgib-keystone-details';
@@ -183,6 +183,7 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
183
183
  this.elements.passkeysCardEl = shadow.getElementById('passkeys-card');
184
184
  this.elements.passkeysListEl = shadow.getElementById('passkeys-list');
185
185
  this.elements.btnAddPasskeyEl = shadow.getElementById('btn-add-passkey');
186
+ this.elements.selectPasskeyAttachmentEl = shadow.getElementById('select-passkey-attachment');
186
187
  this.elements.passkeyStatusMsgEl = shadow.getElementById('passkey-status-msg');
187
188
  this.elements.containerEl = shadow.getElementById('container');
188
189
  this.elements.historicalWarningBannerEl = shadow.getElementById('historical-warning-banner');
@@ -514,19 +515,17 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
514
515
  updatePasswordCardView() {
515
516
  const caps = !!this.ibGib ?
516
517
  getKeystoneCapabilities({ keystone: this.ibGib, aggregatedDetails: this.aggregatedDetails }) :
517
- false;
518
+ undefined;
518
519
  const cardTitleEl = this.elements?.changePasswordCardEl?.querySelector('h3');
519
520
  const cardDescEl = this.elements?.changePasswordCardEl?.querySelector('.description');
520
521
  const currentPasswordGroup = this.elements?.inputCurrentPasswordEl?.closest('.form-group');
521
522
  const btnChangePassword = this.elements?.btnChangePasswordEl;
522
- const isCustodialOnly = !!caps ?
523
- caps.isCustodial && !caps.isSovereign :
524
- false;
525
- if (isCustodialOnly) {
523
+ const hasPassword = !!caps?.hasPassword;
524
+ if (!hasPassword) {
526
525
  if (cardTitleEl)
527
526
  cardTitleEl.textContent = "Add Master Password";
528
527
  if (cardDescEl)
529
- cardDescEl.textContent = "Establish sovereign ownership by adding a master password. This enables direct, independent cryptographic management without relying solely on custodian authentication.";
528
+ cardDescEl.textContent = "Establish a master password for this identity. This enables sovereign password authentication alongside your passkeys and linked providers.";
530
529
  if (currentPasswordGroup)
531
530
  currentPasswordGroup.style.display = 'none';
532
531
  if (btnChangePassword)
@@ -1011,12 +1010,17 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
1011
1010
  const lc = `${this.lc}[${this.handleRevokeDelegate.name}]`;
1012
1011
  try {
1013
1012
  let masterSecret;
1013
+ let matchedPoolId;
1014
1014
  try {
1015
- masterSecret = await promptForSecret({
1015
+ const authResult = await promptForAuthoritySecret({
1016
+ keystone: this.ibGib,
1017
+ aggregatedDetails: this.aggregatedDetails,
1018
+ title: 'Revoke Delegate',
1016
1019
  msg: `Enter Master Password or touch security key to revoke delegate '${deviceName}':`,
1017
- confirm: false,
1018
- passkeys: this.aggregatedDetails?.passkeys,
1020
+ verb: KEYSTONE_VERB_MANAGE
1019
1021
  });
1022
+ masterSecret = authResult.secret;
1023
+ matchedPoolId = authResult.poolId;
1020
1024
  }
1021
1025
  catch (err) {
1022
1026
  // User cancelled prompt
@@ -1034,24 +1038,6 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
1034
1038
  throw new Error("Local space not found");
1035
1039
  }
1036
1040
  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
- }
1055
1041
  // Unregister delegate from parent keystone
1056
1042
  const evolvedParent = await keystoneSvc.unregisterDelegate({
1057
1043
  parentKeystone: this.ibGib,
@@ -1210,10 +1196,16 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
1210
1196
  const username = this.aggregatedDetails?.username || this.ibGib.data?.frameDetails?.username || 'user';
1211
1197
  const rawSalt = crypto.getRandomValues(new Uint8Array(32));
1212
1198
  const saltHex = bufferToHex(rawSalt.buffer);
1199
+ const attachmentVal = this.elements?.selectPasskeyAttachmentEl?.value;
1200
+ const authenticatorAttachment = (attachmentVal === 'cross-platform' || attachmentVal === 'platform')
1201
+ ? attachmentVal
1202
+ : undefined;
1213
1203
  const regResult = await registerPasskeyCredential({
1214
1204
  username,
1215
1205
  displayName: username,
1216
- salt: saltHex
1206
+ salt: saltHex,
1207
+ userVerification: 'preferred',
1208
+ authenticatorAttachment,
1217
1209
  });
1218
1210
  const { credentialId, transports } = regResult;
1219
1211
  let passkeySecret = regResult.passkeySecret;
@@ -1221,7 +1213,8 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
1221
1213
  const prfResult = await evaluatePasskeyPrf({
1222
1214
  credentialId,
1223
1215
  salt: saltHex,
1224
- transports
1216
+ transports,
1217
+ userVerification: 'preferred'
1225
1218
  });
1226
1219
  passkeySecret = prfResult.passkeySecret;
1227
1220
  }
@@ -1245,6 +1238,32 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
1245
1238
  throw new Error("Local space not found");
1246
1239
  const keystoneService = new KeystoneService_V1();
1247
1240
  let evolvedKeystone;
1241
+ const poolConfig = {
1242
+ id: poolId,
1243
+ type: KeystoneChallengeType.hash_reveal_v1,
1244
+ salt: poolId,
1245
+ algo: HashAlgorithm.sha_256,
1246
+ rounds: 1,
1247
+ allowedVerbs: [KEYSTONE_VERB_MANAGE, KEYSTONE_VERB_SYNC, KEYSTONE_VERB_CONNECT],
1248
+ behavior: {
1249
+ size: 10,
1250
+ replenish: KeystoneReplenishStrategy.topUp,
1251
+ selectSequentially: 1,
1252
+ selectRandomly: 0,
1253
+ targetBindingCount: 0,
1254
+ }
1255
+ };
1256
+ const challenges = await generatePoolChallenges({
1257
+ config: poolConfig,
1258
+ masterSecret: passkeySecret
1259
+ });
1260
+ const newPool = {
1261
+ id: poolId,
1262
+ config: poolConfig,
1263
+ challenges
1264
+ };
1265
+ let authSecret;
1266
+ let signingPoolId;
1248
1267
  if (isCustodialOnly) {
1249
1268
  // Elevate custodial account with sovereign passkey
1250
1269
  this.showBusy({
@@ -1253,34 +1272,12 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
1253
1272
  msg: 'Establishing sovereign passkey pool on identity...',
1254
1273
  animationEmoji: '⚡'
1255
1274
  });
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
- };
1275
+ authSecret = passkeySecret;
1276
+ signingPoolId = poolId;
1281
1277
  evolvedKeystone = await keystoneService.addPools({
1282
1278
  latestKeystone: this.ibGib,
1283
- masterSecret: passkeySecret,
1279
+ masterSecret: authSecret,
1280
+ signingPoolId,
1284
1281
  newPools: [newPool],
1285
1282
  frameDetails: {
1286
1283
  passkeys: {
@@ -1296,37 +1293,19 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
1296
1293
  }
1297
1294
  else {
1298
1295
  // 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
1296
+ // Prompt user to authorize with current master password or passkey
1297
+ try {
1298
+ const authResult = await promptForAuthoritySecret({
1299
+ keystone: this.ibGib,
1300
+ aggregatedDetails: this.aggregatedDetails,
1301
+ title: 'Authorize New Passkey',
1302
+ msg: 'Enter Master Password or touch security key to authorize adding this passkey:',
1303
+ verb: KEYSTONE_VERB_MANAGE
1325
1304
  });
1326
- if (!isCorrect)
1327
- tries++;
1305
+ authSecret = authResult.secret;
1306
+ signingPoolId = authResult.poolId;
1328
1307
  }
1329
- if (!isCorrect || !authSecret) {
1308
+ catch (err) {
1330
1309
  this.setPasskeyStatus("Registration cancelled (authorization required).", "error");
1331
1310
  return;
1332
1311
  }
@@ -1336,33 +1315,10 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
1336
1315
  msg: 'Adding cryptographic challenge pool and evolving identity...',
1337
1316
  animationEmoji: '🛡️'
1338
1317
  });
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
1318
  evolvedKeystone = await keystoneService.addPools({
1364
1319
  latestKeystone: this.ibGib,
1365
1320
  masterSecret: authSecret,
1321
+ signingPoolId,
1366
1322
  newPools: [newPool],
1367
1323
  frameDetails: {
1368
1324
  passkeys: {
@@ -1375,22 +1331,66 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
1375
1331
  metaspace,
1376
1332
  space
1377
1333
  });
1334
+ // Sync pool addition frame to server
1335
+ const primaryTjpAddr = getTjpAddr({ ibGib: evolvedKeystone, defaultIfNone: 'incomingAddr' });
1336
+ const resSyncPool = await getApiBridge().putEvolveKeystone({
1337
+ domainAddr: primaryTjpAddr,
1338
+ keystoneIbGib: evolvedKeystone,
1339
+ relatedKeystoneIbGibs: []
1340
+ });
1341
+ if (!resSyncPool.success) {
1342
+ throw new Error(`Server synchronization of passkey pool failed: ${resSyncPool.message}`);
1343
+ }
1378
1344
  }
1379
- // Sync evolved keystone to server
1345
+ // Construct outward device sync delegate for this passkey
1346
+ this.showBusy({
1347
+ isBusy: true,
1348
+ title: 'Creating Sync Delegate...',
1349
+ msg: 'Constructing outward passkey device sync delegate...',
1350
+ animationEmoji: '📱'
1351
+ });
1352
+ const primaryTjpAddr = getTjpAddr({ ibGib: evolvedKeystone, defaultIfNone: 'incomingAddr' });
1353
+ const { delegateKeystone, deviceTag } = await createPasskeyDeviceSyncDelegate({
1354
+ username,
1355
+ primaryTjpAddr,
1356
+ passkeySecret,
1357
+ transports,
1358
+ metaspace,
1359
+ space
1360
+ });
1361
+ // Register outward sync delegate on primary keystone
1362
+ this.showBusy({
1363
+ isBusy: true,
1364
+ title: 'Registering Delegate...',
1365
+ msg: 'Registering outward passkey delegate on primary identity...',
1366
+ animationEmoji: '📝'
1367
+ });
1368
+ const delegateSigningPoolId = isCustodialOnly ? poolId : signingPoolId;
1369
+ const delegateAuthSecret = isCustodialOnly ? passkeySecret : authSecret;
1370
+ evolvedKeystone = await keystoneService.registerDelegate({
1371
+ parentKeystone: evolvedKeystone,
1372
+ delegateKeystone,
1373
+ masterSecret: delegateAuthSecret,
1374
+ signingPoolId: delegateSigningPoolId,
1375
+ metaspace,
1376
+ space,
1377
+ allowedVerbs: [KEYSTONE_VERB_SYNC, KEYSTONE_VERB_CONNECT],
1378
+ deviceName: deviceTag
1379
+ });
1380
+ // Sync evolved keystone and delegate to server
1380
1381
  this.showBusy({
1381
1382
  isBusy: true,
1382
1383
  title: 'Syncing to Server...',
1383
- msg: 'Publishing evolved identity keystone to server...',
1384
+ msg: 'Publishing evolved identity keystone and delegate to server...',
1384
1385
  animationEmoji: '⚡'
1385
1386
  });
1386
- const primaryTjpAddr = getTjpAddr({ ibGib: evolvedKeystone, defaultIfNone: 'incomingAddr' });
1387
1387
  const resSync = await getApiBridge().putEvolveKeystone({
1388
1388
  domainAddr: primaryTjpAddr,
1389
1389
  keystoneIbGib: evolvedKeystone,
1390
- relatedKeystoneIbGibs: []
1390
+ relatedKeystoneIbGibs: [delegateKeystone]
1391
1391
  });
1392
1392
  if (!resSync.success) {
1393
- console.warn(`${lc} Server sync note: ${resSync.message}`);
1393
+ throw new Error(`Server synchronization of delegate failed: ${resSync.message}`);
1394
1394
  }
1395
1395
  // Update Special Index
1396
1396
  await updateSpecialIndex({
@@ -1398,7 +1398,7 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
1398
1398
  rel8nInfos: [
1399
1399
  {
1400
1400
  rel8nName: "keystone",
1401
- ibGibs: [toDto({ ibGib: evolvedKeystone })]
1401
+ ibGibs: [toDto({ ibGib: evolvedKeystone }), toDto({ ibGib: delegateKeystone })]
1402
1402
  }
1403
1403
  ],
1404
1404
  metaspace,
@@ -1427,34 +1427,19 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
1427
1427
  try {
1428
1428
  const keystoneService = new KeystoneService_V1();
1429
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
1430
+ let signingPoolId;
1431
+ try {
1432
+ const authResult = await promptForAuthoritySecret({
1433
+ keystone: this.ibGib,
1434
+ aggregatedDetails: this.aggregatedDetails,
1435
+ title: 'Authorize Removal',
1436
+ msg: `Are you sure you want to remove "${passkeyName}"? Enter your Master Secret (Password) or touch security key to authorize:`,
1437
+ verb: KEYSTONE_VERB_MANAGE
1453
1438
  });
1454
- if (!isCorrect)
1455
- tries++;
1439
+ masterSecret = authResult.secret;
1440
+ signingPoolId = authResult.poolId;
1456
1441
  }
1457
- if (!isCorrect || !masterSecret) {
1442
+ catch (err) {
1458
1443
  this.setPasskeyStatus("Removal cancelled or unauthorized.", "error");
1459
1444
  return;
1460
1445
  }
@@ -1477,7 +1462,8 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
1477
1462
  if (poolExists) {
1478
1463
  evolvedKeystone = await keystoneService.removePools({
1479
1464
  latestKeystone: this.ibGib,
1480
- masterSecret,
1465
+ masterSecret: masterSecret,
1466
+ signingPoolId: signingPoolId,
1481
1467
  poolIds: [poolIdToRemove],
1482
1468
  frameDetails: {
1483
1469
  passkeys: {
@@ -1492,11 +1478,12 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
1492
1478
  });
1493
1479
  }
1494
1480
  else {
1481
+ // If pool doesn't exist, just update frameDetails
1495
1482
  evolvedKeystone = await keystoneService.sign({
1496
1483
  latestKeystone: this.ibGib,
1497
- masterSecret,
1484
+ masterSecret: masterSecret,
1485
+ poolId: signingPoolId,
1498
1486
  claim: {
1499
- verb: POOL_ID_MANAGE,
1500
1487
  target: getIbGibAddr({ ibGib: this.ibGib })
1501
1488
  },
1502
1489
  frameDetails: {
@@ -1664,46 +1651,23 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
1664
1651
  throw new Error(`Failed to retrieve custodian challenge pool: ${linkData.message || 'unknown error'}`);
1665
1652
  }
1666
1653
  const custodianPool = linkData.custodianPool;
1667
- // 2. Prompt user for master secret and verify it
1668
- this.showBusy({ isBusy: false });
1654
+ // 2. Prompt user for authorization secret
1669
1655
  const keystoneService = new KeystoneService_V1();
1670
1656
  let masterSecret;
1671
- let isCorrect = false;
1672
- let tries = 0;
1673
- const maxTries = 8;
1674
- while (tries < maxTries && !isCorrect) {
1675
- try {
1676
- const promptMsg = tries === 0
1677
- ? "Link successful! Enter your local Master Secret (Password) to sign and register the custodian pool"
1678
- : `Incorrect password. Please try again (${maxTries - tries} tries remaining):`;
1679
- masterSecret = await promptForSecret({
1680
- msg: promptMsg,
1681
- confirm: false
1682
- });
1683
- }
1684
- catch (error) {
1685
- // cancelled
1686
- if (logalot) {
1687
- console.log(`${lc}[promptForSecret] user cancelled (I: 911ff1fb0234949228481968e9087626)`);
1688
- }
1689
- masterSecret = undefined;
1690
- break;
1691
- }
1692
- if (!masterSecret) {
1693
- break;
1694
- }
1695
- // Verify secret using verifySigningSecret (requires manage poolId)
1696
- isCorrect = await keystoneService.verifySigningSecret({
1697
- keystoneIbGib: this.ibGib,
1698
- signingSecret: masterSecret,
1699
- poolId: 'manage'
1657
+ let signingPoolId;
1658
+ try {
1659
+ const authResult = await promptForAuthoritySecret({
1660
+ keystone: this.ibGib,
1661
+ aggregatedDetails: this.aggregatedDetails,
1662
+ title: `Link ${providerName}`,
1663
+ msg: `Enter Master Password or touch security key to authorize linking ${providerName}:`,
1664
+ verb: KEYSTONE_VERB_MANAGE
1700
1665
  });
1701
- if (!isCorrect) {
1702
- tries++;
1703
- }
1666
+ masterSecret = authResult.secret;
1667
+ signingPoolId = authResult.poolId;
1704
1668
  }
1705
- if (!isCorrect) {
1706
- this.setSSOStatus("Link cancelled or failed (incorrect password).", "error");
1669
+ catch (error) {
1670
+ this.setSSOStatus("Link cancelled or failed.", "error");
1707
1671
  return;
1708
1672
  }
1709
1673
  this.showBusy({
@@ -1724,6 +1688,7 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
1724
1688
  const evolvedParent = await keystoneService.addPools({
1725
1689
  latestKeystone: this.ibGib,
1726
1690
  masterSecret: masterSecret,
1691
+ signingPoolId: signingPoolId,
1727
1692
  newPools: [custodianPool],
1728
1693
  metaspace,
1729
1694
  space
@@ -1751,7 +1716,7 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
1751
1716
  parentKeystone: evolvedParent,
1752
1717
  delegateKeystone,
1753
1718
  masterSecret: masterSecret,
1754
- signingPoolId: 'manage',
1719
+ signingPoolId: signingPoolId,
1755
1720
  metaspace,
1756
1721
  space,
1757
1722
  allowedVerbs: [KEYSTONE_VERB_SYNC, KEYSTONE_VERB_CONNECT],
@@ -1820,41 +1785,23 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
1820
1785
  const providerName = providerId === 'google' ? 'Google' : providerId === 'github' ? 'GitHub' : providerId;
1821
1786
  this.setSSOStatus(`Unlinking ${providerName}...`, "info");
1822
1787
  try {
1823
- // 1. Prompt user for password
1788
+ // 1. Prompt user for authorization secret
1824
1789
  const keystoneService = new KeystoneService_V1();
1825
1790
  let masterSecret;
1826
- let isCorrect = false;
1827
- let tries = 0;
1828
- const maxTries = 8;
1829
- while (tries < maxTries && !isCorrect) {
1830
- try {
1831
- const promptMsg = tries === 0
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.`
1833
- : `Incorrect password. Please try again (${maxTries - tries} tries remaining):`;
1834
- masterSecret = await promptForSecret({
1835
- msg: promptMsg,
1836
- confirm: false,
1837
- passkeys: this.aggregatedDetails?.passkeys,
1838
- });
1839
- }
1840
- catch (error) {
1841
- masterSecret = undefined;
1842
- break;
1843
- }
1844
- if (!masterSecret) {
1845
- break;
1846
- }
1847
- // Verify secret using verifySigningSecret
1848
- isCorrect = await keystoneService.verifySigningSecret({
1849
- keystoneIbGib: this.ibGib,
1850
- signingSecret: masterSecret
1791
+ let signingPoolId;
1792
+ try {
1793
+ const authResult = await promptForAuthoritySecret({
1794
+ keystone: this.ibGib,
1795
+ aggregatedDetails: this.aggregatedDetails,
1796
+ title: `Unlink ${providerName}`,
1797
+ msg: `Are you sure you want to unlink your ${providerName} account? If so, enter your Master Password or touch security key to authorize.`,
1798
+ verb: KEYSTONE_VERB_MANAGE
1851
1799
  });
1852
- if (!isCorrect) {
1853
- tries++;
1854
- }
1800
+ masterSecret = authResult.secret;
1801
+ signingPoolId = authResult.poolId;
1855
1802
  }
1856
- if (!isCorrect) {
1857
- this.setSSOStatus("Unlink cancelled or failed (incorrect password).", "error");
1803
+ catch (error) {
1804
+ this.setSSOStatus("Unlink cancelled or failed.", "error");
1858
1805
  return;
1859
1806
  }
1860
1807
  this.showBusy({
@@ -1876,6 +1823,7 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
1876
1823
  let evolvedParent = await keystoneService.removePools({
1877
1824
  latestKeystone: this.ibGib,
1878
1825
  masterSecret: masterSecret,
1826
+ signingPoolId: signingPoolId,
1879
1827
  poolIds: [targetPoolId],
1880
1828
  metaspace,
1881
1829
  space
@@ -2134,98 +2082,13 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
2134
2082
  await metaspace.put({ ibGibs: [evolvedParent], space });
2135
2083
  await metaspace.registerNewIbGib({ ibGib: evolvedParent, space });
2136
2084
  }
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,
2085
+ await this.registerAndSyncSovereignPasswordDelegate({
2086
+ evolvedParent,
2087
+ newPassword,
2151
2088
  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 });
2193
- await updateSpecialIndex({
2194
- type: "keystones",
2195
- rel8nInfos: [
2196
- {
2197
- rel8nName: "keystone",
2198
- ibGibs: [toDto({ ibGib: parentWithUserDelegate }), toDto({ ibGib: userSyncDelegate })]
2199
- }
2200
- ],
2201
2089
  metaspace,
2202
2090
  space
2203
2091
  });
2204
- const evolvedAddr = getIbGibAddr({ ibGib: parentWithUserDelegate });
2205
- // Dispatch EVENT_IBGIB_IDENTITY_CHANGED
2206
- window.dispatchEvent(new CustomEvent(EVENT_IBGIB_IDENTITY_CHANGED, {
2207
- detail: { activeIdentityAddr: evolvedAddr },
2208
- bubbles: true,
2209
- composed: true
2210
- }));
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
- }
2222
- // Refresh the view
2223
- this.ibGibAddr = evolvedAddr;
2224
- this.staticIbGib = parentWithUserDelegate;
2225
- await this.loadIbGib({ getLatest: true });
2226
- // Clear inputs
2227
- this.elements.inputNewPasswordEl.value = '';
2228
- this.elements.inputConfirmPasswordEl.value = '';
2229
2092
  }
2230
2093
  catch (error) {
2231
2094
  console.error(`${lc} Elevation failed: ${extractErrorMsg(error)}`);
@@ -2287,14 +2150,265 @@ export class KeystoneDetailsComponentInstance extends IbGibDynamicComponentInsta
2287
2150
  }
2288
2151
  }
2289
2152
  }
2153
+ /**
2154
+ * Common DRY post-elevation workflow: creates and registers the password-based
2155
+ * device sync delegate, synchronizes the evolved identity with the delegate to the server,
2156
+ * updates the local space and special index, and dispatches the identity change event.
2157
+ */
2158
+ async registerAndSyncSovereignPasswordDelegate({ evolvedParent, newPassword, primaryTjpAddr, metaspace, space, }) {
2159
+ const lc = `${this.lc}[${this.registerAndSyncSovereignPasswordDelegate.name}]`;
2160
+ // 1. Create a password-based sync delegate for this device
2161
+ this.showBusy({
2162
+ isBusy: true,
2163
+ title: 'Creating Sovereign Sync Delegate...',
2164
+ msg: 'Deriving local device sync delegate keys from Master Password...',
2165
+ animationEmoji: '🔑'
2166
+ });
2167
+ await delay(50);
2168
+ const rawUsername = this.aggregatedDetails?.username
2169
+ || this.ibGib?.data?.username
2170
+ || this.aggregatedDetails?.emails?.[0]?.split('@')[0]
2171
+ || this.ibGib?.data?.emails?.[0]?.split('@')[0]
2172
+ || 'user';
2173
+ const { delegateKeystone: userSyncDelegate, deviceTag } = await createSovereignDeviceSyncDelegate({
2174
+ username: rawUsername,
2175
+ primaryTjpAddr,
2176
+ masterSecret: newPassword,
2177
+ metaspace,
2178
+ space,
2179
+ });
2180
+ // 2. Register the new user sync delegate on the evolved parent keystone
2181
+ this.showBusy({
2182
+ isBusy: true,
2183
+ title: 'Registering Sovereign Delegate...',
2184
+ msg: 'Registering user sync delegate on primary keystone...',
2185
+ animationEmoji: '📝'
2186
+ });
2187
+ await delay(50);
2188
+ const keystoneService = new KeystoneService_V1();
2189
+ const parentWithUserDelegate = await keystoneService.registerDelegate({
2190
+ parentKeystone: evolvedParent,
2191
+ delegateKeystone: userSyncDelegate,
2192
+ masterSecret: newPassword,
2193
+ signingPoolId: POOL_ID_MANAGE,
2194
+ metaspace,
2195
+ space,
2196
+ allowedVerbs: [KEYSTONE_VERB_SYNC, KEYSTONE_VERB_CONNECT],
2197
+ deviceName: deviceTag
2198
+ });
2199
+ // 3. Post evolved keystone with new delegate to server
2200
+ this.showBusy({
2201
+ isBusy: true,
2202
+ title: 'Syncing to Server...',
2203
+ msg: 'Publishing updated domain identity and sovereign delegate to server...',
2204
+ animationEmoji: '⚡'
2205
+ });
2206
+ const resSync = await getApiBridge().putEvolveKeystone({
2207
+ domainAddr: primaryTjpAddr,
2208
+ keystoneIbGib: parentWithUserDelegate,
2209
+ relatedKeystoneIbGibs: [userSyncDelegate]
2210
+ });
2211
+ if (!resSync.success) {
2212
+ throw new Error(`Server sync of password delegate failed: ${resSync.message}`);
2213
+ }
2214
+ // 4. Save parentWithUserDelegate and userSyncDelegate to local space & metaspace
2215
+ await metaspace.put({ ibGibs: [parentWithUserDelegate, userSyncDelegate], space });
2216
+ await metaspace.registerNewIbGib({ ibGib: parentWithUserDelegate, space });
2217
+ await metaspace.registerNewIbGib({ ibGib: userSyncDelegate, space });
2218
+ await updateSpecialIndex({
2219
+ type: "keystones",
2220
+ rel8nInfos: [
2221
+ {
2222
+ rel8nName: "keystone",
2223
+ ibGibs: [toDto({ ibGib: parentWithUserDelegate }), toDto({ ibGib: userSyncDelegate })]
2224
+ }
2225
+ ],
2226
+ metaspace,
2227
+ space
2228
+ });
2229
+ const evolvedAddr = getIbGibAddr({ ibGib: parentWithUserDelegate });
2230
+ // Dispatch EVENT_IBGIB_IDENTITY_CHANGED
2231
+ window.dispatchEvent(new CustomEvent(EVENT_IBGIB_IDENTITY_CHANGED, {
2232
+ detail: { activeIdentityAddr: evolvedAddr },
2233
+ bubbles: true,
2234
+ composed: true
2235
+ }));
2236
+ this.setChangePasswordStatus("Successfully added Master Password! Identity is now sovereign.", "success");
2237
+ // Reset OTP section and button text
2238
+ if (this.elements?.sectionAddPasswordOtpEl) {
2239
+ this.elements.sectionAddPasswordOtpEl.classList.add('hidden');
2240
+ if (this.elements.inputAddPasswordOtpEl) {
2241
+ this.elements.inputAddPasswordOtpEl.value = '';
2242
+ }
2243
+ }
2244
+ if (this.elements?.btnChangePasswordEl) {
2245
+ this.elements.btnChangePasswordEl.textContent = "Update Password";
2246
+ }
2247
+ // Refresh the view
2248
+ this.ibGibAddr = evolvedAddr;
2249
+ this.staticIbGib = parentWithUserDelegate;
2250
+ await this.loadIbGib({ getLatest: true });
2251
+ // Clear inputs
2252
+ this.elements.inputNewPasswordEl.value = '';
2253
+ this.elements.inputConfirmPasswordEl.value = '';
2254
+ return parentWithUserDelegate;
2255
+ }
2256
+ /**
2257
+ * Handles adding a master password to a sovereign identity that was created with passkeys only.
2258
+ * Authorizes via passkey biometric/hardware assertion and cryptographically adds the manage pool.
2259
+ */
2260
+ async handleAddPasswordToPasskeyIdentity() {
2261
+ const lc = `${this.lc}[${this.handleAddPasswordToPasskeyIdentity.name}]`;
2262
+ this.setChangePasswordStatus("Validating entries...", "info");
2263
+ try {
2264
+ const newPassword = this.elements.inputNewPasswordEl.value;
2265
+ const confirmPassword = this.elements.inputConfirmPasswordEl.value;
2266
+ if (!newPassword) {
2267
+ this.setChangePasswordStatus("Master password is required.", "error");
2268
+ return;
2269
+ }
2270
+ if (newPassword !== confirmPassword) {
2271
+ this.setChangePasswordStatus("Master password and confirmation do not match.", "error");
2272
+ return;
2273
+ }
2274
+ const passkeysMap = this.aggregatedDetails?.passkeys
2275
+ || this.ibGib?.data?.frameDetails?.passkeys
2276
+ || this.ibGib?.data?.passkeys
2277
+ || {};
2278
+ const passkeysList = Array.isArray(passkeysMap)
2279
+ ? passkeysMap
2280
+ : Object.values(passkeysMap);
2281
+ const passkeyPool = this.ibGib?.data?.challengePools?.find(p => p.id.startsWith('passkey-manage-'));
2282
+ if (!passkeyPool) {
2283
+ this.setChangePasswordStatus("No active passkey manage pool found on this identity.", "error");
2284
+ return;
2285
+ }
2286
+ let passkeySecret;
2287
+ let signingPoolId;
2288
+ try {
2289
+ const authResult = await promptForAuthoritySecret({
2290
+ keystone: this.ibGib,
2291
+ aggregatedDetails: this.aggregatedDetails,
2292
+ title: 'Authorize Master Password',
2293
+ msg: 'Please touch your fingerprint sensor, Face ID, or security key to authorize adding a Master Password...',
2294
+ verb: KEYSTONE_VERB_MANAGE
2295
+ });
2296
+ passkeySecret = authResult.secret;
2297
+ signingPoolId = authResult.poolId;
2298
+ }
2299
+ catch (err) {
2300
+ this.setChangePasswordStatus("Authorization cancelled.", "error");
2301
+ return;
2302
+ }
2303
+ this.showBusy({
2304
+ isBusy: true,
2305
+ title: 'Adding Master Password...',
2306
+ msg: 'Compiling cryptographic master password pool and evolving identity...',
2307
+ animationEmoji: '🔑'
2308
+ });
2309
+ await delay(50);
2310
+ this.setChangePasswordStatus("Generating sovereign master password challenge pool...", "info");
2311
+ const metaspace = await getGlobalMetaspace_waitIfNeeded();
2312
+ if (!metaspace)
2313
+ throw new Error("Global metaspace not available");
2314
+ const space = await metaspace.getLocalUserSpace({ lock: false });
2315
+ if (!space)
2316
+ throw new Error("Local user space not available");
2317
+ const poolConfig = {
2318
+ id: POOL_ID_MANAGE,
2319
+ type: KeystoneChallengeType.hash_reveal_v1,
2320
+ salt: POOL_ID_MANAGE,
2321
+ algo: HashAlgorithm.sha_256,
2322
+ rounds: 1,
2323
+ allowedVerbs: [KEYSTONE_VERB_MANAGE, KEYSTONE_VERB_SYNC, KEYSTONE_VERB_CONNECT],
2324
+ behavior: {
2325
+ size: 10,
2326
+ replenish: KeystoneReplenishStrategy.topUp,
2327
+ selectSequentially: 1,
2328
+ selectRandomly: 0,
2329
+ targetBindingCount: 0,
2330
+ }
2331
+ };
2332
+ const newChallenges = await generatePoolChallenges({
2333
+ config: poolConfig,
2334
+ masterSecret: newPassword,
2335
+ });
2336
+ const newManagePool = {
2337
+ id: POOL_ID_MANAGE,
2338
+ config: poolConfig,
2339
+ challenges: newChallenges,
2340
+ };
2341
+ const primaryTjpAddr = (this.ibGib ? getTjpAddr({ ibGib: this.ibGib }) : undefined)
2342
+ || (this.ibGibAddr ? (() => {
2343
+ const { ib } = getIbAndGib({ ibGibAddr: this.ibGibAddr });
2344
+ const { tjpGib } = getGibInfo({ ibGibAddr: this.ibGibAddr });
2345
+ return tjpGib && ib ? `${ib}^${tjpGib}` : this.ibGibAddr;
2346
+ })() : undefined)
2347
+ || getIbGibAddr({ ibGib: this.ibGib });
2348
+ const keystoneService = new KeystoneService_V1();
2349
+ const evolvedParent = await keystoneService.sign({
2350
+ latestKeystone: this.ibGib,
2351
+ claim: {
2352
+ target: primaryTjpAddr,
2353
+ verb: KEYSTONE_VERB_MANAGE
2354
+ },
2355
+ masterSecret: passkeySecret,
2356
+ poolId: signingPoolId,
2357
+ dataToPatch: {
2358
+ challengePools: [...(this.ibGib.data.challengePools || []), newManagePool]
2359
+ },
2360
+ metaspace,
2361
+ space
2362
+ });
2363
+ // Sync pool addition to server
2364
+ this.showBusy({
2365
+ isBusy: true,
2366
+ title: 'Syncing to Server...',
2367
+ msg: 'Publishing updated identity to server...',
2368
+ animationEmoji: '⚡'
2369
+ });
2370
+ const resSync = await getApiBridge().putEvolveKeystone({
2371
+ domainAddr: primaryTjpAddr,
2372
+ keystoneIbGib: evolvedParent,
2373
+ relatedKeystoneIbGibs: []
2374
+ });
2375
+ if (!resSync.success) {
2376
+ throw new Error(`Server sync failed: ${resSync.message}`);
2377
+ }
2378
+ // Save locally
2379
+ await metaspace.put({ ibGibs: [evolvedParent], space });
2380
+ await metaspace.registerNewIbGib({ ibGib: evolvedParent, space });
2381
+ // Delegate registration & final sync via DRY helper
2382
+ await this.registerAndSyncSovereignPasswordDelegate({
2383
+ evolvedParent,
2384
+ newPassword,
2385
+ primaryTjpAddr,
2386
+ metaspace,
2387
+ space
2388
+ });
2389
+ }
2390
+ catch (error) {
2391
+ console.error(`${lc} Failed to add password to passkey identity: ${extractErrorMsg(error)}`, error);
2392
+ this.setChangePasswordStatus(`Failed to add master password: ${extractErrorMsg(error)}`, "error");
2393
+ }
2394
+ finally {
2395
+ this.showBusy({ isBusy: false });
2396
+ }
2397
+ }
2290
2398
  async handleChangePassword() {
2291
2399
  const lc = `${this.lc}[${this.handleChangePassword.name}]`;
2292
2400
  const caps = !!this.ibGib ?
2293
2401
  getKeystoneCapabilities({ keystone: this.ibGib, aggregatedDetails: this.aggregatedDetails }) :
2294
2402
  undefined;
2295
- const isCustodialOnly = !!caps ? caps.isCustodial && !caps.isSovereign : false;
2296
- if (isCustodialOnly) {
2297
- await this.handleAddMasterPassword();
2403
+ const hasPassword = !!caps?.hasPassword;
2404
+ if (!hasPassword) {
2405
+ const isCustodialOnly = !!caps ? caps.isCustodial && !caps.isSovereign : false;
2406
+ if (isCustodialOnly) {
2407
+ await this.handleAddMasterPassword();
2408
+ }
2409
+ else {
2410
+ await this.handleAddPasswordToPasskeyIdentity();
2411
+ }
2298
2412
  return;
2299
2413
  }
2300
2414
  this.setChangePasswordStatus("Validating entries...", "info");