@ibgib/space-gib 0.0.6 → 0.0.8

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/README.md +20 -0
  2. package/dist/client/bootstrap.mjs +31 -31
  3. package/dist/client/bootstrap.mjs.map +3 -3
  4. package/dist/client/chunk-DBHMHCGD.mjs +2341 -0
  5. package/dist/client/{chunk-SMOZ2D5E.mjs.map → chunk-DBHMHCGD.mjs.map} +4 -4
  6. package/dist/client/chunk-SUQ5QJH4.mjs +42 -0
  7. package/dist/client/chunk-SUQ5QJH4.mjs.map +7 -0
  8. package/dist/client/index.mjs +1 -1
  9. package/dist/client/script.mjs +1 -1
  10. package/dist/server/server.mjs +1561 -179
  11. package/dist/server/server.mjs.map +4 -4
  12. package/package.json +5 -5
  13. package/space-gib.localhost-1783713259760.log +45 -0
  14. package/src/client/AUTO-GENERATED-version.mts +1 -1
  15. package/src/client/api/space-gib-api-bridge.mts +7 -2
  16. package/src/client/bootstrap.mts +9 -0
  17. package/src/client/components/identity-header/identity-header.mts +43 -26
  18. package/src/client/components/identity-manager/identity-manager.css +57 -392
  19. package/src/client/components/identity-manager/identity-manager.html +0 -114
  20. package/src/client/components/identity-manager/identity-manager.mts +356 -543
  21. package/src/client/components/keystone-creator/keystone-creator.mts +4 -3
  22. package/src/client/components/keystone-details/keystone-details.css +569 -0
  23. package/src/client/components/keystone-details/keystone-details.html +127 -0
  24. package/src/client/components/keystone-details/keystone-details.mts +1013 -0
  25. package/src/client/components/keystone-scrubber/SCRUBBER_IMPLEMENTATION.md +33 -0
  26. package/src/client/components/keystone-scrubber/keystone-scrubber.css +46 -0
  27. package/src/client/components/keystone-scrubber/keystone-scrubber.html +15 -0
  28. package/src/client/components/keystone-scrubber/keystone-scrubber.mts +356 -0
  29. package/src/client/ui/shell/space-gib-shell-service.mts +4 -0
  30. package/src/server/path-constants.mts +12 -0
  31. package/src/server/serve-gib/handlers/api/keystone/keystone-evolve.handler.mts +33 -0
  32. package/src/server/serve-gib/handlers/api/keystone/sso-config.handler.mts +66 -0
  33. package/src/server/serve-gib/handlers/api/keystone/sso-link.handler.mts +140 -0
  34. package/src/server/serve-gib/handlers/api/keystone/sso-login.handler.mts +190 -0
  35. package/src/server/server.mts +6 -0
  36. package/dist/client/chunk-734MMI4C.mjs +0 -42
  37. package/dist/client/chunk-734MMI4C.mjs.map +0 -7
  38. package/dist/client/chunk-SMOZ2D5E.mjs +0 -2049
