@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.
- package/README.md +20 -0
- package/dist/client/bootstrap.mjs +31 -31
- package/dist/client/bootstrap.mjs.map +3 -3
- package/dist/client/chunk-DBHMHCGD.mjs +2341 -0
- package/dist/client/{chunk-SMOZ2D5E.mjs.map → chunk-DBHMHCGD.mjs.map} +4 -4
- package/dist/client/chunk-SUQ5QJH4.mjs +42 -0
- package/dist/client/chunk-SUQ5QJH4.mjs.map +7 -0
- package/dist/client/index.mjs +1 -1
- package/dist/client/script.mjs +1 -1
- package/dist/server/server.mjs +1561 -179
- package/dist/server/server.mjs.map +4 -4
- package/package.json +5 -5
- package/space-gib.localhost-1783713259760.log +45 -0
- package/src/client/AUTO-GENERATED-version.mts +1 -1
- package/src/client/api/space-gib-api-bridge.mts +7 -2
- package/src/client/bootstrap.mts +9 -0
- package/src/client/components/identity-header/identity-header.mts +43 -26
- package/src/client/components/identity-manager/identity-manager.css +57 -392
- package/src/client/components/identity-manager/identity-manager.html +0 -114
- package/src/client/components/identity-manager/identity-manager.mts +356 -543
- package/src/client/components/keystone-creator/keystone-creator.mts +4 -3
- package/src/client/components/keystone-details/keystone-details.css +569 -0
- package/src/client/components/keystone-details/keystone-details.html +127 -0
- package/src/client/components/keystone-details/keystone-details.mts +1013 -0
- package/src/client/components/keystone-scrubber/SCRUBBER_IMPLEMENTATION.md +33 -0
- package/src/client/components/keystone-scrubber/keystone-scrubber.css +46 -0
- package/src/client/components/keystone-scrubber/keystone-scrubber.html +15 -0
- package/src/client/components/keystone-scrubber/keystone-scrubber.mts +356 -0
- package/src/client/ui/shell/space-gib-shell-service.mts +4 -0
- package/src/server/path-constants.mts +12 -0
- package/src/server/serve-gib/handlers/api/keystone/keystone-evolve.handler.mts +33 -0
- package/src/server/serve-gib/handlers/api/keystone/sso-config.handler.mts +66 -0
- package/src/server/serve-gib/handlers/api/keystone/sso-link.handler.mts +140 -0
- package/src/server/serve-gib/handlers/api/keystone/sso-login.handler.mts +190 -0
- package/src/server/server.mts +6 -0
- package/dist/client/chunk-734MMI4C.mjs +0 -42
- package/dist/client/chunk-734MMI4C.mjs.map +0 -7
- package/dist/client/chunk-SMOZ2D5E.mjs +0 -2049
package/dist/server/server.mjs
CHANGED
|
@@ -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:
|
|
231
|
+
function patchObject({ obj, value, path, pathDelimiter, logalot: logalot63 }) {
|
|
232
232
|
const lc2 = `[${patchObject.name}]`;
|
|
233
233
|
try {
|
|
234
|
-
if (
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
18090
|
-
|
|
18091
|
-
|
|
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
|
|
18724
|
-
|
|
18725
|
-
|
|
18726
|
-
|
|
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
|
-
|
|
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
|
-
|
|
19200
|
-
|
|
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
|
-
|
|
19303
|
-
|
|
19304
|
-
|
|
19305
|
-
|
|
19306
|
-
|
|
19307
|
-
|
|
19308
|
-
|
|
19309
|
-
|
|
19310
|
-
|
|
19311
|
-
|
|
19312
|
-
|
|
19313
|
-
|
|
19314
|
-
|
|
19315
|
-
|
|
19316
|
-
|
|
19317
|
-
|
|
19318
|
-
|
|
19319
|
-
|
|
19320
|
-
|
|
19321
|
-
|
|
19322
|
-
|
|
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()
|