@@ -228,10 +228,10 @@ function isExpired({ expirationTimestampUTC }) {
228
228
  function unique(arr) {
229
229
  return Array.from(new Set(arr));
230
230
  }
231
- function patchObject({ obj, value, path, pathDelimiter, logalot: logalot60 }) {
231
+ function patchObject({ obj, value, path, pathDelimiter, logalot: logalot63 }) {
232
232
  const lc2 = `[${patchObject.name}]`;
233
233
  try {
234
- if (logalot60) {
234
+ if (logalot63) {
235
235
  console.log(`${lc2} starting...`);
236
236
  }
237
237
  if (!obj) {
@@ -263,7 +263,7 @@ function patchObject({ obj, value, path, pathDelimiter, logalot: logalot60 }) {
263
263
  console.error(`${lc2} ${error.message}`);
264
264
  throw error;
265
265
  } finally {
266
- if (logalot60) {
266
+ if (logalot63) {
267
267
  console.log(`${lc2} complete.`);
268
268
  }
269
269
  }
@@ -477,6 +477,121 @@ var ServeGib_V1 = class _ServeGib_V1 {
477
477
  }
478
478
  };
479
479
 
480
+ // ../../libs/ts-gib/dist/V1/sha256v1.mjs
481
+ var crypto3 = globalThis.crypto;
482
+ var { subtle: subtle2 } = crypto3;
483
+ var BYTE_TO_HEX = new Array(256);
484
+ for (let n = 0; n < 256; ++n) {
485
+ BYTE_TO_HEX[n] = n.toString(16).padStart(2, "0");
486
+ }
487
+ function bufToHex(buffer) {
488
+ const bytes = new Uint8Array(buffer);
489
+ let hex = "";
490
+ for (let i = 0; i < bytes.length; i++) {
491
+ hex += BYTE_TO_HEX[bytes[i]];
492
+ }
493
+ return hex;
494
+ }
495
+ function toNormalizedForHashing(value) {
496
+ if (value === null || typeof value !== "object") {
497
+ return value;
498
+ }
499
+ if (Array.isArray(value)) {
500
+ return value.map((element) => toNormalizedForHashing(element));
501
+ }
502
+ const normalizedObject = {};
503
+ const sortedKeys = Object.keys(value).sort();
504
+ for (const key of sortedKeys) {
505
+ const propertyValue = value[key];
506
+ if (propertyValue !== void 0) {
507
+ normalizedObject[key] = toNormalizedForHashing(propertyValue);
508
+ }
509
+ }
510
+ return normalizedObject;
511
+ }
512
+ async function hashToHex(message) {
513
+ if (!message) {
514
+ return "";
515
+ }
516
+ const msgUint8 = new TextEncoder().encode(message);
517
+ const buffer = await subtle2.digest("SHA-256", msgUint8);
518
+ return bufToHex(buffer);
519
+ }
520
+ async function hashToHex_Uint8Array(salt, msgUint8) {
521
+ let tohashUint8Array;
522
+ if (salt) {
523
+ const msgUint8_salt = new TextEncoder().encode(salt);
524
+ tohashUint8Array = new Uint8Array(msgUint8_salt.length + msgUint8.length);
525
+ tohashUint8Array.set(msgUint8_salt);
526
+ tohashUint8Array.set(msgUint8, msgUint8_salt.length);
527
+ } else {
528
+ tohashUint8Array = msgUint8;
529
+ }
530
+ const hashAsBuffer = await subtle2.digest("SHA-256", tohashUint8Array);
531
+ return bufToHex(hashAsBuffer);
532
+ }
533
+ var PROFILE_SHA256 = false;
534
+ var sha256v1CallCount = 0;
535
+ var sha256v1TotalDuration = 0;
536
+ async function sha256v1_Internal(ibGib, salt = "") {
537
+ const s = salt || "";
538
+ const ib = ibGib.ib;
539
+ const data = ibGib.data;
540
+ const rel8ns = ibGib.rel8ns;
541
+ const hasRel8ns = Object.keys(rel8ns || {}).length > 0 && Object.keys(rel8ns || {}).some((k) => rel8ns[k] && rel8ns[k].length > 0);
542
+ let hasData = !!data;
543
+ if (hasData) {
544
+ if (typeof data === "string") {
545
+ hasData = data.length > 0;
546
+ } else if (data instanceof Uint8Array) {
547
+ hasData = true;
548
+ } else if (typeof data === "object") {
549
+ hasData = Object.keys(data || {}).length > 0;
550
+ } else {
551
+ hasData = true;
552
+ }
553
+ }
554
+ const ibHash = (await hashToHex(s ? s + ib : ib)).toUpperCase();
555
+ let rel8nsHash = "";
556
+ if (hasRel8ns) {
557
+ const normalizedRel8ns = toNormalizedForHashing(rel8ns);
558
+ const rel8nsStr = JSON.stringify(normalizedRel8ns);
559
+ rel8nsHash = (await hashToHex(s ? s + rel8nsStr : rel8nsStr)).toUpperCase();
560
+ }
561
+ let dataHash = "";
562
+ if (hasData) {
563
+ if (data instanceof Uint8Array) {
564
+ dataHash = (await hashToHex_Uint8Array(s, data)).toUpperCase();
565
+ } else {
566
+ const normalizedData = toNormalizedForHashing(data);
567
+ const dataStr = JSON.stringify(normalizedData);
568
+ dataHash = (await hashToHex(s ? s + dataStr : dataStr)).toUpperCase();
569
+ }
570
+ }
571
+ let allHash;
572
+ if (hasRel8ns || hasData) {
573
+ const combinedMsg = ibHash + rel8nsHash + dataHash;
574
+ allHash = (await hashToHex(s ? s + combinedMsg : combinedMsg)).toUpperCase();
575
+ } else {
576
+ allHash = (await hashToHex(s ? s + ibHash : ibHash)).toUpperCase();
577
+ }
578
+ return allHash;
579
+ }
580
+ async function sha256v1(ibGib, salt = "") {
581
+ if (PROFILE_SHA256) {
582
+ const start = globalThis.performance ? globalThis.performance.now() : Date.now();
583
+ const res = await sha256v1_Internal(ibGib, salt);
584
+ const duration = (globalThis.performance ? globalThis.performance.now() : Date.now()) - start;
585
+ sha256v1CallCount++;
586
+ sha256v1TotalDuration += duration;
587
+ if (sha256v1CallCount % 1e3 === 0) {
588
+ console.log(`[sha256v1 Profile] Total Runs: ${sha256v1CallCount} | Average Duration: ${(sha256v1TotalDuration / sha256v1CallCount).toFixed(4)} ms`);
589
+ }
590
+ return res;
591
+ }
592
+ return sha256v1_Internal(ibGib, salt);
593
+ }
594
+
480
595
  // ../../libs/ts-gib/dist/helper.mjs
481
596
  function getIbGibAddr({ ib, gib, ibGib, delimiter = "^" }) {
482
597
  ib = ib || ibGib?.ib || "";
@@ -516,114 +631,6 @@ function getIbAndGib({ ibGib, ibGibAddr, delimiter = "^" }) {
516
631
  }
517
632
  }
518
633
 
519
- // ../../libs/ts-gib/dist/V1/sha256v1.mjs
520
- var crypto3 = globalThis.crypto;
521
- var { subtle: subtle2 } = crypto3;
522
- function sha256v1(ibGib, salt = "") {
523
- if (!salt) {
524
- salt = "";
525
- }
526
- let hashToHex = async (message) => {
527
- if (!message) {
528
- return "";
529
- }
530
- const msgUint8 = new TextEncoder().encode(message);
531
- const buffer = await subtle2.digest("SHA-256", msgUint8);
532
- const asArray = Array.from(new Uint8Array(buffer));
533
- return asArray.map((b) => b.toString(16).padStart(2, "0")).join("");
534
- };
535
- let hashToHex_Uint8Array = async (salt2, msgUint8) => {
536
- let tohashUint8Array;
537
- if (salt2) {
538
- const msgUint8_salt = new TextEncoder().encode(salt2);
539
- tohashUint8Array = new Uint8Array(msgUint8_salt.length + msgUint8.length);
540
- tohashUint8Array.set(msgUint8_salt);
541
- tohashUint8Array.set(msgUint8, msgUint8_salt.length);
542
- } else {
543
- tohashUint8Array = msgUint8;
544
- }
545
- const hashAsBuffer = await subtle2.digest("SHA-256", tohashUint8Array);
546
- const hashAsArray = Array.from(new Uint8Array(hashAsBuffer));
547
- return hashAsArray.map((b) => b.toString(16).padStart(2, "0")).join("");
548
- };
549
- const toNormalizedForHashing = (value) => {
550
- if (value === null || typeof value !== "object") {
551
- return value;
552
- }
553
- if (Array.isArray(value)) {
554
- return value.map((element) => toNormalizedForHashing(element));
555
- }
556
- const normalizedObject = {};
557
- const sortedKeys = Object.keys(value).sort();
558
- for (const key of sortedKeys) {
559
- const propertyValue = value[key];
560
- if (propertyValue !== void 0) {
561
- normalizedObject[key] = toNormalizedForHashing(propertyValue);
562
- }
563
- }
564
- return normalizedObject;
565
- };
566
- let hashFields;
567
- if (salt) {
568
- hashFields = async (ib, data, rel8ns) => {
569
- const hasRel8ns = Object.keys(rel8ns || {}).length > 0 && Object.keys(rel8ns || {}).some((k) => rel8ns[k] && rel8ns[k].length > 0);
570
- let hasData = !!data;
571
- if (hasData) {
572
- if (typeof data === "string") {
573
- hasData = data.length > 0;
574
- } else if (data instanceof Uint8Array) {
575
- hasData = true;
576
- } else if (typeof data === "object") {
577
- hasData = Object.keys(data || {}).length > 0;
578
- } else {
579
- hasData = true;
580
- }
581
- }
582
- const ibHash = (await hashToHex(salt + ib)).toUpperCase();
583
- const rel8nsHash = hasRel8ns ? (await hashToHex(salt + JSON.stringify(toNormalizedForHashing(rel8ns)))).toUpperCase() : "";
584
- let dataHash = "";
585
- if (hasData) {
586
- if (data instanceof Uint8Array) {
587
- dataHash = (await hashToHex_Uint8Array(salt, data)).toUpperCase();
588
- } else {
589
- dataHash = (await hashToHex(salt + JSON.stringify(toNormalizedForHashing(data)))).toUpperCase();
590
- }
591
- }
592
- const allHash = hasRel8ns || hasData ? (await hashToHex(salt + ibHash + rel8nsHash + dataHash)).toUpperCase() : (await hashToHex(salt + ibHash)).toUpperCase();
593
- return allHash;
594
- };
595
- } else {
596
- hashFields = async (ib, data, rel8ns) => {
597
- const hasRel8ns = Object.keys(rel8ns || {}).length > 0 && Object.keys(rel8ns || {}).some((k) => rel8ns[k] && rel8ns[k].length > 0);
598
- let hasData = !!data;
599
- if (hasData) {
600
- if (typeof data === "string") {
601
- hasData = data.length > 0;
602
- } else if (data instanceof Uint8Array) {
603
- hasData = true;
604
- } else if (typeof data === "object") {
605
- hasData = Object.keys(data || {}).length > 0;
606
- } else {
607
- hasData = true;
608
- }
609
- }
610
- const ibHash = (await hashToHex(ib)).toUpperCase();
611
- const rel8nsHash = hasRel8ns ? (await hashToHex(JSON.stringify(toNormalizedForHashing(rel8ns)))).toUpperCase() : "";
612
- let dataHash = "";
613
- if (hasData) {
614
- if (data instanceof Uint8Array) {
615
- dataHash = (await hashToHex_Uint8Array("", data)).toUpperCase();
616
- } else {
617
- dataHash = (await hashToHex(JSON.stringify(toNormalizedForHashing(data)))).toUpperCase();
618
- }
619
- }
620
- const allHash = hasRel8ns || hasData ? (await hashToHex(ibHash + rel8nsHash + dataHash)).toUpperCase() : (await hashToHex(ibHash)).toUpperCase();
621
- return allHash;
622
- };
623
- }
624
- return hashFields(ibGib.ib, ibGib?.data, ibGib?.rel8ns);
625
- }
626
-
627
634
  // ../../libs/ts-gib/dist/V1/constants.mjs
628
635
  var IB = "ib";
629
636
  var GIB = "gib";
@@ -17319,7 +17326,19 @@ var API_PATH_REGEXES = {
17319
17326
  /**
17320
17327
  * /api/sync/ws/:domainAddr
17321
17328
  */
17322
- SYNC_WS: /^\/api\/sync\/ws\/([^\/]+?)(?:%5E|\^)([^\/]+)\/?$/
17329
+ SYNC_WS: /^\/api\/sync\/ws\/([^\/]+?)(?:%5E|\^)([^\/]+)\/?$/,
17330
+ /**
17331
+ * /api/identity/sso/link
17332
+ */
17333
+ IDENTITY_SSO_LINK: /^\/api\/identity\/sso\/link\/?$/,
17334
+ /**
17335
+ * /api/identity/sso/login
17336
+ */
17337
+ IDENTITY_SSO_LOGIN: /^\/api\/identity\/sso\/login\/?$/,
17338
+ /**
17339
+ * /api/identity/sso/config
17340
+ */
17341
+ IDENTITY_SSO_CONFIG: /^\/api\/identity\/sso\/config\/?$/
17323
17342
  };
17324
17343
  var VALID_STATIC_PATH_REGEX = /^\/[a-zA-Z0-9\-_\.\/]*$/;
17325
17344
 
@@ -17645,6 +17664,17 @@ var KeystoneReplenishStrategy = {
17645
17664
  deleteAll: KEYSTONE_REPLENISH_STRATEGY_DELETE_ALL
17646
17665
  };
17647
17666
  var KEYSTONE_REPLENISH_STRATEGY_VALID_VALUES = Object.values(KeystoneReplenishStrategy);
17667
+ var KEYSTONE_CLAIM_TYPE_ADD_POOL = "add-pool";
17668
+ var KEYSTONE_CLAIM_TYPE_REMOVE_POOL = "remove-pool";
17669
+ var KEYSTONE_CLAIM_TYPE_REPLACE_POOL = "replace-pool";
17670
+ var KEYSTONE_CLAIM_TYPE_CHANGE_PASSWORD = "change-password";
17671
+ var KeystoneClaimType = {
17672
+ add_pool: KEYSTONE_CLAIM_TYPE_ADD_POOL,
17673
+ remove_pool: KEYSTONE_CLAIM_TYPE_REMOVE_POOL,
17674
+ replace_pool: KEYSTONE_CLAIM_TYPE_REPLACE_POOL,
17675
+ change_password: KEYSTONE_CLAIM_TYPE_CHANGE_PASSWORD
17676
+ };
17677
+ var KEYSTONE_CLAIM_TYPE_VALID_VALUES = Object.values(KeystoneClaimType);
17648
17678
 
17649
17679
  // ../../libs/core-gib/dist/keystone/keystone-constants.mjs
17650
17680
  var KEYSTONE_ATOM = "keystone";
@@ -17692,7 +17722,6 @@ var KeystoneVerb = {
17692
17722
  CONNECT: KEYSTONE_VERB_CONNECT
17693
17723
  };
17694
17724
  var KEYSTONE_VERB_VALID_VALUES = Object.values(KeystoneVerb);
17695
- var POOL_ID_REVOKE = KEYSTONE_VERB_REVOKE;
17696
17725
  var POOL_ID_MANAGE = KEYSTONE_VERB_MANAGE;
17697
17726
  var POOL_ID_CONNECT = KEYSTONE_VERB_CONNECT;
17698
17727
  var POOL_ID_SYNC = KEYSTONE_VERB_SYNC;
@@ -18057,7 +18086,7 @@ async function generateOpaqueChallengeId({ salt, timestamp, index }) {
18057
18086
  const raw = await hash({ s: `${salt}${timestamp}${index}` });
18058
18087
  return raw.substring(0, 16);
18059
18088
  }
18060
- async function applyReplenishmentStrategy({ prevPools, targetPoolId, consumedIds, masterSecret, strategy, config }) {
18089
+ async function applyReplenishmentStrategy({ prevPools, targetPoolId, consumedIds, masterSecret, strategy, config, verb }) {
18061
18090
  const lc2 = `[applyReplenishmentStrategy]`;
18062
18091
  try {
18063
18092
  const newPools = JSON.parse(JSON.stringify(prevPools));
@@ -18068,7 +18097,10 @@ async function applyReplenishmentStrategy({ prevPools, targetPoolId, consumedIds
18068
18097
  const pool = newPools[targetIdx];
18069
18098
  const poolSecret = await strategy.derivePoolSecret({ masterSecret });
18070
18099
  const timestamp = Date.now().toString();
18071
- const strategyType = config.behavior.replenish;
18100
+ let strategyType = config.behavior.replenish;
18101
+ if (verb === KEYSTONE_VERB_REVOKE) {
18102
+ strategyType = KeystoneReplenishStrategy.deleteAll;
18103
+ }
18072
18104
  if (strategyType === KeystoneReplenishStrategy.topUp) {
18073
18105
  consumedIds.forEach((id) => delete pool.challenges[id]);
18074
18106
  for (let i = 0; i < consumedIds.length; i++) {
@@ -18085,20 +18117,10 @@ async function applyReplenishmentStrategy({ prevPools, targetPoolId, consumedIds
18085
18117
  pool.challenges[newId] = await strategy.generateChallenge({ solution });
18086
18118
  }
18087
18119
  } else if (strategyType === KeystoneReplenishStrategy.replaceAll) {
18088
- pool.challenges = {};
18089
- for (let i = 0; i < config.behavior.size; i++) {
18090
- const newId = await generateOpaqueChallengeId({
18091
- salt: config.salt,
18092
- timestamp,
18093
- index: i
18094
- });
18095
- const solution = await strategy.generateSolution({
18096
- poolSecret,
18097
- poolId: pool.id,
18098
- challengeId: newId
18099
- });
18100
- pool.challenges[newId] = await strategy.generateChallenge({ solution });
18101
- }
18120
+ pool.challenges = await generatePoolChallenges({
18121
+ config,
18122
+ masterSecret
18123
+ });
18102
18124
  } else if (strategyType === KeystoneReplenishStrategy.consume) {
18103
18125
  consumedIds.forEach((id) => delete pool.challenges[id]);
18104
18126
  } else if (strategyType === KeystoneReplenishStrategy.deleteAll) {
@@ -18124,6 +18146,10 @@ async function solveAndReplenish({ targetPoolId, prevPools, masterSecret, challe
18124
18146
  }
18125
18147
  const strategy = KeystoneStrategyFactory.create({ config: pool.config });
18126
18148
  const poolSecret = await strategy.derivePoolSecret({ masterSecret });
18149
+ const isSecretCorrect = await verifyPoolSecret({ pool, signingSecret: masterSecret });
18150
+ if (!isSecretCorrect) {
18151
+ throw new Error(`Crypto Violation: Invalid signing secret for pool: ${pool.id} (E: 8a4c2b1d3e5f6a7b8c9d0e1f2a3b4c5f)`);
18152
+ }
18127
18153
  const solutions = [];
18128
18154
  for (const id of challengeIds) {
18129
18155
  const solution = await strategy.generateSolution({
@@ -18144,7 +18170,8 @@ async function solveAndReplenish({ targetPoolId, prevPools, masterSecret, challe
18144
18170
  consumedIds: challengeIds,
18145
18171
  masterSecret,
18146
18172
  strategy,
18147
- config: pool.config
18173
+ config: pool.config,
18174
+ verb: claim.verb
18148
18175
  });
18149
18176
  return { proof, nextPools };
18150
18177
  } catch (error) {
@@ -18417,6 +18444,152 @@ async function validateKeystoneTransition({ currentIbGib, prevIbGib }) {
18417
18444
  errors.push(`Revocation target mismatch. Expected ${expectedTarget}, got ${target}`);
18418
18445
  }
18419
18446
  }
18447
+ const signingPoolIds = new Set(currData.proofs.map((p) => p.solutions?.[0]?.poolId).filter(Boolean));
18448
+ for (const proof of currData.proofs) {
18449
+ const claim = proof.claim;
18450
+ if (claim?.details) {
18451
+ const details = claim.details;
18452
+ const type = details.type;
18453
+ const info = details.info;
18454
+ if (type === "add-pool") {
18455
+ const addInfo = info;
18456
+ if (!addInfo || !Array.isArray(addInfo.add) || addInfo.add.length === 0) {
18457
+ errors.push(`details type is '${type}' but payload is invalid. (E: 1fa2a8cb28c9b60e68994511a5825)`);
18458
+ continue;
18459
+ }
18460
+ const prevIds = new Set(prevData.challengePools.map((p) => p.id));
18461
+ const currIds = new Set(currData.challengePools.map((p) => p.id));
18462
+ for (const id of addInfo.add) {
18463
+ if (!currIds.has(id)) {
18464
+ errors.push(`ClaimDetails_AddPool lists pool ${id} but it is missing in the evolved keystone. (E: 2fa2a8cb28c9b60e68994511a5825)`);
18465
+ }
18466
+ if (prevIds.has(id)) {
18467
+ errors.push(`ClaimDetails_AddPool attempts to add existing pool ${id}. (E: 3fa2a8cb28c9b60e68994511a5825)`);
18468
+ }
18469
+ }
18470
+ if (currData.challengePools.length !== prevData.challengePools.length + addInfo.add.length) {
18471
+ errors.push(`Structural mismatch: expected ${prevData.challengePools.length + addInfo.add.length} pools, found ${currData.challengePools.length}. (E: 4fa2a8cb28c9b60e68994511a5825)`);
18472
+ }
18473
+ for (const prevPool of prevData.challengePools) {
18474
+ const currPool = currData.challengePools.find((p) => p.id === prevPool.id);
18475
+ if (!currPool) {
18476
+ errors.push(`Existing pool ${prevPool.id} was removed during add-pool transition. (E: 5fa2a8cb28c9b60e68994511a5825)`);
18477
+ continue;
18478
+ }
18479
+ if (JSON.stringify(prevPool.config) !== JSON.stringify(currPool.config)) {
18480
+ errors.push(`Existing pool config ${prevPool.id} was mutated during add-pool transition. (E: 6fa2a8cb28c9b60e68994511a5825)`);
18481
+ }
18482
+ if (!signingPoolIds.has(prevPool.id) && JSON.stringify(prevPool.challenges) !== JSON.stringify(currPool.challenges)) {
18483
+ errors.push(`Existing pool challenges ${prevPool.id} were mutated during add-pool transition. (E: 7fa2a8cb28c9b60e68994511a5825)`);
18484
+ }
18485
+ }
18486
+ } else if (type === "replace-pool") {
18487
+ const replaceInfo = info;
18488
+ if (!replaceInfo || typeof replaceInfo.replace !== "string" || !replaceInfo.replace) {
18489
+ errors.push(`details type is '${type}' but payload is invalid. (E: 8fa2a8cb28c9b60e68994511a5825)`);
18490
+ continue;
18491
+ }
18492
+ const targetId = replaceInfo.replace;
18493
+ const prevIds = prevData.challengePools.map((p) => p.id).sort();
18494
+ const currIds = currData.challengePools.map((p) => p.id).sort();
18495
+ if (JSON.stringify(prevIds) !== JSON.stringify(currIds)) {
18496
+ errors.push(`replace-pool transition mutated the pool IDs set. (E: 9fa2a8cb28c9b60e68994511a5825)`);
18497
+ }
18498
+ const prevTarget = prevData.challengePools.find((p) => p.id === targetId);
18499
+ const currTarget = currData.challengePools.find((p) => p.id === targetId);
18500
+ if (!prevTarget || !currTarget) {
18501
+ errors.push(`Replaced pool ${targetId} not found in keystone challenge pools. (E: afa2a8cb28c9b60e68994511a5825)`);
18502
+ }
18503
+ for (const prevPool of prevData.challengePools) {
18504
+ if (prevPool.id === targetId)
18505
+ continue;
18506
+ const currPool = currData.challengePools.find((p) => p.id === prevPool.id);
18507
+ if (!currPool)
18508
+ continue;
18509
+ if (JSON.stringify(prevPool.config) !== JSON.stringify(currPool.config)) {
18510
+ errors.push(`Unrelated pool config ${prevPool.id} was mutated during replace-pool. (E: bfa2a8cb28c9b60e68994511a5825)`);
18511
+ }
18512
+ if (!signingPoolIds.has(prevPool.id) && JSON.stringify(prevPool.challenges) !== JSON.stringify(currPool.challenges)) {
18513
+ errors.push(`Unrelated pool challenges ${prevPool.id} were mutated during replace-pool. (E: cfa2a8cb28c9b60e68994511a5825)`);
18514
+ }
18515
+ }
18516
+ } else if (type === "remove-pool") {
18517
+ const removeInfo = info;
18518
+ if (!removeInfo || !Array.isArray(removeInfo.remove) || removeInfo.remove.length === 0) {
18519
+ errors.push(`details type is '${type}' but payload is invalid. (E: b7a2a8cb28c9b60e68994511a5825)`);
18520
+ continue;
18521
+ }
18522
+ const removeSet = new Set(removeInfo.remove);
18523
+ const prevIds = new Set(prevData.challengePools.map((p) => p.id));
18524
+ const currIds = new Set(currData.challengePools.map((p) => p.id));
18525
+ for (const id of removeInfo.remove) {
18526
+ if (!prevIds.has(id)) {
18527
+ errors.push(`ClaimDetails_RemovePool lists pool ${id} but it is missing in the previous keystone. (E: c7a2a8cb28c9b60e68994511a5825)`);
18528
+ }
18529
+ if (currIds.has(id)) {
18530
+ errors.push(`ClaimDetails_RemovePool lists pool ${id} but it is still present in the evolved keystone. (E: d7a2a8cb28c9b60e68994511a5825)`);
18531
+ }
18532
+ }
18533
+ if (currData.challengePools.length !== prevData.challengePools.length - removeInfo.remove.length) {
18534
+ errors.push(`Structural mismatch: expected ${prevData.challengePools.length - removeInfo.remove.length} pools, found ${currData.challengePools.length}. (E: e7a2a8cb28c9b60e68994511a5825)`);
18535
+ }
18536
+ for (const prevPool of prevData.challengePools) {
18537
+ if (removeSet.has(prevPool.id))
18538
+ continue;
18539
+ const currPool = currData.challengePools.find((p) => p.id === prevPool.id);
18540
+ if (!currPool) {
18541
+ errors.push(`Pool ${prevPool.id} was unexpectedly removed. (E: f7a2a8cb28c9b60e68994511a5825)`);
18542
+ continue;
18543
+ }
18544
+ if (JSON.stringify(prevPool.config) !== JSON.stringify(currPool.config)) {
18545
+ errors.push(`Remaining pool config ${prevPool.id} was mutated during remove-pool. (E: 07a2a8cb28c9b60e68994511a5825)`);
18546
+ }
18547
+ if (!signingPoolIds.has(prevPool.id) && JSON.stringify(prevPool.challenges) !== JSON.stringify(currPool.challenges)) {
18548
+ errors.push(`Remaining pool challenges ${prevPool.id} were mutated during remove-pool. (E: 17a2a8cb28c9b60e68994511a5825)`);
18549
+ }
18550
+ }
18551
+ } else if (type === "change-password") {
18552
+ const changeInfo = info;
18553
+ if (!changeInfo || !Array.isArray(changeInfo.change) || changeInfo.change.length === 0) {
18554
+ errors.push(`details type is '${type}' but payload is invalid. (E: dfa2a8cb28c9b60e68994511a5825)`);
18555
+ continue;
18556
+ }
18557
+ const prevIds = prevData.challengePools.map((p) => p.id).sort();
18558
+ const currIds = currData.challengePools.map((p) => p.id).sort();
18559
+ if (JSON.stringify(prevIds) !== JSON.stringify(currIds)) {
18560
+ errors.push(`change-password transition mutated the pool IDs set. (E: efa2a8cb28c9b60e68994511a5825)`);
18561
+ }
18562
+ const rotatedIds = new Set(changeInfo.change);
18563
+ for (const prevPool of prevData.challengePools) {
18564
+ const currPool = currData.challengePools.find((p) => p.id === prevPool.id);
18565
+ if (!currPool)
18566
+ continue;
18567
+ if (rotatedIds.has(prevPool.id)) {
18568
+ if (prevPool.isForeign) {
18569
+ errors.push(`Security Violation: Password rotation attempted on foreign pool ${prevPool.id}. (E: ffa2a8cb28c9b60e68994511a5825)`);
18570
+ }
18571
+ const prevConfCopy = { ...prevPool.config, salt: "" };
18572
+ const currConfCopy = { ...currPool.config, salt: "" };
18573
+ if (JSON.stringify(prevConfCopy) !== JSON.stringify(currConfCopy)) {
18574
+ errors.push(`Pool config metadata for rotated pool ${prevPool.id} was mutated. (E: 00b2a8cb28c9b60e68994511a5825)`);
18575
+ }
18576
+ if (prevPool.config.salt === currPool.config.salt) {
18577
+ errors.push(`Pool salt for rotated pool ${prevPool.id} was not rotated. (E: 01b2a8cb28c9b60e68994511a5825)`);
18578
+ }
18579
+ } else {
18580
+ if (JSON.stringify(prevPool.config) !== JSON.stringify(currPool.config)) {
18581
+ errors.push(`Unrelated pool config ${prevPool.id} was mutated during change-password. (E: 02b2a8cb28c9b60e68994511a5825)`);
18582
+ }
18583
+ if (!signingPoolIds.has(prevPool.id) && JSON.stringify(prevPool.challenges) !== JSON.stringify(currPool.challenges)) {
18584
+ errors.push(`Unrelated pool challenges ${prevPool.id} were mutated during change-password. (E: 03b2a8cb28c9b60e68994511a5825)`);
18585
+ }
18586
+ }
18587
+ }
18588
+ } else {
18589
+ errors.push(`Unknown claim details type: ${type}`);
18590
+ }
18591
+ }
18592
+ }
18420
18593
  return errors;
18421
18594
  } catch (error) {
18422
18595
  console.error(`${lc2} ${extractErrorMsg(error)}`);
@@ -18704,6 +18877,65 @@ async function validateKeystoneGraph({ keystoneIbGib, dependencyGraph, getLatest
18704
18877
  }
18705
18878
  }
18706
18879
  }
18880
+ async function generatePoolChallenges({ config, masterSecret }) {
18881
+ const strategy = KeystoneStrategyFactory.create({ config });
18882
+ const poolSecret = await strategy.derivePoolSecret({ masterSecret });
18883
+ const challenges = {};
18884
+ const targetSize = config.behavior.size;
18885
+ const timestamp = Date.now().toString();
18886
+ for (let i = 0; i < targetSize; i++) {
18887
+ const challengeId = await generateOpaqueChallengeId({
18888
+ salt: config.salt,
18889
+ timestamp,
18890
+ index: i
18891
+ });
18892
+ const solution = await strategy.generateSolution({
18893
+ poolSecret,
18894
+ poolId: config.id,
18895
+ challengeId
18896
+ });
18897
+ challenges[challengeId] = await strategy.generateChallenge({ solution });
18898
+ }
18899
+ return challenges;
18900
+ }
18901
+ async function rotatePoolChallenges({ prevPool, newMasterSecret }) {
18902
+ const prevConfig = prevPool.config;
18903
+ const newSalt = (await getUUID()).substring(0, 16);
18904
+ const newConfig = {
18905
+ ...prevConfig,
18906
+ salt: newSalt
18907
+ };
18908
+ const newChallenges = await generatePoolChallenges({
18909
+ config: newConfig,
18910
+ masterSecret: newMasterSecret
18911
+ });
18912
+ return {
18913
+ id: prevPool.id,
18914
+ config: newConfig,
18915
+ challenges: newChallenges,
18916
+ ...prevPool.isForeign !== void 0 ? { isForeign: prevPool.isForeign } : {},
18917
+ ...prevPool.metadata ? { metadata: prevPool.metadata } : {}
18918
+ };
18919
+ }
18920
+ async function verifyPoolSecret({ pool, signingSecret }) {
18921
+ const challengeIds = Object.keys(pool.challenges);
18922
+ if (challengeIds.length === 0)
18923
+ return false;
18924
+ try {
18925
+ const firstId = challengeIds[0];
18926
+ const challenge = pool.challenges[firstId];
18927
+ const strategy = KeystoneStrategyFactory.create({ config: pool.config });
18928
+ const poolSecret = await strategy.derivePoolSecret({ masterSecret: signingSecret });
18929
+ const solution = await strategy.generateSolution({
18930
+ poolSecret,
18931
+ poolId: pool.id,
18932
+ challengeId: firstId
18933
+ });
18934
+ return await strategy.validateSolution({ solution, challenge });
18935
+ } catch {
18936
+ return false;
18937
+ }
18938
+ }
18707
18939
 
18708
18940
  // ../../libs/core-gib/dist/keystone/keystone-service-v1.mjs
18709
18941
  var logalot41 = GLOBAL_LOG_A_LOT2;
@@ -18712,7 +18944,7 @@ var KeystoneService_V1 = class _KeystoneService_V1 {
18712
18944
  /**
18713
18945
  * Creates a brand new Keystone Identity Timeline.
18714
18946
  */
18715
- async genesis({ masterSecret, frameDetails, configs, metaspace, space }) {
18947
+ async genesis({ masterSecret, frameDetails, configs, metaspace, space, isPrimary }) {
18716
18948
  const lc2 = `${this.lc}[${this.genesis.name}]`;
18717
18949
  try {
18718
18950
  if (logalot41) {
@@ -18720,25 +18952,10 @@ var KeystoneService_V1 = class _KeystoneService_V1 {
18720
18952
  }
18721
18953
  const challengePools = [];
18722
18954
  for (const config of configs) {
18723
- const strategy = KeystoneStrategyFactory.create({ config });
18724
- const poolSecret = await strategy.derivePoolSecret({ masterSecret });
18725
- const challenges = {};
18726
- const targetSize = config.behavior.size;
18727
- const timestamp = Date.now().toString();
18728
- for (let i = 0; i < targetSize; i++) {
18729
- const challengeId = await generateOpaqueChallengeId({
18730
- salt: config.salt,
18731
- timestamp,
18732
- index: i
18733
- });
18734
- const solution = await strategy.generateSolution({
18735
- poolSecret,
18736
- poolId: config.id,
18737
- challengeId
18738
- });
18739
- const challenge = await strategy.generateChallenge({ solution });
18740
- challenges[challengeId] = challenge;
18741
- }
18955
+ const challenges = await generatePoolChallenges({
18956
+ config,
18957
+ masterSecret
18958
+ });
18742
18959
  challengePools.push({
18743
18960
  id: config.id,
18744
18961
  config,
@@ -18749,8 +18966,11 @@ var KeystoneService_V1 = class _KeystoneService_V1 {
18749
18966
  throw new Error(`No challenge pools created. (E: 38e538530996940e1f16a8b199995825)`);
18750
18967
  }
18751
18968
  const data = { challengePools, proofs: [] };
18752
- if (frameDetails) {
18753
- data.frameDetails = frameDetails;
18969
+ if (frameDetails || isPrimary !== void 0) {
18970
+ data.frameDetails = { ...frameDetails || {} };
18971
+ if (isPrimary !== void 0) {
18972
+ data.frameDetails.isPrimary = isPrimary;
18973
+ }
18754
18974
  }
18755
18975
  const keystoneIbGib = await createKeystoneIbGibImpl({ data, metaspace, space });
18756
18976
  return keystoneIbGib;
@@ -19095,8 +19315,7 @@ var KeystoneService_V1 = class _KeystoneService_V1 {
19095
19315
  const prevData = latestKeystone.data;
19096
19316
  const pool = resolveTargetPool({
19097
19317
  pools: prevData.challengePools,
19098
- poolId: POOL_ID_REVOKE
19099
- // Explicitly require the special revoke pool
19318
+ verb: KEYSTONE_VERB_REVOKE
19100
19319
  });
19101
19320
  const claim = {
19102
19321
  verb: KEYSTONE_VERB_REVOKE,
@@ -19196,8 +19415,10 @@ var KeystoneService_V1 = class _KeystoneService_V1 {
19196
19415
  verb: KEYSTONE_VERB_MANAGE,
19197
19416
  target,
19198
19417
  // I am managing myself
19199
- // Scope creates a cryptographic commitment to WHICH pools are being added
19200
- scope: JSON.stringify({ add: newPools.map((p) => p.id) })
19418
+ details: {
19419
+ type: KeystoneClaimType.add_pool,
19420
+ info: { add: newPools.map((p) => p.id) }
19421
+ }
19201
19422
  };
19202
19423
  const idsToSolve = await selectChallengeIds({
19203
19424
  pool: adminPool,
@@ -19253,6 +19474,98 @@ var KeystoneService_V1 = class _KeystoneService_V1 {
19253
19474
  }
19254
19475
  }
19255
19476
  }
19477
+ /**
19478
+ * Structural evolution: Removes challenge pools from the keystone.
19479
+ *
19480
+ * Use Case: Unlinking a custodian or delegate SSO pool.
19481
+ *
19482
+ * Requires the Master Secret to authorize the change via a pool containing
19483
+ * the 'manage' verb (the administrative manage pool).
19484
+ */
19485
+ async removePools({ latestKeystone, masterSecret, poolIds, metaspace, space }) {
19486
+ const lc2 = `${this.lc}[${this.removePools.name}]`;
19487
+ try {
19488
+ if (logalot41) {
19489
+ console.log(`${lc2} starting...`);
19490
+ }
19491
+ if (!latestKeystone.data) {
19492
+ throw new Error(`(UNEXPECTED) latestKeystone.data falsy? (E: 8334c8faed128166a999d428c7805b25)`);
19493
+ }
19494
+ const prevData = latestKeystone.data;
19495
+ if (prevData.revocationInfo) {
19496
+ throw new Error(`Keystone has been revoked. Cannot remove pools. (E: 9599f8f51c78d722252ddb2894fdbe25)`);
19497
+ }
19498
+ if (poolIds.length === 0) {
19499
+ throw new Error(`No pool IDs provided to remove. (E: 7599f8f51c78d722252ddb2894fdbe25)`);
19500
+ }
19501
+ const adminPool = resolveTargetPool({
19502
+ pools: prevData.challengePools,
19503
+ verb: KEYSTONE_VERB_MANAGE
19504
+ });
19505
+ if (logalot41) {
19506
+ console.log(`${lc2} Authorized via pool: ${adminPool.id}`);
19507
+ }
19508
+ const target = getIbGibAddr({ ibGib: latestKeystone });
19509
+ const claim = {
19510
+ verb: KEYSTONE_VERB_MANAGE,
19511
+ target,
19512
+ details: {
19513
+ type: KeystoneClaimType.remove_pool,
19514
+ info: { remove: poolIds }
19515
+ }
19516
+ };
19517
+ const idsToSolve = await selectChallengeIds({
19518
+ pool: adminPool,
19519
+ targetAddr: target
19520
+ });
19521
+ const { proof, nextPools: replenishedExistingPools } = await solveAndReplenish({
19522
+ targetPoolId: adminPool.id,
19523
+ prevPools: prevData.challengePools,
19524
+ masterSecret,
19525
+ challengeIds: idsToSolve,
19526
+ claim
19527
+ });
19528
+ const removeSet = new Set(poolIds);
19529
+ const finalPools = replenishedExistingPools.filter((p) => !removeSet.has(p.id));
19530
+ if (finalPools.length !== replenishedExistingPools.length - poolIds.length) {
19531
+ const missingIds = poolIds.filter((id) => !replenishedExistingPools.some((p) => p.id === id));
19532
+ throw new Error(`Cannot remove pool. Pool IDs not found in keystone: ${missingIds.join(", ")} (E: 8a4c2b1d3e5f6a7b8c9d0e1f2a3b4c5e)`);
19533
+ }
19534
+ const n = (prevData.n ?? 0) + 1;
19535
+ let checkpointDetails;
19536
+ if (n % KEYSTONE_CHECKPOINT_FREQUENCY === 0) {
19537
+ const currentFrameView = {
19538
+ ...latestKeystone,
19539
+ data: { ...prevData, challengePools: finalPools }
19540
+ };
19541
+ checkpointDetails = await this.getAggregateDetails({
19542
+ latestKeystone: currentFrameView,
19543
+ metaspace,
19544
+ space
19545
+ });
19546
+ }
19547
+ const newData = {
19548
+ challengePools: finalPools,
19549
+ proofs: [proof],
19550
+ checkpointDetails,
19551
+ ...prevData.delegates ? { delegates: prevData.delegates } : {}
19552
+ };
19553
+ const newKeystone = await evolvePersistAndRegisterKeystone({
19554
+ prevIbGib: latestKeystone,
19555
+ newData,
19556
+ metaspace,
19557
+ space
19558
+ });
19559
+ return newKeystone;
19560
+ } catch (error) {
19561
+ console.error(`${lc2} ${extractErrorMsg(error)}`);
19562
+ throw error;
19563
+ } finally {
19564
+ if (logalot41) {
19565
+ console.log(`${lc2} complete.`);
19566
+ }
19567
+ }
19568
+ }
19256
19569
  /**
19257
19570
  * Consolidates the state of a Keystone identity across its history.
19258
19571
  * Walks back until it hits a checkpoint or genesis.
@@ -19297,29 +19610,328 @@ var KeystoneService_V1 = class _KeystoneService_V1 {
19297
19610
  throw error;
19298
19611
  }
19299
19612
  }
19300
- };
19301
-
19302
- // ../../libs/core-gib/dist/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-receiver/sync-websocket-peer-helpers.mjs
19303
- var logalot42 = GLOBAL_LOG_A_LOT2;
19304
- var SESSION_KEYSTONE_POLICY = {
19305
- COMMON: {
19306
- ALGO: HashAlgorithm.sha_256,
19307
- TYPE: KeystoneChallengeType.hash_reveal_v1,
19308
- ROUNDS: 2,
19309
- REPLENISH: KeystoneReplenishStrategy.topUp
19310
- },
19311
- CONNECT_POOL: {
19312
- ID: POOL_ID_CONNECT,
19313
- VERB: KEYSTONE_VERB_CONNECT,
19314
- SIZE: 10,
19315
- SELECT_SEQUENTIALLY: 2,
19316
- SELECT_RANDOMLY: 2,
19317
- TARGET_BINDING_COUNT: 0,
19318
- SERVER_DEMAND_COUNT: 3
19319
- }
19320
- };
19321
- function getConnectChallenge(keystone) {
19322
- const lc2 = `[${getConnectChallenge.name}]`;
19613
+ /**
19614
+ * Retrieves common aggregated info for a keystone timeline.
19615
+ */
19616
+ async getKeystoneCommonInfo({ addr, ibGib, metaspace, space }) {
19617
+ const lc2 = `${this.lc}[getKeystoneCommonInfo]`;
19618
+ try {
19619
+ let targetAddr = addr;
19620
+ let targetIbGib = ibGib;
19621
+ if (!targetAddr) {
19622
+ if (!targetIbGib) {
19623
+ return null;
19624
+ }
19625
+ targetAddr = getIbGibAddr({ ibGib: targetIbGib });
19626
+ }
19627
+ if (!targetIbGib) {
19628
+ const { ib, gib } = getIbAndGib({ ibGibAddr: targetAddr });
19629
+ targetIbGib = { ib, gib };
19630
+ }
19631
+ const resGetLatest = await getLatestAddrs({
19632
+ ibGibs: [targetIbGib],
19633
+ space
19634
+ });
19635
+ if (!resGetLatest.data?.latestAddrsMap) {
19636
+ return null;
19637
+ }
19638
+ const latestAddr = resGetLatest.data.latestAddrsMap[targetAddr] || Object.values(resGetLatest.data.latestAddrsMap)[0];
19639
+ if (!latestAddr) {
19640
+ return null;
19641
+ }
19642
+ const resGet = await metaspace.get({ addr: latestAddr, space });
19643
+ const latestKeystone = resGet.ibGibs?.at(0);
19644
+ if (!latestKeystone || !latestKeystone.data) {
19645
+ return null;
19646
+ }
19647
+ const aggregateDetails = await this.getAggregateDetails({
19648
+ latestKeystone,
19649
+ metaspace,
19650
+ space
19651
+ });
19652
+ const name = aggregateDetails?.username || aggregateDetails?.profile?.name || aggregateDetails?.name || "";
19653
+ const description = aggregateDetails?.description || aggregateDetails?.profile?.description || "";
19654
+ const isPrimary = !!aggregateDetails?.isPrimary || !!aggregateDetails?.profile?.isPrimary;
19655
+ const latestFrameDetails = latestKeystone.data.frameDetails || {};
19656
+ const n = latestKeystone.data.n ?? 0;
19657
+ const timestamp = latestFrameDetails?.timestamp || aggregateDetails?.timestamp || "";
19658
+ const timestampMs = latestFrameDetails?.timestampMs || aggregateDetails?.timestampMs || void 0;
19659
+ return {
19660
+ name,
19661
+ description,
19662
+ isPrimary,
19663
+ aggregateDetails,
19664
+ latestFrameDetails,
19665
+ n,
19666
+ timestamp,
19667
+ timestampMs
19668
+ };
19669
+ } catch (error) {
19670
+ console.error(`${lc2} ${extractErrorMsg(error)}`);
19671
+ return null;
19672
+ }
19673
+ }
19674
+ /**
19675
+ * Replaces a challenge pool (such as manage or custodian-manage) with a new pool,
19676
+ * authorizing the transition via the specified signing pool.
19677
+ *
19678
+ * @param latestKeystone The current tip frame of the keystone timeline.
19679
+ * - Visibility: Public (Public Merkle frame, safe for logs/ledger).
19680
+ * - Generator: Client/Server (Constructed locally and synced).
19681
+ *
19682
+ * @param newPool The fully constructed new pool to place in the next frame.
19683
+ * - Visibility: Public (Public configuration and public challenges, safe for ledger).
19684
+ * - Generator: Client/Server (Generated dynamically by the party proposing the update).
19685
+ *
19686
+ * @param signingSecret The master secret or derived KDF secret of the pool authorizing the change.
19687
+ * - Visibility: Private (Confidential secret credential. Must never go over the wire).
19688
+ * - Generator: Client/Server (Known only to the signing party).
19689
+ *
19690
+ * @param signingPoolId The ID of the pool used to authorize the transition (defaults to 'manage').
19691
+ * - Visibility: Public (Non-sensitive configuration string).
19692
+ * - Generator: Client/Server (From pool configuration).
19693
+ */
19694
+ async replacePool({ latestKeystone, newPool, signingSecret, signingPoolId = POOL_ID_MANAGE, frameDetails, metaspace, space }) {
19695
+ const lc2 = `${this.lc}[${this.replacePool.name}]`;
19696
+ try {
19697
+ if (logalot41) {
19698
+ console.log(`${lc2} starting... replacing pool: ${newPool.id}`);
19699
+ }
19700
+ if (!latestKeystone.data) {
19701
+ throw new Error(`(UNEXPECTED) latestKeystone.data falsy? (E: 1334c8faed128166a999d428c7805b25)`);
19702
+ }
19703
+ const prevData = latestKeystone.data;
19704
+ if (prevData.revocationInfo) {
19705
+ throw new Error(`Keystone has been revoked. Cannot replace pool. (E: 2599f8f51c78d722252ddb2894fdbe25)`);
19706
+ }
19707
+ const prevPools = prevData.challengePools;
19708
+ const signingPool = prevPools.find((p) => p.id === signingPoolId);
19709
+ if (!signingPool) {
19710
+ throw new Error(`Signing pool not found: ${signingPoolId} (E: 3599f8f51c78d722252ddb2894fdbe25)`);
19711
+ }
19712
+ const target = getIbGibAddr({ ibGib: latestKeystone });
19713
+ const claim = {
19714
+ verb: KEYSTONE_VERB_MANAGE,
19715
+ target,
19716
+ details: {
19717
+ type: KeystoneClaimType.replace_pool,
19718
+ info: { replace: newPool.id }
19719
+ }
19720
+ };
19721
+ const idsToSolve = await selectChallengeIds({
19722
+ pool: signingPool,
19723
+ targetAddr: target
19724
+ });
19725
+ const { proof, nextPools } = await solveAndReplenish({
19726
+ targetPoolId: signingPoolId,
19727
+ prevPools,
19728
+ masterSecret: signingSecret,
19729
+ challengeIds: idsToSolve,
19730
+ claim
19731
+ });
19732
+ const targetIdx = nextPools.findIndex((p) => p.id === newPool.id);
19733
+ if (targetIdx === -1) {
19734
+ throw new Error(`Target pool to replace '${newPool.id}' not found in keystone challenge pools.`);
19735
+ }
19736
+ nextPools[targetIdx] = newPool;
19737
+ const n = (prevData.n ?? 0) + 1;
19738
+ let checkpointDetails;
19739
+ if (n % KEYSTONE_CHECKPOINT_FREQUENCY === 0) {
19740
+ const currentFrameView = {
19741
+ ...latestKeystone,
19742
+ data: { ...prevData, frameDetails }
19743
+ };
19744
+ checkpointDetails = await this.getAggregateDetails({
19745
+ latestKeystone: currentFrameView,
19746
+ metaspace,
19747
+ space
19748
+ });
19749
+ }
19750
+ const newData = {
19751
+ challengePools: nextPools,
19752
+ proofs: [proof],
19753
+ frameDetails,
19754
+ checkpointDetails,
19755
+ ...prevData.delegates ? { delegates: prevData.delegates } : {}
19756
+ };
19757
+ const newKeystone = await evolvePersistAndRegisterKeystone({
19758
+ prevIbGib: latestKeystone,
19759
+ newData,
19760
+ metaspace,
19761
+ space
19762
+ });
19763
+ return newKeystone;
19764
+ } catch (error) {
19765
+ console.error(`${lc2} ${extractErrorMsg(error)}`);
19766
+ throw error;
19767
+ } finally {
19768
+ if (logalot41) {
19769
+ console.log(`${lc2} complete.`);
19770
+ }
19771
+ }
19772
+ }
19773
+ /**
19774
+ * Evolve the keystone by rotating the primary user manage pool (`manage`) to a new master secret (passphrase).
19775
+ * This creates a new frame signed by the specified `signingPoolId` using `signingSecret`.
19776
+ *
19777
+ * Supports recovery (signing via `custodian-manage` using the custodian secret) and normal rotation
19778
+ * (signing via `manage` using the old password).
19779
+ *
19780
+ * @param latestKeystone The current tip frame of the keystone.
19781
+ * - Visibility: Public (Public Merkle frame, safe for ledger).
19782
+ * - Generator: Client/Server (Constructed locally and synced).
19783
+ *
19784
+ * @param newMasterSecret The new passphrase/master secret to set for the user's primary pools.
19785
+ * - Visibility: Private (Confidential secret credential. Must never go over the wire).
19786
+ * - Generator: Client (From user input in settings).
19787
+ *
19788
+ * @param signingSecret The secret of the pool authorizing this change (either old passphrase or derived custodian secret).
19789
+ * - Visibility: Private (Confidential secret. Must never go over the wire).
19790
+ * - Generator: Client/Server (Known only to the signing party).
19791
+ *
19792
+ * @param signingPoolId The ID of the pool used to authorize the rotation (defaults to 'manage').
19793
+ * - Visibility: Public (Non-sensitive ID string).
19794
+ * - Generator: Client/Server (From pool configuration).
19795
+ */
19796
+ async changePassword({ latestKeystone, newMasterSecret, signingSecret, signingPoolId = POOL_ID_MANAGE, poolIds, frameDetails, metaspace, space }) {
19797
+ const lc2 = `${this.lc}[${this.changePassword.name}]`;
19798
+ try {
19799
+ if (logalot41) {
19800
+ console.log(`${lc2} starting...`);
19801
+ }
19802
+ if (!latestKeystone.data) {
19803
+ throw new Error(`latestKeystone.data falsy`);
19804
+ }
19805
+ if (latestKeystone.data.revocationInfo) {
19806
+ throw new Error(`Keystone has been revoked. Cannot change password. (E: 8599f8f51c78d722252ddb2894fdbe25)`);
19807
+ }
19808
+ const prevPools = latestKeystone.data.challengePools;
19809
+ const signingPool = prevPools.find((p) => p.id === signingPoolId);
19810
+ if (!signingPool) {
19811
+ throw new Error(`Signing pool not found: ${signingPoolId} (E: 3599f8f51c78d722252ddb2894fdbe25)`);
19812
+ }
19813
+ let targets = [];
19814
+ if (poolIds && poolIds.length > 0) {
19815
+ for (const id of poolIds) {
19816
+ const found = prevPools.find((p) => p.id === id);
19817
+ if (!found) {
19818
+ throw new Error(`Target pool to rotate not found: ${id} (E: 4599f8f51c78d722252ddb2894fdbe25)`);
19819
+ }
19820
+ targets.push(found);
19821
+ }
19822
+ } else {
19823
+ targets = prevPools.filter((p) => !p.isForeign);
19824
+ if (targets.length === 0) {
19825
+ throw new Error(`No non-foreign pools found to rotate. (E: 5599f8f51c78d722252ddb2894fdbe25)`);
19826
+ }
19827
+ }
19828
+ const rotatedPools = [];
19829
+ for (const targetPool of targets) {
19830
+ const rotated = await rotatePoolChallenges({
19831
+ prevPool: targetPool,
19832
+ newMasterSecret
19833
+ });
19834
+ rotatedPools.push(rotated);
19835
+ }
19836
+ const target = getIbGibAddr({ ibGib: latestKeystone });
19837
+ const claim = {
19838
+ verb: KEYSTONE_VERB_MANAGE,
19839
+ target,
19840
+ details: {
19841
+ type: KeystoneClaimType.change_password,
19842
+ info: { change: rotatedPools.map((p) => p.id) }
19843
+ }
19844
+ };
19845
+ const idsToSolve = await selectChallengeIds({
19846
+ pool: signingPool,
19847
+ targetAddr: target
19848
+ });
19849
+ const { proof, nextPools } = await solveAndReplenish({
19850
+ targetPoolId: signingPoolId,
19851
+ prevPools,
19852
+ masterSecret: signingSecret,
19853
+ challengeIds: idsToSolve,
19854
+ claim
19855
+ });
19856
+ for (const rotated of rotatedPools) {
19857
+ const idx = nextPools.findIndex((p) => p.id === rotated.id);
19858
+ if (idx === -1) {
19859
+ throw new Error(`Rotated pool ${rotated.id} not found in next pools list. (E: 6599f8f51c78d722252ddb2894fdbe25)`);
19860
+ }
19861
+ nextPools[idx] = rotated;
19862
+ }
19863
+ const n = (latestKeystone.data.n ?? 0) + 1;
19864
+ let checkpointDetails;
19865
+ if (n % KEYSTONE_CHECKPOINT_FREQUENCY === 0) {
19866
+ const currentFrameView = {
19867
+ ...latestKeystone,
19868
+ data: { ...latestKeystone.data, challengePools: nextPools }
19869
+ };
19870
+ checkpointDetails = await this.getAggregateDetails({
19871
+ latestKeystone: currentFrameView,
19872
+ metaspace,
19873
+ space
19874
+ });
19875
+ }
19876
+ const newData = {
19877
+ challengePools: nextPools,
19878
+ proofs: [proof],
19879
+ frameDetails,
19880
+ checkpointDetails,
19881
+ ...latestKeystone.data.delegates ? { delegates: latestKeystone.data.delegates } : {}
19882
+ };
19883
+ return await evolvePersistAndRegisterKeystone({
19884
+ prevIbGib: latestKeystone,
19885
+ newData,
19886
+ metaspace,
19887
+ space
19888
+ });
19889
+ } catch (error) {
19890
+ console.error(`${lc2} ${extractErrorMsg(error)}`);
19891
+ throw error;
19892
+ }
19893
+ }
19894
+ /**
19895
+ * Verifies if a candidate signing secret (passphrase) is correct for a specific challenge pool.
19896
+ * Does not mutate any state.
19897
+ */
19898
+ async verifySigningSecret({ keystoneIbGib, signingSecret, poolId }) {
19899
+ const lc2 = `${this.lc}[${this.verifySigningSecret.name}]`;
19900
+ try {
19901
+ if (!keystoneIbGib.data)
19902
+ return false;
19903
+ const pool = keystoneIbGib.data.challengePools.find((p) => p.id === poolId);
19904
+ if (!pool)
19905
+ return false;
19906
+ return await verifyPoolSecret({ pool, signingSecret });
19907
+ } catch (error) {
19908
+ console.warn(`${lc2} Exception during verification: ${extractErrorMsg(error)}`);
19909
+ return false;
19910
+ }
19911
+ }
19912
+ };
19913
+
19914
+ // ../../libs/core-gib/dist/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-receiver/sync-websocket-peer-helpers.mjs
19915
+ var logalot42 = GLOBAL_LOG_A_LOT2;
19916
+ var SESSION_KEYSTONE_POLICY = {
19917
+ COMMON: {
19918
+ ALGO: HashAlgorithm.sha_256,
19919
+ TYPE: KeystoneChallengeType.hash_reveal_v1,
19920
+ ROUNDS: 2,
19921
+ REPLENISH: KeystoneReplenishStrategy.topUp
19922
+ },
19923
+ CONNECT_POOL: {
19924
+ ID: POOL_ID_CONNECT,
19925
+ VERB: KEYSTONE_VERB_CONNECT,
19926
+ SIZE: 10,
19927
+ SELECT_SEQUENTIALLY: 2,
19928
+ SELECT_RANDOMLY: 2,
19929
+ TARGET_BINDING_COUNT: 0,
19930
+ SERVER_DEMAND_COUNT: 3
19931
+ }
19932
+ };
19933
+ function getConnectChallenge(keystone) {
19934
+ const lc2 = `[${getConnectChallenge.name}]`;
19323
19935
  try {
19324
19936
  if (logalot42) {
19325
19937
  console.log(`${lc2} starting... (I: 3c791af2f978a00a087dfdde90884826)`);
@@ -19890,6 +20502,457 @@ var KeystoneGenesisHandler = class _KeystoneGenesisHandler extends ServeGibHandl
19890
20502
  }
19891
20503
  };
19892
20504
 
20505
+ // ../../libs/web-gib/dist/identity/sso/sso-config-helper.mjs
20506
+ function loadSsoServerConfig() {
20507
+ const ssoServerKdfSecret = process.env.SSO_SERVER_KDF_SECRET;
20508
+ const sessionSecret = process.env.SESSION_SECRET;
20509
+ if (!ssoServerKdfSecret) {
20510
+ throw new Error(`SSO_SERVER_KDF_SECRET is missing from environment. (E: 840899ab4fdfd23e89cfae1889cfae12)`);
20511
+ }
20512
+ if (!sessionSecret) {
20513
+ throw new Error(`SESSION_SECRET is missing from environment. (E: bfa899ab4fdfd23e89cfae23)`);
20514
+ }
20515
+ const providers = {};
20516
+ if (process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET) {
20517
+ providers.google = {
20518
+ providerId: "google",
20519
+ clientId: process.env.GOOGLE_CLIENT_ID,
20520
+ clientSecret: process.env.GOOGLE_CLIENT_SECRET,
20521
+ tokenUrl: "https://oauth2.googleapis.com/token",
20522
+ userInfoUrl: "https://openidconnect.googleapis.com/v1/userinfo",
20523
+ jwksUrl: "https://www.googleapis.com/oauth2/v3/certs"
20524
+ };
20525
+ }
20526
+ if (process.env.GITHUB_CLIENT_ID && process.env.GITHUB_CLIENT_SECRET) {
20527
+ providers.github = {
20528
+ providerId: "github",
20529
+ clientId: process.env.GITHUB_CLIENT_ID,
20530
+ clientSecret: process.env.GITHUB_CLIENT_SECRET,
20531
+ tokenUrl: "https://github.com/login/oauth/access_token",
20532
+ userInfoUrl: "https://api.github.com/user"
20533
+ };
20534
+ }
20535
+ return {
20536
+ ssoServerKdfSecret,
20537
+ sessionSecret,
20538
+ providers
20539
+ };
20540
+ }
20541
+
20542
+ // ../../libs/web-gib/dist/identity/sso/sso-custodian-service.mjs
20543
+ import { createPublicKey, verify } from "node:crypto";
20544
+ var SsoCustodianService = class _SsoCustodianService {
20545
+ config;
20546
+ lc = `[${_SsoCustodianService.name}]`;
20547
+ // In-memory cache for JSON Web Key Sets (JWKS) per provider
20548
+ jwksCache = /* @__PURE__ */ new Map();
20549
+ jwksTtlMs = 24 * 60 * 60 * 1e3;
20550
+ // Cache certs for 24 hours
20551
+ /**
20552
+ * @param config The server's global SSO configuration.
20553
+ * - Visibility: Private (Contains high-entropy secrets like `ssoServerKdfSecret` and `sessionSecret`).
20554
+ * - Generator: Server (Bootstrapped from node environment variables on server initialization).
20555
+ */
20556
+ constructor(config) {
20557
+ this.config = config;
20558
+ }
20559
+ /**
20560
+ * Exchanges an authorization code for an OAuth2 token and retrieves normalized user info.
20561
+ *
20562
+ * @param providerId The string identifier of the OAuth provider (e.g. 'google' or 'github').
20563
+ * - Visibility: Public (Non-sensitive metadata string).
20564
+ * - Generator: Client (Sent from browser UI selection).
20565
+ *
20566
+ * @param code The temporary authorization code returned by the OAuth provider.
20567
+ * - Visibility: Private (Confidential short-lived bearer credential. Must be kept private).
20568
+ * - Generator: Provider (Created by provider's OAuth page and sent to client redirect URI).
20569
+ *
20570
+ * @param redirectUri The redirect URI registered in the provider portal.
20571
+ * - Visibility: Public (Configuration URL).
20572
+ * - Generator: Client/Server (Derived from environmental configuration and sent by client).
20573
+ */
20574
+ async getOAuthUserInfo(providerId, code, redirectUri) {
20575
+ const lc2 = `${this.lc}[${this.getOAuthUserInfo.name}]`;
20576
+ const providerConfig = this.config.providers[providerId];
20577
+ if (!providerConfig) {
20578
+ throw new Error(`Provider '${providerId}' is not configured on this server. (E: c28900abfefd23e89cf23f12)`);
20579
+ }
20580
+ console.log(`${lc2} Exchanging code for provider: ${providerId}`);
20581
+ const tokens = await this.exchangeCodeForTokens(providerConfig, code, redirectUri);
20582
+ if (providerId === "google") {
20583
+ if (!tokens.id_token) {
20584
+ throw new Error(`Google token exchange did not return id_token. (E: e28900abfefd23e89cf45f23)`);
20585
+ }
20586
+ return this.verifyAndNormalizeGoogleToken(tokens.id_token);
20587
+ } else if (providerId === "github") {
20588
+ if (!tokens.access_token) {
20589
+ throw new Error(`GitHub token exchange did not return access_token. (E: a28900abfefd23e89cf56f34)`);
20590
+ }
20591
+ return this.fetchAndNormalizeGithubUser(providerConfig, tokens.access_token);
20592
+ }
20593
+ throw new Error(`Unsupported provider: ${providerId}. (E: d28900abfefd23e89cf67f45)`);
20594
+ }
20595
+ /**
20596
+ * Directly validates a Google ID Token (JWT) locally and returns normalized user info.
20597
+ * Useful for client-side authentication handshakes.
20598
+ *
20599
+ * @param idToken The raw base64-encoded signed JSON Web Token (JWT) ID token returned by Google.
20600
+ * - Visibility: Private (Sensitive bearer credential containing signature and claims. Must be kept private).
20601
+ * - Generator: Provider (Generated and signed cryptographically by Google authentication servers).
20602
+ */
20603
+ async verifyAndNormalizeGoogleToken(idToken) {
20604
+ const lc2 = `${this.lc}[${this.verifyAndNormalizeGoogleToken.name}]`;
20605
+ const providerConfig = this.config.providers.google;
20606
+ if (!providerConfig) {
20607
+ throw new Error(`Google provider config is missing. (E: g28900abfefd23e89cf78f56)`);
20608
+ }
20609
+ const payload = await this.verifyJwt(idToken, providerConfig);
20610
+ const nowSeconds = Math.floor(Date.now() / 1e3);
20611
+ if (payload.exp && payload.exp < nowSeconds) {
20612
+ throw new Error(`Token has expired. (E: t28900abfefd23e89cf89f67)`);
20613
+ }
20614
+ if (payload.aud !== providerConfig.clientId) {
20615
+ throw new Error(`Token audience mismatch. Expected ${providerConfig.clientId}, got ${payload.aud}. (E: m28900abfefd23e89cf90f78)`);
20616
+ }
20617
+ if (!payload.sub) {
20618
+ throw new Error(`Google JWT is missing subject claim ('sub'). (E: s28900abfefd23e89cfa1f89)`);
20619
+ }
20620
+ return {
20621
+ providerKey: `google:${payload.sub}`,
20622
+ providerId: "google",
20623
+ sub: payload.sub,
20624
+ email: payload.email,
20625
+ name: payload.name,
20626
+ picture: payload.picture
20627
+ };
20628
+ }
20629
+ /**
20630
+ * Internal: Exchange authorization code for access token / ID token.
20631
+ *
20632
+ * @param providerConfig Server configuration settings for the specific provider.
20633
+ * - Visibility: Private (Contains provider client secrets. Must stay server-side).
20634
+ * - Generator: Server (Configured from node environment variables).
20635
+ *
20636
+ * @param code The temporary authorization code to exchange.
20637
+ * - Visibility: Private (Short-lived credential. Must be kept private).
20638
+ * - Generator: Provider (Via OAuth flow).
20639
+ *
20640
+ * @param redirectUri The redirect URI registered for the exchange.
20641
+ * - Visibility: Public (Configuration URL).
20642
+ * - Generator: Client/Server (From app configuration).
20643
+ */
20644
+ async exchangeCodeForTokens(providerConfig, code, redirectUri) {
20645
+ const body = new URLSearchParams({
20646
+ client_id: providerConfig.clientId,
20647
+ client_secret: providerConfig.clientSecret,
20648
+ code,
20649
+ redirect_uri: redirectUri,
20650
+ grant_type: "authorization_code"
20651
+ });
20652
+ const headers = {
20653
+ "Content-Type": "application/x-www-form-urlencoded"
20654
+ };
20655
+ if (providerConfig.providerId === "github") {
20656
+ headers["Accept"] = "application/json";
20657
+ }
20658
+ const res = await fetch(providerConfig.tokenUrl, {
20659
+ method: "POST",
20660
+ headers,
20661
+ body: body.toString()
20662
+ });
20663
+ if (!res.ok) {
20664
+ const errText = await res.text();
20665
+ throw new Error(`Token exchange failed (HTTP ${res.status}): ${errText}`);
20666
+ }
20667
+ return res.json();
20668
+ }
20669
+ /**
20670
+ * Internal: Fetch GitHub user profile and normalize.
20671
+ *
20672
+ * @param providerConfig Server configuration settings for GitHub.
20673
+ * - Visibility: Private (Contains GitHub client secret).
20674
+ * - Generator: Server (From environment config).
20675
+ *
20676
+ * @param accessToken The active OAuth2 access token to authorize the API call.
20677
+ * - Visibility: Private (Bearer token. Must be kept private).
20678
+ * - Generator: Provider (GitHub token endpoint).
20679
+ */
20680
+ async fetchAndNormalizeGithubUser(providerConfig, accessToken) {
20681
+ const res = await fetch(providerConfig.userInfoUrl, {
20682
+ headers: {
20683
+ "Authorization": `Bearer ${accessToken}`,
20684
+ "User-Agent": "space-gib-auth-agent"
20685
+ // GitHub API requires User-Agent
20686
+ }
20687
+ });
20688
+ if (!res.ok) {
20689
+ const errText = await res.text();
20690
+ throw new Error(`Failed to fetch GitHub user info (HTTP ${res.status}): ${errText}`);
20691
+ }
20692
+ const rawUser = await res.json();
20693
+ if (!rawUser.id) {
20694
+ throw new Error(`GitHub user info response missing 'id' field.`);
20695
+ }
20696
+ const sub = String(rawUser.id);
20697
+ return {
20698
+ providerKey: `github:${sub}`,
20699
+ providerId: "github",
20700
+ sub,
20701
+ email: rawUser.email || void 0,
20702
+ name: rawUser.name || rawUser.login || void 0,
20703
+ picture: rawUser.avatar_url || void 0
20704
+ };
20705
+ }
20706
+ /**
20707
+ * Internal: Decodes and cryptographically verifies a JWT signature using JWKS public keys.
20708
+ *
20709
+ * @param token The raw base64-encoded JWT token to verify.
20710
+ * - Visibility: Private (Sensitive token. Must be kept private).
20711
+ * - Generator: Provider (Google / OIDC server).
20712
+ *
20713
+ * @param providerConfig The target provider server settings containing `jwksUrl`.
20714
+ * - Visibility: Private (Contains secrets).
20715
+ * - Generator: Server (From environment config).
20716
+ */
20717
+ async verifyJwt(token, providerConfig) {
20718
+ const parts = token.split(".");
20719
+ if (parts.length !== 3) {
20720
+ throw new Error("JWT must have 3 parts separated by dots.");
20721
+ }
20722
+ const [headerB64, payloadB64, signatureB64] = parts;
20723
+ let header;
20724
+ try {
20725
+ header = JSON.parse(Buffer.from(headerB64, "base64url").toString("utf-8"));
20726
+ } catch {
20727
+ throw new Error("Failed to parse JWT header.");
20728
+ }
20729
+ if (header.alg !== "RS256") {
20730
+ throw new Error(`Only RS256 algorithm is supported, got: ${header.alg}`);
20731
+ }
20732
+ const kid = header.kid;
20733
+ if (!kid) {
20734
+ throw new Error("JWT header missing key ID (kid).");
20735
+ }
20736
+ const keys = await this.getJwksKeys(providerConfig);
20737
+ const jwk = keys.find((key) => key.kid === kid);
20738
+ if (!jwk) {
20739
+ throw new Error(`JWK public key matching kid '${kid}' not found.`);
20740
+ }
20741
+ const publicKey = createPublicKey({
20742
+ key: jwk,
20743
+ format: "jwk"
20744
+ });
20745
+ const signature = Buffer.from(signatureB64, "base64url");
20746
+ const data = Buffer.from(`${headerB64}.${payloadB64}`);
20747
+ const isValid = verify("sha256", data, publicKey, signature);
20748
+ if (!isValid) {
20749
+ throw new Error("Cryptographic signature verification failed.");
20750
+ }
20751
+ try {
20752
+ return JSON.parse(Buffer.from(payloadB64, "base64url").toString("utf-8"));
20753
+ } catch {
20754
+ throw new Error("Failed to parse JWT payload.");
20755
+ }
20756
+ }
20757
+ /**
20758
+ * Internal: Retrieves JWKS keys from cache or fetches them from the provider's jwksUrl.
20759
+ *
20760
+ * @param providerConfig The provider config containing the target `jwksUrl`.
20761
+ * - Visibility: Private (Contains client secrets).
20762
+ * - Generator: Server (From environment config).
20763
+ */
20764
+ async getJwksKeys(providerConfig) {
20765
+ const providerId = providerConfig.providerId;
20766
+ const cached = this.jwksCache.get(providerId);
20767
+ if (cached && cached.expiresAt > Date.now()) {
20768
+ return cached.keys;
20769
+ }
20770
+ if (!providerConfig.jwksUrl) {
20771
+ throw new Error(`Provider '${providerId}' does not define a jwksUrl for JWT signature validation.`);
20772
+ }
20773
+ const res = await fetch(providerConfig.jwksUrl);
20774
+ if (!res.ok) {
20775
+ throw new Error(`Failed to fetch JWKS from ${providerConfig.jwksUrl} (HTTP ${res.status})`);
20776
+ }
20777
+ const body = await res.json();
20778
+ if (!body.keys || !Array.isArray(body.keys)) {
20779
+ throw new Error(`Invalid JWKS response structure from ${providerConfig.jwksUrl}`);
20780
+ }
20781
+ this.jwksCache.set(providerId, {
20782
+ keys: body.keys,
20783
+ expiresAt: Date.now() + this.jwksTtlMs
20784
+ });
20785
+ return body.keys;
20786
+ }
20787
+ /**
20788
+ * Derives a server-delegate master secret deterministically using the server's private KDF secret,
20789
+ * the providerKey, and the user's public nonce.
20790
+ *
20791
+ * @param providerKey The unique key identifying the provider and user sub ID (e.g. `google:123...`).
20792
+ * - Visibility: Public (Identifier saved on parent's delegate claim, safe for log/ledger storage).
20793
+ * - Generator: Provider/Server (Derived by mapping provider user sub ID after authentication).
20794
+ *
20795
+ * @param userNonce The public, high-entropy nonce generated by the client to salt the KDF derivation.
20796
+ * - Visibility: Public (Salt value stored on parent's delegate claim, safe for ledger storage).
20797
+ * - Generator: Client (Generated offline on user's device).
20798
+ */
20799
+ async deriveServerDelegateSecret(providerKey, userNonce) {
20800
+ const salt = await hash({
20801
+ s: `${providerKey}:${userNonce}`,
20802
+ algorithm: HashAlgorithm.sha_512
20803
+ });
20804
+ return await kdf_recursiveSaltWrap({
20805
+ masterSecret: this.config.ssoServerKdfSecret,
20806
+ salt,
20807
+ rounds: 1e3,
20808
+ algorithm: HashAlgorithm.sha_512
20809
+ });
20810
+ }
20811
+ /**
20812
+ * Creates a public custodian challenge pool (custodian-manage) derived deterministically
20813
+ * from the server's private KDF secret, providerKey, and userNonce.
20814
+ *
20815
+ * @param providerKey The unique key identifying the provider and user sub ID (e.g. `google:123...`).
20816
+ * - Visibility: Public (Mapped identifier, safe for ledger).
20817
+ * - Generator: Provider/Server (Derived after successful token exchange).
20818
+ *
20819
+ * @param userNonce The public, high-entropy nonce.
20820
+ * - Visibility: Public (Derivation salt, safe for ledger).
20821
+ * - Generator: Client (Generated on user's device).
20822
+ */
20823
+ async createCustodianChallengePool({ providerId, providerKey, userNonce, keystoneAddr, metaspace, space }) {
20824
+ const keystoneService = new KeystoneService_V1();
20825
+ const targetKeystone = await keystoneService.getLatestKeystone({
20826
+ addr: keystoneAddr,
20827
+ metaspace,
20828
+ space
20829
+ });
20830
+ if (!targetKeystone) {
20831
+ throw new Error(`Keystone not found for address: ${keystoneAddr} (E: 8a9c2b1d3e5f6a7b8c9d0e1f2a3b4c5d)`);
20832
+ }
20833
+ const userManagePool = targetKeystone.data?.challengePools.find((p) => p.id === POOL_ID_MANAGE);
20834
+ if (!userManagePool) {
20835
+ throw new Error(`Keystone manage pool not found. Custodian manage pool must match the user manage pool's security config. (E: 7c4e5f6a7b8c9d0e1f2a3b4c5e6f7a8b)`);
20836
+ }
20837
+ if (!providerId || !KEYSTONE_POOL_ID_REGEXP.test(providerId)) {
20838
+ throw new Error(`Invalid providerId: "${providerId}". Must match ${KEYSTONE_POOL_ID_REGEXP} (E: 3a2c4e5f6a7b8c9d0e1f2a3b4c5e6f)`);
20839
+ }
20840
+ const poolId = `custodian-manage-${providerId}`;
20841
+ const custodianSecret = await this.deriveServerDelegateSecret(providerKey, userNonce);
20842
+ const poolSalt = await hash({
20843
+ s: `${providerKey}:${userNonce}:salt`,
20844
+ algorithm: HashAlgorithm.sha_256
20845
+ });
20846
+ const poolSaltShort = poolSalt.substring(0, 16);
20847
+ const config = {
20848
+ ...userManagePool.config,
20849
+ id: poolId,
20850
+ salt: poolSaltShort,
20851
+ allowedVerbs: [KEYSTONE_VERB_MANAGE, KEYSTONE_VERB_REVOKE]
20852
+ };
20853
+ const strategy = KeystoneStrategyFactory.create({ config });
20854
+ const poolSecret = await strategy.derivePoolSecret({ masterSecret: custodianSecret });
20855
+ const challenges = {};
20856
+ const timestamp = Date.now().toString();
20857
+ for (let i = 0; i < config.behavior.size; i++) {
20858
+ const challengeId = await generateOpaqueChallengeId({
20859
+ salt: config.salt,
20860
+ timestamp,
20861
+ index: i
20862
+ });
20863
+ const solution = await strategy.generateSolution({
20864
+ poolSecret,
20865
+ poolId: config.id,
20866
+ challengeId
20867
+ });
20868
+ challenges[challengeId] = await strategy.generateChallenge({ solution });
20869
+ }
20870
+ return {
20871
+ id: poolId,
20872
+ config,
20873
+ challenges,
20874
+ isForeign: true,
20875
+ metadata: {
20876
+ owner: "custodian",
20877
+ providerKey
20878
+ }
20879
+ };
20880
+ }
20881
+ /**
20882
+ * Maps private server configuration to safe public client configurations.
20883
+ *
20884
+ * @param hostUrl The root URL host address of the server (e.g. `https://space-gib.localhost` or `https://ibgib.space`).
20885
+ * - Visibility: Public (Non-sensitive hostname configuration).
20886
+ * - Generator: Server (Extracted from the HTTP request context headers/protocol).
20887
+ */
20888
+ getSsoClientConfigs(hostUrl) {
20889
+ const configs = [];
20890
+ if (this.config.providers.google) {
20891
+ configs.push({
20892
+ providerId: "google",
20893
+ clientId: this.config.providers.google.clientId,
20894
+ authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
20895
+ redirectUri: `${hostUrl}/`,
20896
+ scope: "openid email profile"
20897
+ });
20898
+ }
20899
+ if (this.config.providers.github) {
20900
+ configs.push({
20901
+ providerId: "github",
20902
+ clientId: this.config.providers.github.clientId,
20903
+ authUrl: "https://github.com/login/oauth/authorize",
20904
+ redirectUri: `${hostUrl}/`,
20905
+ scope: "read:user user:email"
20906
+ });
20907
+ }
20908
+ return configs;
20909
+ }
20910
+ /**
20911
+ * Revokes an active OAuth access or refresh token with the provider.
20912
+ * Keeps token revocation logic DRY and centralized.
20913
+ */
20914
+ async revokeToken(providerId, token) {
20915
+ const lc2 = `${this.lc}[${this.revokeToken.name}]`;
20916
+ const providerConfig = this.config.providers[providerId];
20917
+ if (!providerConfig) {
20918
+ throw new Error(`SSO provider "${providerId}" config is missing. (E: r28900abfefd23e89cf78f57)`);
20919
+ }
20920
+ try {
20921
+ if (providerId === "google") {
20922
+ const res = await fetch(`https://oauth2.googleapis.com/revoke?token=${encodeURIComponent(token)}`, {
20923
+ method: "POST",
20924
+ headers: {
20925
+ "Content-Type": "application/x-www-form-urlencoded"
20926
+ }
20927
+ });
20928
+ if (!res.ok) {
20929
+ const text = await res.text();
20930
+ console.warn(`${lc2} Google token revocation returned status ${res.status}: ${text}`);
20931
+ }
20932
+ } else if (providerId === "github") {
20933
+ const auth = Buffer.from(`${providerConfig.clientId}:${providerConfig.clientSecret}`).toString("base64");
20934
+ const res = await fetch(`https://api.github.com/applications/${providerConfig.clientId}/grant`, {
20935
+ method: "DELETE",
20936
+ headers: {
20937
+ "Authorization": `Basic ${auth}`,
20938
+ "Accept": "application/vnd.github.v3+json",
20939
+ "User-Agent": "space-gib-server"
20940
+ },
20941
+ body: JSON.stringify({ access_token: token })
20942
+ });
20943
+ if (!res.ok && res.status !== 404) {
20944
+ const text = await res.text();
20945
+ console.warn(`${lc2} GitHub token revocation returned status ${res.status}: ${text}`);
20946
+ }
20947
+ } else {
20948
+ throw new Error(`Unsupported provider: ${providerId} (E: e28900abfefd23e89cf78f58)`);
20949
+ }
20950
+ } catch (error) {
20951
+ console.error(`${lc2} Token revocation failed for provider ${providerId}: ${extractErrorMsg(error)}`);
20952
+ }
20953
+ }
20954
+ };
20955
+
19893
20956
  // src/server/serve-gib/handlers/api/keystone/keystone-evolve.handler.mts
19894
20957
  var logalot46 = GLOBAL_LOG_A_LOT || true;
19895
20958
  var KeystoneEvolveHandler = class _KeystoneEvolveHandler extends ServeGibHandlerWithMetaspaceBase {
@@ -19951,6 +21014,30 @@ var KeystoneEvolveHandler = class _KeystoneEvolveHandler extends ServeGibHandler
19951
21014
  metaspace: reqCtx.metaspace,
19952
21015
  space
19953
21016
  });
21017
+ const claim = keystoneIbGib.data?.proofs?.[0]?.claim;
21018
+ if (claim?.details?.type === "remove-pool") {
21019
+ const removeInfo = claim.details.info;
21020
+ if (removeInfo && Array.isArray(removeInfo.remove)) {
21021
+ const config = loadSsoServerConfig();
21022
+ const custodian4 = new SsoCustodianService(config);
21023
+ for (const poolId of removeInfo.remove) {
21024
+ if (poolId.startsWith("custodian-manage-")) {
21025
+ const providerId = poolId.replace("custodian-manage-", "");
21026
+ console.log(`${lc2} Evolved keystone removed custodian pool: ${poolId}. Performing unlink cleanup for provider: ${providerId}`);
21027
+ const token = parsed.revokeTokens?.[providerId];
21028
+ if (token) {
21029
+ try {
21030
+ await custodian4.revokeToken(providerId, token);
21031
+ } catch (e) {
21032
+ console.warn(`${lc2} Failed to revoke token for provider ${providerId}: ${extractErrorMsg(e)}`);
21033
+ }
21034
+ } else {
21035
+ console.log(`${lc2} No OAuth token supplied by client for revocation of provider: ${providerId}`);
21036
+ }
21037
+ }
21038
+ }
21039
+ }
21040
+ }
19954
21041
  return this.ok({
19955
21042
  message: `Successfully evolved domain keystone with session keystone`
19956
21043
  });
@@ -25225,6 +26312,298 @@ var SyncChannelHandler = class _SyncChannelHandler extends SyncUpgradeHandlerBas
25225
26312
  };
25226
26313
  var SyncUpgradeHandler = SyncChannelHandler;
25227
26314
 
26315
+ // ../../libs/web-gib/dist/identity/sso/sso-helpers.mjs
26316
+ function isSsoProviderLinked({ keystone, providerId }) {
26317
+ if (!keystone?.data?.challengePools)
26318
+ return false;
26319
+ const targetPoolId = `custodian-manage-${providerId}`;
26320
+ return keystone.data.challengePools.some((p) => p.id === targetPoolId);
26321
+ }
26322
+
26323
+ // src/server/serve-gib/handlers/api/keystone/sso-link.handler.mts
26324
+ var logalot60 = GLOBAL_LOG_A_LOT || true;
26325
+ var custodian;
26326
+ function getCustodian() {
26327
+ if (!custodian) {
26328
+ const config = loadSsoServerConfig();
26329
+ custodian = new SsoCustodianService(config);
26330
+ }
26331
+ return custodian;
26332
+ }
26333
+ var SsoLinkHandler = class _SsoLinkHandler extends ServeGibHandlerWithMetaspaceBase {
26334
+ lc = `[${_SsoLinkHandler.name}]`;
26335
+ method = "POST";
26336
+ regex = API_PATH_REGEXES.IDENTITY_SSO_LINK;
26337
+ async parseParamsImpl(reqCtx) {
26338
+ let parsed;
26339
+ try {
26340
+ parsed = JSON.parse(reqCtx.body);
26341
+ } catch {
26342
+ return void 0;
26343
+ }
26344
+ const parentTjpAddr = parsed?.parentTjpAddr;
26345
+ if (!parentTjpAddr) {
26346
+ return void 0;
26347
+ }
26348
+ return {
26349
+ parentTjpAddr,
26350
+ domainInfo: this.getDomainInfo({ domainAddr: parentTjpAddr })
26351
+ };
26352
+ }
26353
+ async validateQueryParams({ queryParams }) {
26354
+ return [];
26355
+ }
26356
+ async handleRouteImpl(reqCtx) {
26357
+ const lc2 = `${this.lc}[${this.handleRouteImpl.name}]`;
26358
+ try {
26359
+ if (logalot60) {
26360
+ console.log(`${lc2} starting...`);
26361
+ }
26362
+ if (!reqCtx.params) {
26363
+ return this.error(400, "Invalid parameters: parentTjpAddr missing");
26364
+ }
26365
+ if (!reqCtx.metaspace) {
26366
+ return this.error(500, "Metaspace not initialized");
26367
+ }
26368
+ let parsed;
26369
+ try {
26370
+ parsed = JSON.parse(reqCtx.body);
26371
+ } catch {
26372
+ return this.error(400, "Request body must be valid JSON");
26373
+ }
26374
+ const { code, providerId, parentTjpAddr, userNonce, redirectUri } = parsed;
26375
+ if (!code || !providerId || !parentTjpAddr || !userNonce || !redirectUri) {
26376
+ return this.error(400, "Body missing required parameters (code, providerId, parentTjpAddr, userNonce, redirectUri)");
26377
+ }
26378
+ const space = await reqCtx.metaspace.getLocalUserSpace({ lock: false });
26379
+ if (!space) {
26380
+ return this.error(500, "No local user space found in domain metaspace");
26381
+ }
26382
+ const keystoneService = new KeystoneService_V1();
26383
+ const targetKeystone = await keystoneService.getLatestKeystone({
26384
+ addr: parentTjpAddr,
26385
+ metaspace: reqCtx.metaspace,
26386
+ space
26387
+ });
26388
+ if (!targetKeystone) {
26389
+ return this.error(400, `Keystone not found for address: ${parentTjpAddr}`);
26390
+ }
26391
+ if (isSsoProviderLinked({ keystone: targetKeystone, providerId })) {
26392
+ return this.error(400, `SSO provider "${providerId}" is already linked to this keystone.`);
26393
+ }
26394
+ const service = getCustodian();
26395
+ const userInfo = await service.getOAuthUserInfo(providerId, code, redirectUri);
26396
+ const custodianPool = await service.createCustodianChallengePool({
26397
+ providerId,
26398
+ providerKey: userInfo.providerKey,
26399
+ userNonce,
26400
+ keystoneAddr: parentTjpAddr,
26401
+ metaspace: reqCtx.metaspace,
26402
+ space
26403
+ });
26404
+ console.log(`${lc2} created custodian pool for: ${userInfo.providerKey}`);
26405
+ return this.ok({
26406
+ success: true,
26407
+ custodianPool
26408
+ }, 200);
26409
+ } catch (error) {
26410
+ const emsg = extractErrorMsg(error);
26411
+ console.error(`${lc2} Link failed: ${emsg}`);
26412
+ return this.error(500, `Link failed: ${emsg}`);
26413
+ } finally {
26414
+ if (logalot60) {
26415
+ console.log(`${lc2} complete.`);
26416
+ }
26417
+ }
26418
+ }
26419
+ };
26420
+
26421
+ // src/server/serve-gib/handlers/api/keystone/sso-login.handler.mts
26422
+ import { createHmac } from "node:crypto";
26423
+ var logalot61 = GLOBAL_LOG_A_LOT || true;
26424
+ var custodian2;
26425
+ function getCustodian2() {
26426
+ if (!custodian2) {
26427
+ const config = loadSsoServerConfig();
26428
+ custodian2 = new SsoCustodianService(config);
26429
+ }
26430
+ return custodian2;
26431
+ }
26432
+ function signSessionToken(payload, secret) {
26433
+ const header = { alg: "HS256", typ: "JWT" };
26434
+ const headerB64 = Buffer.from(JSON.stringify(header)).toString("base64url");
26435
+ const payloadB64 = Buffer.from(JSON.stringify(payload)).toString("base64url");
26436
+ const data = `${headerB64}.${payloadB64}`;
26437
+ const signature = createHmac("sha256", secret).update(data).digest("base64url");
26438
+ return `${data}.${signature}`;
26439
+ }
26440
+ var SsoLoginHandler = class _SsoLoginHandler extends ServeGibHandlerWithMetaspaceBase {
26441
+ lc = `[${_SsoLoginHandler.name}]`;
26442
+ method = "POST";
26443
+ regex = API_PATH_REGEXES.IDENTITY_SSO_LOGIN;
26444
+ async parseParamsImpl(reqCtx) {
26445
+ let parsed;
26446
+ try {
26447
+ parsed = JSON.parse(reqCtx.body);
26448
+ } catch {
26449
+ return void 0;
26450
+ }
26451
+ const parentTjpAddr = parsed?.parentTjpAddr;
26452
+ if (!parentTjpAddr) {
26453
+ return void 0;
26454
+ }
26455
+ return {
26456
+ parentTjpAddr,
26457
+ domainInfo: this.getDomainInfo({ domainAddr: parentTjpAddr })
26458
+ };
26459
+ }
26460
+ async validateQueryParams({ queryParams }) {
26461
+ return [];
26462
+ }
26463
+ async handleRouteImpl(reqCtx) {
26464
+ const lc2 = `${this.lc}[${this.handleRouteImpl.name}]`;
26465
+ try {
26466
+ if (logalot61) {
26467
+ console.log(`${lc2} starting...`);
26468
+ }
26469
+ if (!reqCtx.params) {
26470
+ return this.error(400, "Invalid parameters: parentTjpAddr missing");
26471
+ }
26472
+ if (!reqCtx.metaspace) {
26473
+ return this.error(500, "Metaspace not initialized");
26474
+ }
26475
+ let parsed;
26476
+ try {
26477
+ parsed = JSON.parse(reqCtx.body);
26478
+ } catch {
26479
+ return this.error(400, "Request body must be valid JSON");
26480
+ }
26481
+ const { code, providerId, parentTjpAddr, userNonce, redirectUri } = parsed;
26482
+ if (!code || !providerId || !parentTjpAddr || !userNonce || !redirectUri) {
26483
+ return this.error(400, "Body missing required parameters (code, providerId, parentTjpAddr, userNonce, redirectUri)");
26484
+ }
26485
+ const space = await reqCtx.metaspace.getLocalUserSpace({ lock: false });
26486
+ if (!space) {
26487
+ return this.error(500, "No local user space found in domain metaspace");
26488
+ }
26489
+ const service = getCustodian2();
26490
+ const userInfo = await service.getOAuthUserInfo(providerId, code, redirectUri);
26491
+ const custodianSecret = await service.deriveServerDelegateSecret(userInfo.providerKey, userNonce);
26492
+ const latestParentAddr = await reqCtx.metaspace.getLatestAddr({ addr: parentTjpAddr, space });
26493
+ if (!latestParentAddr) {
26494
+ return this.error(404, `Parent identity keystone not found for address: ${parentTjpAddr}`);
26495
+ }
26496
+ const resGet = await reqCtx.metaspace.get({ addr: latestParentAddr, space });
26497
+ const parentKeystone = resGet.ibGibs?.[0];
26498
+ if (!parentKeystone) {
26499
+ return this.error(404, `Parent identity keystone not found in space for address: ${latestParentAddr}`);
26500
+ }
26501
+ const targetPoolId = `custodian-manage-${providerId}`;
26502
+ const custodianPool = parentKeystone.data?.challengePools?.find((p) => p.id === targetPoolId);
26503
+ if (!custodianPool) {
26504
+ return this.error(401, `Unauthorized: Custodian pool '${targetPoolId}' is not registered in the parent identity keystone.`);
26505
+ }
26506
+ const allowedVerbs = custodianPool.config.allowedVerbs || [];
26507
+ if (!allowedVerbs.includes(KEYSTONE_VERB_MANAGE)) {
26508
+ return this.error(401, `Unauthorized: Custodian pool does not have the required manage permission.`);
26509
+ }
26510
+ const strategy = KeystoneStrategyFactory.create({ config: custodianPool.config });
26511
+ const poolSecret = await strategy.derivePoolSecret({ masterSecret: custodianSecret });
26512
+ for (const [id, challenge] of Object.entries(custodianPool.challenges)) {
26513
+ const solution = await strategy.generateSolution({
26514
+ poolSecret,
26515
+ poolId: custodianPool.id,
26516
+ challengeId: id
26517
+ });
26518
+ const hashValue = await strategy.generateChallenge({ solution });
26519
+ if (hashValue.hash !== challenge.hash) {
26520
+ return this.error(401, "Unauthorized: Cryptographic verification of custodian pool challenges failed.");
26521
+ }
26522
+ }
26523
+ const sessionPayload = {
26524
+ parentTjpAddr,
26525
+ providerKey: userInfo.providerKey,
26526
+ exp: Math.floor(Date.now() / 1e3) + 24 * 3600
26527
+ // 24-hour expiration
26528
+ };
26529
+ const config = loadSsoServerConfig();
26530
+ const sessionToken = signSessionToken(sessionPayload, config.sessionSecret);
26531
+ const headers = {
26532
+ "Set-Cookie": `space_gib_session=${sessionToken}; Path=/; HttpOnly; SameSite=Strict; Max-Age=86400`
26533
+ };
26534
+ console.log(`${lc2} login successful for parent: ${parentTjpAddr}, custodian providerKey: ${userInfo.providerKey}`);
26535
+ return {
26536
+ status: 200,
26537
+ headers,
26538
+ body: {
26539
+ success: true,
26540
+ parentTjpAddr,
26541
+ custodianPoolId: custodianPool.id
26542
+ },
26543
+ isJson: true
26544
+ };
26545
+ } catch (error) {
26546
+ const emsg = extractErrorMsg(error);
26547
+ console.error(`${lc2} Login failed: ${emsg}`);
26548
+ return this.error(500, `Login failed: ${emsg}`);
26549
+ } finally {
26550
+ if (logalot61) {
26551
+ console.log(`${lc2} complete.`);
26552
+ }
26553
+ }
26554
+ }
26555
+ };
26556
+
26557
+ // src/server/serve-gib/handlers/api/keystone/sso-config.handler.mts
26558
+ var logalot62 = GLOBAL_LOG_A_LOT || true;
26559
+ var custodian3;
26560
+ function getCustodian3() {
26561
+ if (!custodian3) {
26562
+ const config = loadSsoServerConfig();
26563
+ custodian3 = new SsoCustodianService(config);
26564
+ }
26565
+ return custodian3;
26566
+ }
26567
+ var SsoConfigHandler = class _SsoConfigHandler extends ServeGibHandlerBase {
26568
+ lc = `[${_SsoConfigHandler.name}]`;
26569
+ method = "GET";
26570
+ regex = API_PATH_REGEXES.IDENTITY_SSO_CONFIG;
26571
+ async parseParamsImpl(reqCtx) {
26572
+ return void 0;
26573
+ }
26574
+ async validateQueryParams({ queryParams }) {
26575
+ return [];
26576
+ }
26577
+ /**
26578
+ * @param reqCtx Context details of the incoming request.
26579
+ * - Visibility: Public/Private (Contextual request information containing headers).
26580
+ * - Generator: Server (Constructed dynamically by serve-gib-v1 pipeline).
26581
+ */
26582
+ async handleRouteImpl(reqCtx) {
26583
+ const lc2 = `${this.lc}[${this.handleRouteImpl.name}]`;
26584
+ try {
26585
+ if (logalot62) {
26586
+ console.log(`${lc2} starting...`);
26587
+ }
26588
+ const service = getCustodian3();
26589
+ const hostUrl = `${reqCtx.protocol}://${reqCtx.headers.host}`;
26590
+ const providers = service.getSsoClientConfigs(hostUrl);
26591
+ return this.ok({
26592
+ success: true,
26593
+ providers
26594
+ }, 200);
26595
+ } catch (error) {
26596
+ const emsg = extractErrorMsg(error);
26597
+ console.error(`${lc2} Fetching SSO config failed: ${emsg}`);
26598
+ return this.error(500, `SSO configuration retrieval failed: ${emsg}`);
26599
+ } finally {
26600
+ if (logalot62) {
26601
+ console.log(`${lc2} complete.`);
26602
+ }
26603
+ }
26604
+ }
26605
+ };
26606
+
25228
26607
  // src/server/server.mts
25229
26608
  var __dirname = dirname(fileURLToPath(import.meta.url));
25230
26609
  var PORT = parseInt(process.env.PORT ?? "3000", 10);
@@ -25246,6 +26625,9 @@ async function main() {
25246
26625
  new KeystoneEvolveHandler(),
25247
26626
  new KeystonePostHandler(),
25248
26627
  new KeystoneGetHandler(),
26628
+ new SsoLinkHandler(),
26629
+ new SsoLoginHandler(),
26630
+ new SsoConfigHandler(),
25249
26631
  new StaticFileHandler(CLIENT_DIR)
25250
26632
  ],
25251
26633
  errorHandler: new ErrorHandler()