@ibgib/space-gib 0.0.6 → 0.0.7
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-RXFIBFRK.mjs +42 -0
- package/dist/client/chunk-RXFIBFRK.mjs.map +7 -0
- package/dist/client/chunk-ULFNE26Y.mjs +2216 -0
- package/dist/client/chunk-ULFNE26Y.mjs.map +7 -0
- package/dist/client/index.mjs +1 -1
- package/dist/client/script.mjs +1 -1
- package/dist/server/server.mjs +1346 -245
- 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/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 +472 -0
- package/src/client/components/keystone-details/keystone-details.html +99 -0
- package/src/client/components/keystone-details/keystone-details.mts +821 -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/sso-config.handler.mts +66 -0
- package/src/server/serve-gib/handlers/api/keystone/sso-link.handler.mts +119 -0
- package/src/server/serve-gib/handlers/api/keystone/sso-login.handler.mts +189 -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/client/chunk-SMOZ2D5E.mjs.map +0 -7
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,15 @@ 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_REPLACE_POOL = "replace-pool";
|
|
17669
|
+
var KEYSTONE_CLAIM_TYPE_CHANGE_PASSWORD = "change-password";
|
|
17670
|
+
var KeystoneClaimType = {
|
|
17671
|
+
add_pool: KEYSTONE_CLAIM_TYPE_ADD_POOL,
|
|
17672
|
+
replace_pool: KEYSTONE_CLAIM_TYPE_REPLACE_POOL,
|
|
17673
|
+
change_password: KEYSTONE_CLAIM_TYPE_CHANGE_PASSWORD
|
|
17674
|
+
};
|
|
17675
|
+
var KEYSTONE_CLAIM_TYPE_VALID_VALUES = Object.values(KeystoneClaimType);
|
|
17648
17676
|
|
|
17649
17677
|
// ../../libs/core-gib/dist/keystone/keystone-constants.mjs
|
|
17650
17678
|
var KEYSTONE_ATOM = "keystone";
|
|
@@ -17692,8 +17720,8 @@ var KeystoneVerb = {
|
|
|
17692
17720
|
CONNECT: KEYSTONE_VERB_CONNECT
|
|
17693
17721
|
};
|
|
17694
17722
|
var KEYSTONE_VERB_VALID_VALUES = Object.values(KeystoneVerb);
|
|
17695
|
-
var POOL_ID_REVOKE = KEYSTONE_VERB_REVOKE;
|
|
17696
17723
|
var POOL_ID_MANAGE = KEYSTONE_VERB_MANAGE;
|
|
17724
|
+
var POOL_ID_CUSTODIAN_MANAGE = "custodian-manage";
|
|
17697
17725
|
var POOL_ID_CONNECT = KEYSTONE_VERB_CONNECT;
|
|
17698
17726
|
var POOL_ID_SYNC = KEYSTONE_VERB_SYNC;
|
|
17699
17727
|
var KEYSTONE_CONFIG_DEFAULT_SIZE = 200;
|
|
@@ -18057,7 +18085,7 @@ async function generateOpaqueChallengeId({ salt, timestamp, index }) {
|
|
|
18057
18085
|
const raw = await hash({ s: `${salt}${timestamp}${index}` });
|
|
18058
18086
|
return raw.substring(0, 16);
|
|
18059
18087
|
}
|
|
18060
|
-
async function applyReplenishmentStrategy({ prevPools, targetPoolId, consumedIds, masterSecret, strategy, config }) {
|
|
18088
|
+
async function applyReplenishmentStrategy({ prevPools, targetPoolId, consumedIds, masterSecret, strategy, config, verb }) {
|
|
18061
18089
|
const lc2 = `[applyReplenishmentStrategy]`;
|
|
18062
18090
|
try {
|
|
18063
18091
|
const newPools = JSON.parse(JSON.stringify(prevPools));
|
|
@@ -18068,7 +18096,10 @@ async function applyReplenishmentStrategy({ prevPools, targetPoolId, consumedIds
|
|
|
18068
18096
|
const pool = newPools[targetIdx];
|
|
18069
18097
|
const poolSecret = await strategy.derivePoolSecret({ masterSecret });
|
|
18070
18098
|
const timestamp = Date.now().toString();
|
|
18071
|
-
|
|
18099
|
+
let strategyType = config.behavior.replenish;
|
|
18100
|
+
if (verb === KEYSTONE_VERB_REVOKE) {
|
|
18101
|
+
strategyType = KeystoneReplenishStrategy.deleteAll;
|
|
18102
|
+
}
|
|
18072
18103
|
if (strategyType === KeystoneReplenishStrategy.topUp) {
|
|
18073
18104
|
consumedIds.forEach((id) => delete pool.challenges[id]);
|
|
18074
18105
|
for (let i = 0; i < consumedIds.length; i++) {
|
|
@@ -18085,20 +18116,10 @@ async function applyReplenishmentStrategy({ prevPools, targetPoolId, consumedIds
|
|
|
18085
18116
|
pool.challenges[newId] = await strategy.generateChallenge({ solution });
|
|
18086
18117
|
}
|
|
18087
18118
|
} 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
|
-
}
|
|
18119
|
+
pool.challenges = await generatePoolChallenges({
|
|
18120
|
+
config,
|
|
18121
|
+
masterSecret
|
|
18122
|
+
});
|
|
18102
18123
|
} else if (strategyType === KeystoneReplenishStrategy.consume) {
|
|
18103
18124
|
consumedIds.forEach((id) => delete pool.challenges[id]);
|
|
18104
18125
|
} else if (strategyType === KeystoneReplenishStrategy.deleteAll) {
|
|
@@ -18144,7 +18165,8 @@ async function solveAndReplenish({ targetPoolId, prevPools, masterSecret, challe
|
|
|
18144
18165
|
consumedIds: challengeIds,
|
|
18145
18166
|
masterSecret,
|
|
18146
18167
|
strategy,
|
|
18147
|
-
config: pool.config
|
|
18168
|
+
config: pool.config,
|
|
18169
|
+
verb: claim.verb
|
|
18148
18170
|
});
|
|
18149
18171
|
return { proof, nextPools };
|
|
18150
18172
|
} catch (error) {
|
|
@@ -18417,6 +18439,116 @@ async function validateKeystoneTransition({ currentIbGib, prevIbGib }) {
|
|
|
18417
18439
|
errors.push(`Revocation target mismatch. Expected ${expectedTarget}, got ${target}`);
|
|
18418
18440
|
}
|
|
18419
18441
|
}
|
|
18442
|
+
for (const proof of currData.proofs) {
|
|
18443
|
+
const claim = proof.claim;
|
|
18444
|
+
if (claim?.details) {
|
|
18445
|
+
const details = claim.details;
|
|
18446
|
+
const type = details.type;
|
|
18447
|
+
const info = details.info;
|
|
18448
|
+
if (type === "add-pool") {
|
|
18449
|
+
const addInfo = info;
|
|
18450
|
+
if (!addInfo || !Array.isArray(addInfo.add) || addInfo.add.length === 0) {
|
|
18451
|
+
errors.push(`details type is '${type}' but payload is invalid. (E: 1fa2a8cb28c9b60e68994511a5825)`);
|
|
18452
|
+
continue;
|
|
18453
|
+
}
|
|
18454
|
+
const prevIds = new Set(prevData.challengePools.map((p) => p.id));
|
|
18455
|
+
const currIds = new Set(currData.challengePools.map((p) => p.id));
|
|
18456
|
+
for (const id of addInfo.add) {
|
|
18457
|
+
if (!currIds.has(id)) {
|
|
18458
|
+
errors.push(`ClaimDetails_AddPool lists pool ${id} but it is missing in the evolved keystone. (E: 2fa2a8cb28c9b60e68994511a5825)`);
|
|
18459
|
+
}
|
|
18460
|
+
if (prevIds.has(id)) {
|
|
18461
|
+
errors.push(`ClaimDetails_AddPool attempts to add existing pool ${id}. (E: 3fa2a8cb28c9b60e68994511a5825)`);
|
|
18462
|
+
}
|
|
18463
|
+
}
|
|
18464
|
+
if (currData.challengePools.length !== prevData.challengePools.length + addInfo.add.length) {
|
|
18465
|
+
errors.push(`Structural mismatch: expected ${prevData.challengePools.length + addInfo.add.length} pools, found ${currData.challengePools.length}. (E: 4fa2a8cb28c9b60e68994511a5825)`);
|
|
18466
|
+
}
|
|
18467
|
+
for (const prevPool of prevData.challengePools) {
|
|
18468
|
+
const currPool = currData.challengePools.find((p) => p.id === prevPool.id);
|
|
18469
|
+
if (!currPool) {
|
|
18470
|
+
errors.push(`Existing pool ${prevPool.id} was removed during add-pool transition. (E: 5fa2a8cb28c9b60e68994511a5825)`);
|
|
18471
|
+
continue;
|
|
18472
|
+
}
|
|
18473
|
+
if (JSON.stringify(prevPool.config) !== JSON.stringify(currPool.config)) {
|
|
18474
|
+
errors.push(`Existing pool config ${prevPool.id} was mutated during add-pool transition. (E: 6fa2a8cb28c9b60e68994511a5825)`);
|
|
18475
|
+
}
|
|
18476
|
+
if (JSON.stringify(prevPool.challenges) !== JSON.stringify(currPool.challenges)) {
|
|
18477
|
+
errors.push(`Existing pool challenges ${prevPool.id} were mutated during add-pool transition. (E: 7fa2a8cb28c9b60e68994511a5825)`);
|
|
18478
|
+
}
|
|
18479
|
+
}
|
|
18480
|
+
} else if (type === "replace-pool") {
|
|
18481
|
+
const replaceInfo = info;
|
|
18482
|
+
if (!replaceInfo || typeof replaceInfo.replace !== "string" || !replaceInfo.replace) {
|
|
18483
|
+
errors.push(`details type is '${type}' but payload is invalid. (E: 8fa2a8cb28c9b60e68994511a5825)`);
|
|
18484
|
+
continue;
|
|
18485
|
+
}
|
|
18486
|
+
const targetId = replaceInfo.replace;
|
|
18487
|
+
const prevIds = prevData.challengePools.map((p) => p.id).sort();
|
|
18488
|
+
const currIds = currData.challengePools.map((p) => p.id).sort();
|
|
18489
|
+
if (JSON.stringify(prevIds) !== JSON.stringify(currIds)) {
|
|
18490
|
+
errors.push(`replace-pool transition mutated the pool IDs set. (E: 9fa2a8cb28c9b60e68994511a5825)`);
|
|
18491
|
+
}
|
|
18492
|
+
const prevTarget = prevData.challengePools.find((p) => p.id === targetId);
|
|
18493
|
+
const currTarget = currData.challengePools.find((p) => p.id === targetId);
|
|
18494
|
+
if (!prevTarget || !currTarget) {
|
|
18495
|
+
errors.push(`Replaced pool ${targetId} not found in keystone challenge pools. (E: afa2a8cb28c9b60e68994511a5825)`);
|
|
18496
|
+
}
|
|
18497
|
+
for (const prevPool of prevData.challengePools) {
|
|
18498
|
+
if (prevPool.id === targetId)
|
|
18499
|
+
continue;
|
|
18500
|
+
const currPool = currData.challengePools.find((p) => p.id === prevPool.id);
|
|
18501
|
+
if (!currPool)
|
|
18502
|
+
continue;
|
|
18503
|
+
if (JSON.stringify(prevPool.config) !== JSON.stringify(currPool.config)) {
|
|
18504
|
+
errors.push(`Unrelated pool config ${prevPool.id} was mutated during replace-pool. (E: bfa2a8cb28c9b60e68994511a5825)`);
|
|
18505
|
+
}
|
|
18506
|
+
if (JSON.stringify(prevPool.challenges) !== JSON.stringify(currPool.challenges)) {
|
|
18507
|
+
errors.push(`Unrelated pool challenges ${prevPool.id} were mutated during replace-pool. (E: cfa2a8cb28c9b60e68994511a5825)`);
|
|
18508
|
+
}
|
|
18509
|
+
}
|
|
18510
|
+
} else if (type === "change-password") {
|
|
18511
|
+
const changeInfo = info;
|
|
18512
|
+
if (!changeInfo || !Array.isArray(changeInfo.change) || changeInfo.change.length === 0) {
|
|
18513
|
+
errors.push(`details type is '${type}' but payload is invalid. (E: dfa2a8cb28c9b60e68994511a5825)`);
|
|
18514
|
+
continue;
|
|
18515
|
+
}
|
|
18516
|
+
const prevIds = prevData.challengePools.map((p) => p.id).sort();
|
|
18517
|
+
const currIds = currData.challengePools.map((p) => p.id).sort();
|
|
18518
|
+
if (JSON.stringify(prevIds) !== JSON.stringify(currIds)) {
|
|
18519
|
+
errors.push(`change-password transition mutated the pool IDs set. (E: efa2a8cb28c9b60e68994511a5825)`);
|
|
18520
|
+
}
|
|
18521
|
+
const rotatedIds = new Set(changeInfo.change);
|
|
18522
|
+
for (const prevPool of prevData.challengePools) {
|
|
18523
|
+
const currPool = currData.challengePools.find((p) => p.id === prevPool.id);
|
|
18524
|
+
if (!currPool)
|
|
18525
|
+
continue;
|
|
18526
|
+
if (rotatedIds.has(prevPool.id)) {
|
|
18527
|
+
if (prevPool.isForeign) {
|
|
18528
|
+
errors.push(`Security Violation: Password rotation attempted on foreign pool ${prevPool.id}. (E: ffa2a8cb28c9b60e68994511a5825)`);
|
|
18529
|
+
}
|
|
18530
|
+
const prevConfCopy = { ...prevPool.config, salt: "" };
|
|
18531
|
+
const currConfCopy = { ...currPool.config, salt: "" };
|
|
18532
|
+
if (JSON.stringify(prevConfCopy) !== JSON.stringify(currConfCopy)) {
|
|
18533
|
+
errors.push(`Pool config metadata for rotated pool ${prevPool.id} was mutated. (E: 00b2a8cb28c9b60e68994511a5825)`);
|
|
18534
|
+
}
|
|
18535
|
+
if (prevPool.config.salt === currPool.config.salt) {
|
|
18536
|
+
errors.push(`Pool salt for rotated pool ${prevPool.id} was not rotated. (E: 01b2a8cb28c9b60e68994511a5825)`);
|
|
18537
|
+
}
|
|
18538
|
+
} else {
|
|
18539
|
+
if (JSON.stringify(prevPool.config) !== JSON.stringify(currPool.config)) {
|
|
18540
|
+
errors.push(`Unrelated pool config ${prevPool.id} was mutated during change-password. (E: 02b2a8cb28c9b60e68994511a5825)`);
|
|
18541
|
+
}
|
|
18542
|
+
if (JSON.stringify(prevPool.challenges) !== JSON.stringify(currPool.challenges)) {
|
|
18543
|
+
errors.push(`Unrelated pool challenges ${prevPool.id} were mutated during change-password. (E: 03b2a8cb28c9b60e68994511a5825)`);
|
|
18544
|
+
}
|
|
18545
|
+
}
|
|
18546
|
+
}
|
|
18547
|
+
} else {
|
|
18548
|
+
errors.push(`Unknown claim details type: ${type}`);
|
|
18549
|
+
}
|
|
18550
|
+
}
|
|
18551
|
+
}
|
|
18420
18552
|
return errors;
|
|
18421
18553
|
} catch (error) {
|
|
18422
18554
|
console.error(`${lc2} ${extractErrorMsg(error)}`);
|
|
@@ -18704,6 +18836,46 @@ async function validateKeystoneGraph({ keystoneIbGib, dependencyGraph, getLatest
|
|
|
18704
18836
|
}
|
|
18705
18837
|
}
|
|
18706
18838
|
}
|
|
18839
|
+
async function generatePoolChallenges({ config, masterSecret }) {
|
|
18840
|
+
const strategy = KeystoneStrategyFactory.create({ config });
|
|
18841
|
+
const poolSecret = await strategy.derivePoolSecret({ masterSecret });
|
|
18842
|
+
const challenges = {};
|
|
18843
|
+
const targetSize = config.behavior.size;
|
|
18844
|
+
const timestamp = Date.now().toString();
|
|
18845
|
+
for (let i = 0; i < targetSize; i++) {
|
|
18846
|
+
const challengeId = await generateOpaqueChallengeId({
|
|
18847
|
+
salt: config.salt,
|
|
18848
|
+
timestamp,
|
|
18849
|
+
index: i
|
|
18850
|
+
});
|
|
18851
|
+
const solution = await strategy.generateSolution({
|
|
18852
|
+
poolSecret,
|
|
18853
|
+
poolId: config.id,
|
|
18854
|
+
challengeId
|
|
18855
|
+
});
|
|
18856
|
+
challenges[challengeId] = await strategy.generateChallenge({ solution });
|
|
18857
|
+
}
|
|
18858
|
+
return challenges;
|
|
18859
|
+
}
|
|
18860
|
+
async function rotatePoolChallenges({ prevPool, newMasterSecret }) {
|
|
18861
|
+
const prevConfig = prevPool.config;
|
|
18862
|
+
const newSalt = (await getUUID()).substring(0, 16);
|
|
18863
|
+
const newConfig = {
|
|
18864
|
+
...prevConfig,
|
|
18865
|
+
salt: newSalt
|
|
18866
|
+
};
|
|
18867
|
+
const newChallenges = await generatePoolChallenges({
|
|
18868
|
+
config: newConfig,
|
|
18869
|
+
masterSecret: newMasterSecret
|
|
18870
|
+
});
|
|
18871
|
+
return {
|
|
18872
|
+
id: prevPool.id,
|
|
18873
|
+
config: newConfig,
|
|
18874
|
+
challenges: newChallenges,
|
|
18875
|
+
...prevPool.isForeign !== void 0 ? { isForeign: prevPool.isForeign } : {},
|
|
18876
|
+
...prevPool.metadata ? { metadata: prevPool.metadata } : {}
|
|
18877
|
+
};
|
|
18878
|
+
}
|
|
18707
18879
|
|
|
18708
18880
|
// ../../libs/core-gib/dist/keystone/keystone-service-v1.mjs
|
|
18709
18881
|
var logalot41 = GLOBAL_LOG_A_LOT2;
|
|
@@ -18712,7 +18884,7 @@ var KeystoneService_V1 = class _KeystoneService_V1 {
|
|
|
18712
18884
|
/**
|
|
18713
18885
|
* Creates a brand new Keystone Identity Timeline.
|
|
18714
18886
|
*/
|
|
18715
|
-
async genesis({ masterSecret, frameDetails, configs, metaspace, space }) {
|
|
18887
|
+
async genesis({ masterSecret, frameDetails, configs, metaspace, space, isPrimary }) {
|
|
18716
18888
|
const lc2 = `${this.lc}[${this.genesis.name}]`;
|
|
18717
18889
|
try {
|
|
18718
18890
|
if (logalot41) {
|
|
@@ -18720,25 +18892,10 @@ var KeystoneService_V1 = class _KeystoneService_V1 {
|
|
|
18720
18892
|
}
|
|
18721
18893
|
const challengePools = [];
|
|
18722
18894
|
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
|
-
}
|
|
18895
|
+
const challenges = await generatePoolChallenges({
|
|
18896
|
+
config,
|
|
18897
|
+
masterSecret
|
|
18898
|
+
});
|
|
18742
18899
|
challengePools.push({
|
|
18743
18900
|
id: config.id,
|
|
18744
18901
|
config,
|
|
@@ -18749,8 +18906,11 @@ var KeystoneService_V1 = class _KeystoneService_V1 {
|
|
|
18749
18906
|
throw new Error(`No challenge pools created. (E: 38e538530996940e1f16a8b199995825)`);
|
|
18750
18907
|
}
|
|
18751
18908
|
const data = { challengePools, proofs: [] };
|
|
18752
|
-
if (frameDetails) {
|
|
18753
|
-
data.frameDetails = frameDetails;
|
|
18909
|
+
if (frameDetails || isPrimary !== void 0) {
|
|
18910
|
+
data.frameDetails = { ...frameDetails || {} };
|
|
18911
|
+
if (isPrimary !== void 0) {
|
|
18912
|
+
data.frameDetails.isPrimary = isPrimary;
|
|
18913
|
+
}
|
|
18754
18914
|
}
|
|
18755
18915
|
const keystoneIbGib = await createKeystoneIbGibImpl({ data, metaspace, space });
|
|
18756
18916
|
return keystoneIbGib;
|
|
@@ -19095,8 +19255,7 @@ var KeystoneService_V1 = class _KeystoneService_V1 {
|
|
|
19095
19255
|
const prevData = latestKeystone.data;
|
|
19096
19256
|
const pool = resolveTargetPool({
|
|
19097
19257
|
pools: prevData.challengePools,
|
|
19098
|
-
|
|
19099
|
-
// Explicitly require the special revoke pool
|
|
19258
|
+
verb: KEYSTONE_VERB_REVOKE
|
|
19100
19259
|
});
|
|
19101
19260
|
const claim = {
|
|
19102
19261
|
verb: KEYSTONE_VERB_REVOKE,
|
|
@@ -19196,8 +19355,10 @@ var KeystoneService_V1 = class _KeystoneService_V1 {
|
|
|
19196
19355
|
verb: KEYSTONE_VERB_MANAGE,
|
|
19197
19356
|
target,
|
|
19198
19357
|
// I am managing myself
|
|
19199
|
-
|
|
19200
|
-
|
|
19358
|
+
details: {
|
|
19359
|
+
type: KeystoneClaimType.add_pool,
|
|
19360
|
+
info: { add: newPools.map((p) => p.id) }
|
|
19361
|
+
}
|
|
19201
19362
|
};
|
|
19202
19363
|
const idsToSolve = await selectChallengeIds({
|
|
19203
19364
|
pool: adminPool,
|
|
@@ -19297,97 +19458,378 @@ var KeystoneService_V1 = class _KeystoneService_V1 {
|
|
|
19297
19458
|
throw error;
|
|
19298
19459
|
}
|
|
19299
19460
|
}
|
|
19300
|
-
|
|
19301
|
-
|
|
19302
|
-
|
|
19303
|
-
|
|
19304
|
-
|
|
19305
|
-
|
|
19306
|
-
|
|
19307
|
-
|
|
19308
|
-
|
|
19309
|
-
|
|
19310
|
-
|
|
19311
|
-
|
|
19312
|
-
|
|
19313
|
-
|
|
19314
|
-
|
|
19315
|
-
|
|
19316
|
-
|
|
19317
|
-
|
|
19318
|
-
|
|
19319
|
-
|
|
19320
|
-
|
|
19321
|
-
|
|
19322
|
-
|
|
19323
|
-
|
|
19324
|
-
|
|
19325
|
-
|
|
19326
|
-
|
|
19327
|
-
|
|
19328
|
-
|
|
19329
|
-
|
|
19330
|
-
|
|
19331
|
-
|
|
19332
|
-
|
|
19333
|
-
|
|
19334
|
-
|
|
19335
|
-
|
|
19336
|
-
|
|
19337
|
-
|
|
19338
|
-
|
|
19339
|
-
|
|
19340
|
-
|
|
19341
|
-
|
|
19342
|
-
|
|
19343
|
-
|
|
19344
|
-
|
|
19345
|
-
|
|
19346
|
-
|
|
19347
|
-
|
|
19348
|
-
|
|
19349
|
-
|
|
19350
|
-
|
|
19351
|
-
|
|
19352
|
-
|
|
19353
|
-
|
|
19354
|
-
|
|
19355
|
-
|
|
19356
|
-
}
|
|
19357
|
-
|
|
19358
|
-
|
|
19359
|
-
const solution = {
|
|
19360
|
-
type: "hash-reveal-v1",
|
|
19361
|
-
challengeId,
|
|
19362
|
-
poolId: SESSION_KEYSTONE_POLICY.CONNECT_POOL.ID,
|
|
19363
|
-
value: solutionValue
|
|
19364
|
-
};
|
|
19365
|
-
return await strategy.validateSolution({ solution, challenge });
|
|
19366
|
-
} catch (error) {
|
|
19367
|
-
console.error(`${lc2} ${extractErrorMsg(error)}`);
|
|
19368
|
-
throw error;
|
|
19369
|
-
} finally {
|
|
19370
|
-
if (logalot42) {
|
|
19371
|
-
console.log(`${lc2} complete.`);
|
|
19461
|
+
/**
|
|
19462
|
+
* Retrieves common aggregated info for a keystone timeline.
|
|
19463
|
+
*/
|
|
19464
|
+
async getKeystoneCommonInfo({ addr, ibGib, metaspace, space }) {
|
|
19465
|
+
const lc2 = `${this.lc}[getKeystoneCommonInfo]`;
|
|
19466
|
+
try {
|
|
19467
|
+
let targetAddr = addr;
|
|
19468
|
+
let targetIbGib = ibGib;
|
|
19469
|
+
if (!targetAddr) {
|
|
19470
|
+
if (!targetIbGib) {
|
|
19471
|
+
return null;
|
|
19472
|
+
}
|
|
19473
|
+
targetAddr = getIbGibAddr({ ibGib: targetIbGib });
|
|
19474
|
+
}
|
|
19475
|
+
if (!targetIbGib) {
|
|
19476
|
+
const { ib, gib } = getIbAndGib({ ibGibAddr: targetAddr });
|
|
19477
|
+
targetIbGib = { ib, gib };
|
|
19478
|
+
}
|
|
19479
|
+
const resGetLatest = await getLatestAddrs({
|
|
19480
|
+
ibGibs: [targetIbGib],
|
|
19481
|
+
space
|
|
19482
|
+
});
|
|
19483
|
+
if (!resGetLatest.data?.latestAddrsMap) {
|
|
19484
|
+
return null;
|
|
19485
|
+
}
|
|
19486
|
+
const latestAddr = resGetLatest.data.latestAddrsMap[targetAddr] || Object.values(resGetLatest.data.latestAddrsMap)[0];
|
|
19487
|
+
if (!latestAddr) {
|
|
19488
|
+
return null;
|
|
19489
|
+
}
|
|
19490
|
+
const resGet = await metaspace.get({ addr: latestAddr, space });
|
|
19491
|
+
const latestKeystone = resGet.ibGibs?.at(0);
|
|
19492
|
+
if (!latestKeystone || !latestKeystone.data) {
|
|
19493
|
+
return null;
|
|
19494
|
+
}
|
|
19495
|
+
const aggregateDetails = await this.getAggregateDetails({
|
|
19496
|
+
latestKeystone,
|
|
19497
|
+
metaspace,
|
|
19498
|
+
space
|
|
19499
|
+
});
|
|
19500
|
+
const name = aggregateDetails?.username || aggregateDetails?.profile?.name || aggregateDetails?.name || "";
|
|
19501
|
+
const description = aggregateDetails?.description || aggregateDetails?.profile?.description || "";
|
|
19502
|
+
const isPrimary = !!aggregateDetails?.isPrimary || !!aggregateDetails?.profile?.isPrimary;
|
|
19503
|
+
const latestFrameDetails = latestKeystone.data.frameDetails || {};
|
|
19504
|
+
const n = latestKeystone.data.n ?? 0;
|
|
19505
|
+
const timestamp = latestFrameDetails?.timestamp || aggregateDetails?.timestamp || "";
|
|
19506
|
+
const timestampMs = latestFrameDetails?.timestampMs || aggregateDetails?.timestampMs || void 0;
|
|
19507
|
+
return {
|
|
19508
|
+
name,
|
|
19509
|
+
description,
|
|
19510
|
+
isPrimary,
|
|
19511
|
+
aggregateDetails,
|
|
19512
|
+
latestFrameDetails,
|
|
19513
|
+
n,
|
|
19514
|
+
timestamp,
|
|
19515
|
+
timestampMs
|
|
19516
|
+
};
|
|
19517
|
+
} catch (error) {
|
|
19518
|
+
console.error(`${lc2} ${extractErrorMsg(error)}`);
|
|
19519
|
+
return null;
|
|
19372
19520
|
}
|
|
19373
19521
|
}
|
|
19374
|
-
|
|
19375
|
-
|
|
19376
|
-
|
|
19377
|
-
|
|
19378
|
-
|
|
19379
|
-
|
|
19380
|
-
|
|
19381
|
-
|
|
19382
|
-
|
|
19383
|
-
|
|
19384
|
-
|
|
19385
|
-
|
|
19386
|
-
|
|
19387
|
-
|
|
19388
|
-
|
|
19389
|
-
|
|
19390
|
-
|
|
19522
|
+
/**
|
|
19523
|
+
* Replaces a challenge pool (such as manage or custodian-manage) with a new pool,
|
|
19524
|
+
* authorizing the transition via the specified signing pool.
|
|
19525
|
+
*
|
|
19526
|
+
* @param latestKeystone The current tip frame of the keystone timeline.
|
|
19527
|
+
* - Visibility: Public (Public Merkle frame, safe for logs/ledger).
|
|
19528
|
+
* - Generator: Client/Server (Constructed locally and synced).
|
|
19529
|
+
*
|
|
19530
|
+
* @param newPool The fully constructed new pool to place in the next frame.
|
|
19531
|
+
* - Visibility: Public (Public configuration and public challenges, safe for ledger).
|
|
19532
|
+
* - Generator: Client/Server (Generated dynamically by the party proposing the update).
|
|
19533
|
+
*
|
|
19534
|
+
* @param signingSecret The master secret or derived KDF secret of the pool authorizing the change.
|
|
19535
|
+
* - Visibility: Private (Confidential secret credential. Must never go over the wire).
|
|
19536
|
+
* - Generator: Client/Server (Known only to the signing party).
|
|
19537
|
+
*
|
|
19538
|
+
* @param signingPoolId The ID of the pool used to authorize the transition (defaults to 'manage').
|
|
19539
|
+
* - Visibility: Public (Non-sensitive configuration string).
|
|
19540
|
+
* - Generator: Client/Server (From pool configuration).
|
|
19541
|
+
*/
|
|
19542
|
+
async replacePool({ latestKeystone, newPool, signingSecret, signingPoolId = POOL_ID_MANAGE, frameDetails, metaspace, space }) {
|
|
19543
|
+
const lc2 = `${this.lc}[${this.replacePool.name}]`;
|
|
19544
|
+
try {
|
|
19545
|
+
if (logalot41) {
|
|
19546
|
+
console.log(`${lc2} starting... replacing pool: ${newPool.id}`);
|
|
19547
|
+
}
|
|
19548
|
+
if (!latestKeystone.data) {
|
|
19549
|
+
throw new Error(`(UNEXPECTED) latestKeystone.data falsy? (E: 1334c8faed128166a999d428c7805b25)`);
|
|
19550
|
+
}
|
|
19551
|
+
const prevData = latestKeystone.data;
|
|
19552
|
+
if (prevData.revocationInfo) {
|
|
19553
|
+
throw new Error(`Keystone has been revoked. Cannot replace pool. (E: 2599f8f51c78d722252ddb2894fdbe25)`);
|
|
19554
|
+
}
|
|
19555
|
+
const prevPools = prevData.challengePools;
|
|
19556
|
+
const signingPool = prevPools.find((p) => p.id === signingPoolId);
|
|
19557
|
+
if (!signingPool) {
|
|
19558
|
+
throw new Error(`Signing pool not found: ${signingPoolId} (E: 3599f8f51c78d722252ddb2894fdbe25)`);
|
|
19559
|
+
}
|
|
19560
|
+
const target = getIbGibAddr({ ibGib: latestKeystone });
|
|
19561
|
+
const claim = {
|
|
19562
|
+
verb: KEYSTONE_VERB_MANAGE,
|
|
19563
|
+
target,
|
|
19564
|
+
details: {
|
|
19565
|
+
type: KeystoneClaimType.replace_pool,
|
|
19566
|
+
info: { replace: newPool.id }
|
|
19567
|
+
}
|
|
19568
|
+
};
|
|
19569
|
+
const idsToSolve = await selectChallengeIds({
|
|
19570
|
+
pool: signingPool,
|
|
19571
|
+
targetAddr: target
|
|
19572
|
+
});
|
|
19573
|
+
const { proof, nextPools } = await solveAndReplenish({
|
|
19574
|
+
targetPoolId: signingPoolId,
|
|
19575
|
+
prevPools,
|
|
19576
|
+
masterSecret: signingSecret,
|
|
19577
|
+
challengeIds: idsToSolve,
|
|
19578
|
+
claim
|
|
19579
|
+
});
|
|
19580
|
+
const targetIdx = nextPools.findIndex((p) => p.id === newPool.id);
|
|
19581
|
+
if (targetIdx === -1) {
|
|
19582
|
+
throw new Error(`Target pool to replace '${newPool.id}' not found in keystone challenge pools.`);
|
|
19583
|
+
}
|
|
19584
|
+
nextPools[targetIdx] = newPool;
|
|
19585
|
+
const n = (prevData.n ?? 0) + 1;
|
|
19586
|
+
let checkpointDetails;
|
|
19587
|
+
if (n % KEYSTONE_CHECKPOINT_FREQUENCY === 0) {
|
|
19588
|
+
const currentFrameView = {
|
|
19589
|
+
...latestKeystone,
|
|
19590
|
+
data: { ...prevData, frameDetails }
|
|
19591
|
+
};
|
|
19592
|
+
checkpointDetails = await this.getAggregateDetails({
|
|
19593
|
+
latestKeystone: currentFrameView,
|
|
19594
|
+
metaspace,
|
|
19595
|
+
space
|
|
19596
|
+
});
|
|
19597
|
+
}
|
|
19598
|
+
const newData = {
|
|
19599
|
+
challengePools: nextPools,
|
|
19600
|
+
proofs: [proof],
|
|
19601
|
+
frameDetails,
|
|
19602
|
+
checkpointDetails,
|
|
19603
|
+
...prevData.delegates ? { delegates: prevData.delegates } : {}
|
|
19604
|
+
};
|
|
19605
|
+
const newKeystone = await evolvePersistAndRegisterKeystone({
|
|
19606
|
+
prevIbGib: latestKeystone,
|
|
19607
|
+
newData,
|
|
19608
|
+
metaspace,
|
|
19609
|
+
space
|
|
19610
|
+
});
|
|
19611
|
+
return newKeystone;
|
|
19612
|
+
} catch (error) {
|
|
19613
|
+
console.error(`${lc2} ${extractErrorMsg(error)}`);
|
|
19614
|
+
throw error;
|
|
19615
|
+
} finally {
|
|
19616
|
+
if (logalot41) {
|
|
19617
|
+
console.log(`${lc2} complete.`);
|
|
19618
|
+
}
|
|
19619
|
+
}
|
|
19620
|
+
}
|
|
19621
|
+
/**
|
|
19622
|
+
* Evolve the keystone by rotating the primary user manage pool (`manage`) to a new master secret (passphrase).
|
|
19623
|
+
* This creates a new frame signed by the specified `signingPoolId` using `signingSecret`.
|
|
19624
|
+
*
|
|
19625
|
+
* Supports recovery (signing via `custodian-manage` using the custodian secret) and normal rotation
|
|
19626
|
+
* (signing via `manage` using the old password).
|
|
19627
|
+
*
|
|
19628
|
+
* @param latestKeystone The current tip frame of the keystone.
|
|
19629
|
+
* - Visibility: Public (Public Merkle frame, safe for ledger).
|
|
19630
|
+
* - Generator: Client/Server (Constructed locally and synced).
|
|
19631
|
+
*
|
|
19632
|
+
* @param newMasterSecret The new passphrase/master secret to set for the user's primary pools.
|
|
19633
|
+
* - Visibility: Private (Confidential secret credential. Must never go over the wire).
|
|
19634
|
+
* - Generator: Client (From user input in settings).
|
|
19635
|
+
*
|
|
19636
|
+
* @param signingSecret The secret of the pool authorizing this change (either old passphrase or derived custodian secret).
|
|
19637
|
+
* - Visibility: Private (Confidential secret. Must never go over the wire).
|
|
19638
|
+
* - Generator: Client/Server (Known only to the signing party).
|
|
19639
|
+
*
|
|
19640
|
+
* @param signingPoolId The ID of the pool used to authorize the rotation (defaults to 'manage').
|
|
19641
|
+
* - Visibility: Public (Non-sensitive ID string).
|
|
19642
|
+
* - Generator: Client/Server (From pool configuration).
|
|
19643
|
+
*/
|
|
19644
|
+
async changePassword({ latestKeystone, newMasterSecret, signingSecret, signingPoolId = POOL_ID_MANAGE, poolIds, frameDetails, metaspace, space }) {
|
|
19645
|
+
const lc2 = `${this.lc}[${this.changePassword.name}]`;
|
|
19646
|
+
try {
|
|
19647
|
+
if (logalot41) {
|
|
19648
|
+
console.log(`${lc2} starting...`);
|
|
19649
|
+
}
|
|
19650
|
+
if (!latestKeystone.data) {
|
|
19651
|
+
throw new Error(`latestKeystone.data falsy`);
|
|
19652
|
+
}
|
|
19653
|
+
if (latestKeystone.data.revocationInfo) {
|
|
19654
|
+
throw new Error(`Keystone has been revoked. Cannot change password. (E: 8599f8f51c78d722252ddb2894fdbe25)`);
|
|
19655
|
+
}
|
|
19656
|
+
const prevPools = latestKeystone.data.challengePools;
|
|
19657
|
+
const signingPool = prevPools.find((p) => p.id === signingPoolId);
|
|
19658
|
+
if (!signingPool) {
|
|
19659
|
+
throw new Error(`Signing pool not found: ${signingPoolId} (E: 3599f8f51c78d722252ddb2894fdbe25)`);
|
|
19660
|
+
}
|
|
19661
|
+
let targets = [];
|
|
19662
|
+
if (poolIds && poolIds.length > 0) {
|
|
19663
|
+
for (const id of poolIds) {
|
|
19664
|
+
const found = prevPools.find((p) => p.id === id);
|
|
19665
|
+
if (!found) {
|
|
19666
|
+
throw new Error(`Target pool to rotate not found: ${id} (E: 4599f8f51c78d722252ddb2894fdbe25)`);
|
|
19667
|
+
}
|
|
19668
|
+
targets.push(found);
|
|
19669
|
+
}
|
|
19670
|
+
} else {
|
|
19671
|
+
targets = prevPools.filter((p) => !p.isForeign);
|
|
19672
|
+
if (targets.length === 0) {
|
|
19673
|
+
throw new Error(`No non-foreign pools found to rotate. (E: 5599f8f51c78d722252ddb2894fdbe25)`);
|
|
19674
|
+
}
|
|
19675
|
+
}
|
|
19676
|
+
const rotatedPools = [];
|
|
19677
|
+
for (const targetPool of targets) {
|
|
19678
|
+
const rotated = await rotatePoolChallenges({
|
|
19679
|
+
prevPool: targetPool,
|
|
19680
|
+
newMasterSecret
|
|
19681
|
+
});
|
|
19682
|
+
rotatedPools.push(rotated);
|
|
19683
|
+
}
|
|
19684
|
+
const target = getIbGibAddr({ ibGib: latestKeystone });
|
|
19685
|
+
const claim = {
|
|
19686
|
+
verb: KEYSTONE_VERB_MANAGE,
|
|
19687
|
+
target,
|
|
19688
|
+
details: {
|
|
19689
|
+
type: KeystoneClaimType.change_password,
|
|
19690
|
+
info: { change: rotatedPools.map((p) => p.id) }
|
|
19691
|
+
}
|
|
19692
|
+
};
|
|
19693
|
+
const idsToSolve = await selectChallengeIds({
|
|
19694
|
+
pool: signingPool,
|
|
19695
|
+
targetAddr: target
|
|
19696
|
+
});
|
|
19697
|
+
const { proof, nextPools } = await solveAndReplenish({
|
|
19698
|
+
targetPoolId: signingPoolId,
|
|
19699
|
+
prevPools,
|
|
19700
|
+
masterSecret: signingSecret,
|
|
19701
|
+
challengeIds: idsToSolve,
|
|
19702
|
+
claim
|
|
19703
|
+
});
|
|
19704
|
+
for (const rotated of rotatedPools) {
|
|
19705
|
+
const idx = nextPools.findIndex((p) => p.id === rotated.id);
|
|
19706
|
+
if (idx === -1) {
|
|
19707
|
+
throw new Error(`Rotated pool ${rotated.id} not found in next pools list. (E: 6599f8f51c78d722252ddb2894fdbe25)`);
|
|
19708
|
+
}
|
|
19709
|
+
nextPools[idx] = rotated;
|
|
19710
|
+
}
|
|
19711
|
+
const n = (latestKeystone.data.n ?? 0) + 1;
|
|
19712
|
+
let checkpointDetails;
|
|
19713
|
+
if (n % KEYSTONE_CHECKPOINT_FREQUENCY === 0) {
|
|
19714
|
+
const currentFrameView = {
|
|
19715
|
+
...latestKeystone,
|
|
19716
|
+
data: { ...latestKeystone.data, challengePools: nextPools }
|
|
19717
|
+
};
|
|
19718
|
+
checkpointDetails = await this.getAggregateDetails({
|
|
19719
|
+
latestKeystone: currentFrameView,
|
|
19720
|
+
metaspace,
|
|
19721
|
+
space
|
|
19722
|
+
});
|
|
19723
|
+
}
|
|
19724
|
+
const newData = {
|
|
19725
|
+
challengePools: nextPools,
|
|
19726
|
+
proofs: [proof],
|
|
19727
|
+
frameDetails,
|
|
19728
|
+
checkpointDetails,
|
|
19729
|
+
...latestKeystone.data.delegates ? { delegates: latestKeystone.data.delegates } : {}
|
|
19730
|
+
};
|
|
19731
|
+
return await evolvePersistAndRegisterKeystone({
|
|
19732
|
+
prevIbGib: latestKeystone,
|
|
19733
|
+
newData,
|
|
19734
|
+
metaspace,
|
|
19735
|
+
space
|
|
19736
|
+
});
|
|
19737
|
+
} catch (error) {
|
|
19738
|
+
console.error(`${lc2} ${extractErrorMsg(error)}`);
|
|
19739
|
+
throw error;
|
|
19740
|
+
}
|
|
19741
|
+
}
|
|
19742
|
+
};
|
|
19743
|
+
|
|
19744
|
+
// ../../libs/core-gib/dist/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-receiver/sync-websocket-peer-helpers.mjs
|
|
19745
|
+
var logalot42 = GLOBAL_LOG_A_LOT2;
|
|
19746
|
+
var SESSION_KEYSTONE_POLICY = {
|
|
19747
|
+
COMMON: {
|
|
19748
|
+
ALGO: HashAlgorithm.sha_256,
|
|
19749
|
+
TYPE: KeystoneChallengeType.hash_reveal_v1,
|
|
19750
|
+
ROUNDS: 2,
|
|
19751
|
+
REPLENISH: KeystoneReplenishStrategy.topUp
|
|
19752
|
+
},
|
|
19753
|
+
CONNECT_POOL: {
|
|
19754
|
+
ID: POOL_ID_CONNECT,
|
|
19755
|
+
VERB: KEYSTONE_VERB_CONNECT,
|
|
19756
|
+
SIZE: 10,
|
|
19757
|
+
SELECT_SEQUENTIALLY: 2,
|
|
19758
|
+
SELECT_RANDOMLY: 2,
|
|
19759
|
+
TARGET_BINDING_COUNT: 0,
|
|
19760
|
+
SERVER_DEMAND_COUNT: 3
|
|
19761
|
+
}
|
|
19762
|
+
};
|
|
19763
|
+
function getConnectChallenge(keystone) {
|
|
19764
|
+
const lc2 = `[${getConnectChallenge.name}]`;
|
|
19765
|
+
try {
|
|
19766
|
+
if (logalot42) {
|
|
19767
|
+
console.log(`${lc2} starting... (I: 3c791af2f978a00a087dfdde90884826)`);
|
|
19768
|
+
}
|
|
19769
|
+
const connectPool = (keystone.data?.challengePools ?? []).find((p) => p.id === SESSION_KEYSTONE_POLICY.CONNECT_POOL.ID);
|
|
19770
|
+
if (!connectPool) {
|
|
19771
|
+
throw new Error(`Keystone missing "${SESSION_KEYSTONE_POLICY.CONNECT_POOL.ID}" pool (E: 6382e29260e4ddbac42ed18f49733526)`);
|
|
19772
|
+
}
|
|
19773
|
+
const challengeIds = Object.keys(connectPool.challenges).sort();
|
|
19774
|
+
if (challengeIds.length === 0) {
|
|
19775
|
+
throw new Error(`Connect pool has no challenges (E: 1ef62fdbda182a790835c8c796475226)`);
|
|
19776
|
+
}
|
|
19777
|
+
const challengeId = challengeIds[0];
|
|
19778
|
+
const challenge = connectPool.challenges[challengeId];
|
|
19779
|
+
return { challengeId, challenge };
|
|
19780
|
+
} catch (error) {
|
|
19781
|
+
console.error(`${lc2} ${extractErrorMsg(error)}`);
|
|
19782
|
+
throw error;
|
|
19783
|
+
} finally {
|
|
19784
|
+
if (logalot42) {
|
|
19785
|
+
console.log(`${lc2} complete.`);
|
|
19786
|
+
}
|
|
19787
|
+
}
|
|
19788
|
+
}
|
|
19789
|
+
async function checkConnectSolution(keystone, solutionValue) {
|
|
19790
|
+
const lc2 = `[${checkConnectSolution.name}]`;
|
|
19791
|
+
try {
|
|
19792
|
+
if (logalot42) {
|
|
19793
|
+
console.log(`${lc2} starting... (I: 166aa101e109194556e6b99575945826)`);
|
|
19794
|
+
}
|
|
19795
|
+
const connectPool = (keystone.data?.challengePools ?? []).find((p) => p.id === SESSION_KEYSTONE_POLICY.CONNECT_POOL.ID);
|
|
19796
|
+
if (!connectPool) {
|
|
19797
|
+
return false;
|
|
19798
|
+
}
|
|
19799
|
+
const { challengeId, challenge } = getConnectChallenge(keystone);
|
|
19800
|
+
const strategy = KeystoneStrategyFactory.create({ config: connectPool.config });
|
|
19801
|
+
const solution = {
|
|
19802
|
+
type: "hash-reveal-v1",
|
|
19803
|
+
challengeId,
|
|
19804
|
+
poolId: SESSION_KEYSTONE_POLICY.CONNECT_POOL.ID,
|
|
19805
|
+
value: solutionValue
|
|
19806
|
+
};
|
|
19807
|
+
return await strategy.validateSolution({ solution, challenge });
|
|
19808
|
+
} catch (error) {
|
|
19809
|
+
console.error(`${lc2} ${extractErrorMsg(error)}`);
|
|
19810
|
+
throw error;
|
|
19811
|
+
} finally {
|
|
19812
|
+
if (logalot42) {
|
|
19813
|
+
console.log(`${lc2} complete.`);
|
|
19814
|
+
}
|
|
19815
|
+
}
|
|
19816
|
+
}
|
|
19817
|
+
async function validateUpfrontConnect({ sAddr, solution, space, metaspace }) {
|
|
19818
|
+
const lc2 = `[${validateUpfrontConnect.name}]`;
|
|
19819
|
+
try {
|
|
19820
|
+
if (logalot42) {
|
|
19821
|
+
console.log(`${lc2} starting... (I: 7c21ecaf0b7f59347882bc18d8245826)`);
|
|
19822
|
+
}
|
|
19823
|
+
const addrErrors = validateIbGibAddr({ addr: sAddr }) ?? [];
|
|
19824
|
+
if (addrErrors.length > 0) {
|
|
19825
|
+
throw new Error(`Invalid sAddr: ${addrErrors.join(", ")} (E: 9a80cea03308ddab721eec08ffe0e826)`);
|
|
19826
|
+
}
|
|
19827
|
+
const { ib } = getIbAndGib({ ibGibAddr: sAddr });
|
|
19828
|
+
parseKeystoneIb({ ib });
|
|
19829
|
+
const resGet = await metaspace.get({ addrs: [sAddr], space });
|
|
19830
|
+
if (!resGet.success || !resGet.ibGibs || resGet.ibGibs.length !== 1) {
|
|
19831
|
+
throw new Error(`Session keystone not found in space: ${sAddr} (E: d381a8269328dbe6b8a9e4f86c2a3626)`);
|
|
19832
|
+
}
|
|
19391
19833
|
const authorizedS = resGet.ibGibs[0];
|
|
19392
19834
|
const isValid = await checkConnectSolution(authorizedS, solution);
|
|
19393
19835
|
if (!isValid) {
|
|
@@ -25225,6 +25667,662 @@ var SyncChannelHandler = class _SyncChannelHandler extends SyncUpgradeHandlerBas
|
|
|
25225
25667
|
};
|
|
25226
25668
|
var SyncUpgradeHandler = SyncChannelHandler;
|
|
25227
25669
|
|
|
25670
|
+
// ../../libs/web-gib/dist/identity/sso/sso-config-helper.mjs
|
|
25671
|
+
function loadSsoServerConfig() {
|
|
25672
|
+
const ssoServerKdfSecret = process.env.SSO_SERVER_KDF_SECRET;
|
|
25673
|
+
const sessionSecret = process.env.SESSION_SECRET;
|
|
25674
|
+
if (!ssoServerKdfSecret) {
|
|
25675
|
+
throw new Error(`SSO_SERVER_KDF_SECRET is missing from environment. (E: 840899ab4fdfd23e89cfae1889cfae12)`);
|
|
25676
|
+
}
|
|
25677
|
+
if (!sessionSecret) {
|
|
25678
|
+
throw new Error(`SESSION_SECRET is missing from environment. (E: bfa899ab4fdfd23e89cfae23)`);
|
|
25679
|
+
}
|
|
25680
|
+
const providers = {};
|
|
25681
|
+
if (process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET) {
|
|
25682
|
+
providers.google = {
|
|
25683
|
+
providerId: "google",
|
|
25684
|
+
clientId: process.env.GOOGLE_CLIENT_ID,
|
|
25685
|
+
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
|
25686
|
+
tokenUrl: "https://oauth2.googleapis.com/token",
|
|
25687
|
+
userInfoUrl: "https://openidconnect.googleapis.com/v1/userinfo",
|
|
25688
|
+
jwksUrl: "https://www.googleapis.com/oauth2/v3/certs"
|
|
25689
|
+
};
|
|
25690
|
+
}
|
|
25691
|
+
if (process.env.GITHUB_CLIENT_ID && process.env.GITHUB_CLIENT_SECRET) {
|
|
25692
|
+
providers.github = {
|
|
25693
|
+
providerId: "github",
|
|
25694
|
+
clientId: process.env.GITHUB_CLIENT_ID,
|
|
25695
|
+
clientSecret: process.env.GITHUB_CLIENT_SECRET,
|
|
25696
|
+
tokenUrl: "https://github.com/login/oauth/access_token",
|
|
25697
|
+
userInfoUrl: "https://api.github.com/user"
|
|
25698
|
+
};
|
|
25699
|
+
}
|
|
25700
|
+
return {
|
|
25701
|
+
ssoServerKdfSecret,
|
|
25702
|
+
sessionSecret,
|
|
25703
|
+
providers
|
|
25704
|
+
};
|
|
25705
|
+
}
|
|
25706
|
+
|
|
25707
|
+
// ../../libs/web-gib/dist/identity/sso/sso-custodian-service.mjs
|
|
25708
|
+
import { createPublicKey, verify } from "node:crypto";
|
|
25709
|
+
var SsoCustodianService = class _SsoCustodianService {
|
|
25710
|
+
config;
|
|
25711
|
+
lc = `[${_SsoCustodianService.name}]`;
|
|
25712
|
+
// In-memory cache for JSON Web Key Sets (JWKS) per provider
|
|
25713
|
+
jwksCache = /* @__PURE__ */ new Map();
|
|
25714
|
+
jwksTtlMs = 24 * 60 * 60 * 1e3;
|
|
25715
|
+
// Cache certs for 24 hours
|
|
25716
|
+
/**
|
|
25717
|
+
* @param config The server's global SSO configuration.
|
|
25718
|
+
* - Visibility: Private (Contains high-entropy secrets like `ssoServerKdfSecret` and `sessionSecret`).
|
|
25719
|
+
* - Generator: Server (Bootstrapped from node environment variables on server initialization).
|
|
25720
|
+
*/
|
|
25721
|
+
constructor(config) {
|
|
25722
|
+
this.config = config;
|
|
25723
|
+
}
|
|
25724
|
+
/**
|
|
25725
|
+
* Exchanges an authorization code for an OAuth2 token and retrieves normalized user info.
|
|
25726
|
+
*
|
|
25727
|
+
* @param providerId The string identifier of the OAuth provider (e.g. 'google' or 'github').
|
|
25728
|
+
* - Visibility: Public (Non-sensitive metadata string).
|
|
25729
|
+
* - Generator: Client (Sent from browser UI selection).
|
|
25730
|
+
*
|
|
25731
|
+
* @param code The temporary authorization code returned by the OAuth provider.
|
|
25732
|
+
* - Visibility: Private (Confidential short-lived bearer credential. Must be kept private).
|
|
25733
|
+
* - Generator: Provider (Created by provider's OAuth page and sent to client redirect URI).
|
|
25734
|
+
*
|
|
25735
|
+
* @param redirectUri The redirect URI registered in the provider portal.
|
|
25736
|
+
* - Visibility: Public (Configuration URL).
|
|
25737
|
+
* - Generator: Client/Server (Derived from environmental configuration and sent by client).
|
|
25738
|
+
*/
|
|
25739
|
+
async getOAuthUserInfo(providerId, code, redirectUri) {
|
|
25740
|
+
const lc2 = `${this.lc}[${this.getOAuthUserInfo.name}]`;
|
|
25741
|
+
const providerConfig = this.config.providers[providerId];
|
|
25742
|
+
if (!providerConfig) {
|
|
25743
|
+
throw new Error(`Provider '${providerId}' is not configured on this server. (E: c28900abfefd23e89cf23f12)`);
|
|
25744
|
+
}
|
|
25745
|
+
console.log(`${lc2} Exchanging code for provider: ${providerId}`);
|
|
25746
|
+
const tokens = await this.exchangeCodeForTokens(providerConfig, code, redirectUri);
|
|
25747
|
+
if (providerId === "google") {
|
|
25748
|
+
if (!tokens.id_token) {
|
|
25749
|
+
throw new Error(`Google token exchange did not return id_token. (E: e28900abfefd23e89cf45f23)`);
|
|
25750
|
+
}
|
|
25751
|
+
return this.verifyAndNormalizeGoogleToken(tokens.id_token);
|
|
25752
|
+
} else if (providerId === "github") {
|
|
25753
|
+
if (!tokens.access_token) {
|
|
25754
|
+
throw new Error(`GitHub token exchange did not return access_token. (E: a28900abfefd23e89cf56f34)`);
|
|
25755
|
+
}
|
|
25756
|
+
return this.fetchAndNormalizeGithubUser(providerConfig, tokens.access_token);
|
|
25757
|
+
}
|
|
25758
|
+
throw new Error(`Unsupported provider: ${providerId}. (E: d28900abfefd23e89cf67f45)`);
|
|
25759
|
+
}
|
|
25760
|
+
/**
|
|
25761
|
+
* Directly validates a Google ID Token (JWT) locally and returns normalized user info.
|
|
25762
|
+
* Useful for client-side authentication handshakes.
|
|
25763
|
+
*
|
|
25764
|
+
* @param idToken The raw base64-encoded signed JSON Web Token (JWT) ID token returned by Google.
|
|
25765
|
+
* - Visibility: Private (Sensitive bearer credential containing signature and claims. Must be kept private).
|
|
25766
|
+
* - Generator: Provider (Generated and signed cryptographically by Google authentication servers).
|
|
25767
|
+
*/
|
|
25768
|
+
async verifyAndNormalizeGoogleToken(idToken) {
|
|
25769
|
+
const lc2 = `${this.lc}[${this.verifyAndNormalizeGoogleToken.name}]`;
|
|
25770
|
+
const providerConfig = this.config.providers.google;
|
|
25771
|
+
if (!providerConfig) {
|
|
25772
|
+
throw new Error(`Google provider config is missing. (E: g28900abfefd23e89cf78f56)`);
|
|
25773
|
+
}
|
|
25774
|
+
const payload = await this.verifyJwt(idToken, providerConfig);
|
|
25775
|
+
const nowSeconds = Math.floor(Date.now() / 1e3);
|
|
25776
|
+
if (payload.exp && payload.exp < nowSeconds) {
|
|
25777
|
+
throw new Error(`Token has expired. (E: t28900abfefd23e89cf89f67)`);
|
|
25778
|
+
}
|
|
25779
|
+
if (payload.aud !== providerConfig.clientId) {
|
|
25780
|
+
throw new Error(`Token audience mismatch. Expected ${providerConfig.clientId}, got ${payload.aud}. (E: m28900abfefd23e89cf90f78)`);
|
|
25781
|
+
}
|
|
25782
|
+
if (!payload.sub) {
|
|
25783
|
+
throw new Error(`Google JWT is missing subject claim ('sub'). (E: s28900abfefd23e89cfa1f89)`);
|
|
25784
|
+
}
|
|
25785
|
+
return {
|
|
25786
|
+
providerKey: `google:${payload.sub}`,
|
|
25787
|
+
providerId: "google",
|
|
25788
|
+
sub: payload.sub,
|
|
25789
|
+
email: payload.email,
|
|
25790
|
+
name: payload.name,
|
|
25791
|
+
picture: payload.picture
|
|
25792
|
+
};
|
|
25793
|
+
}
|
|
25794
|
+
/**
|
|
25795
|
+
* Internal: Exchange authorization code for access token / ID token.
|
|
25796
|
+
*
|
|
25797
|
+
* @param providerConfig Server configuration settings for the specific provider.
|
|
25798
|
+
* - Visibility: Private (Contains provider client secrets. Must stay server-side).
|
|
25799
|
+
* - Generator: Server (Configured from node environment variables).
|
|
25800
|
+
*
|
|
25801
|
+
* @param code The temporary authorization code to exchange.
|
|
25802
|
+
* - Visibility: Private (Short-lived credential. Must be kept private).
|
|
25803
|
+
* - Generator: Provider (Via OAuth flow).
|
|
25804
|
+
*
|
|
25805
|
+
* @param redirectUri The redirect URI registered for the exchange.
|
|
25806
|
+
* - Visibility: Public (Configuration URL).
|
|
25807
|
+
* - Generator: Client/Server (From app configuration).
|
|
25808
|
+
*/
|
|
25809
|
+
async exchangeCodeForTokens(providerConfig, code, redirectUri) {
|
|
25810
|
+
const body = new URLSearchParams({
|
|
25811
|
+
client_id: providerConfig.clientId,
|
|
25812
|
+
client_secret: providerConfig.clientSecret,
|
|
25813
|
+
code,
|
|
25814
|
+
redirect_uri: redirectUri,
|
|
25815
|
+
grant_type: "authorization_code"
|
|
25816
|
+
});
|
|
25817
|
+
const headers = {
|
|
25818
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
25819
|
+
};
|
|
25820
|
+
if (providerConfig.providerId === "github") {
|
|
25821
|
+
headers["Accept"] = "application/json";
|
|
25822
|
+
}
|
|
25823
|
+
const res = await fetch(providerConfig.tokenUrl, {
|
|
25824
|
+
method: "POST",
|
|
25825
|
+
headers,
|
|
25826
|
+
body: body.toString()
|
|
25827
|
+
});
|
|
25828
|
+
if (!res.ok) {
|
|
25829
|
+
const errText = await res.text();
|
|
25830
|
+
throw new Error(`Token exchange failed (HTTP ${res.status}): ${errText}`);
|
|
25831
|
+
}
|
|
25832
|
+
return res.json();
|
|
25833
|
+
}
|
|
25834
|
+
/**
|
|
25835
|
+
* Internal: Fetch GitHub user profile and normalize.
|
|
25836
|
+
*
|
|
25837
|
+
* @param providerConfig Server configuration settings for GitHub.
|
|
25838
|
+
* - Visibility: Private (Contains GitHub client secret).
|
|
25839
|
+
* - Generator: Server (From environment config).
|
|
25840
|
+
*
|
|
25841
|
+
* @param accessToken The active OAuth2 access token to authorize the API call.
|
|
25842
|
+
* - Visibility: Private (Bearer token. Must be kept private).
|
|
25843
|
+
* - Generator: Provider (GitHub token endpoint).
|
|
25844
|
+
*/
|
|
25845
|
+
async fetchAndNormalizeGithubUser(providerConfig, accessToken) {
|
|
25846
|
+
const res = await fetch(providerConfig.userInfoUrl, {
|
|
25847
|
+
headers: {
|
|
25848
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
25849
|
+
"User-Agent": "space-gib-auth-agent"
|
|
25850
|
+
// GitHub API requires User-Agent
|
|
25851
|
+
}
|
|
25852
|
+
});
|
|
25853
|
+
if (!res.ok) {
|
|
25854
|
+
const errText = await res.text();
|
|
25855
|
+
throw new Error(`Failed to fetch GitHub user info (HTTP ${res.status}): ${errText}`);
|
|
25856
|
+
}
|
|
25857
|
+
const rawUser = await res.json();
|
|
25858
|
+
if (!rawUser.id) {
|
|
25859
|
+
throw new Error(`GitHub user info response missing 'id' field.`);
|
|
25860
|
+
}
|
|
25861
|
+
const sub = String(rawUser.id);
|
|
25862
|
+
return {
|
|
25863
|
+
providerKey: `github:${sub}`,
|
|
25864
|
+
providerId: "github",
|
|
25865
|
+
sub,
|
|
25866
|
+
email: rawUser.email || void 0,
|
|
25867
|
+
name: rawUser.name || rawUser.login || void 0,
|
|
25868
|
+
picture: rawUser.avatar_url || void 0
|
|
25869
|
+
};
|
|
25870
|
+
}
|
|
25871
|
+
/**
|
|
25872
|
+
* Internal: Decodes and cryptographically verifies a JWT signature using JWKS public keys.
|
|
25873
|
+
*
|
|
25874
|
+
* @param token The raw base64-encoded JWT token to verify.
|
|
25875
|
+
* - Visibility: Private (Sensitive token. Must be kept private).
|
|
25876
|
+
* - Generator: Provider (Google / OIDC server).
|
|
25877
|
+
*
|
|
25878
|
+
* @param providerConfig The target provider server settings containing `jwksUrl`.
|
|
25879
|
+
* - Visibility: Private (Contains secrets).
|
|
25880
|
+
* - Generator: Server (From environment config).
|
|
25881
|
+
*/
|
|
25882
|
+
async verifyJwt(token, providerConfig) {
|
|
25883
|
+
const parts = token.split(".");
|
|
25884
|
+
if (parts.length !== 3) {
|
|
25885
|
+
throw new Error("JWT must have 3 parts separated by dots.");
|
|
25886
|
+
}
|
|
25887
|
+
const [headerB64, payloadB64, signatureB64] = parts;
|
|
25888
|
+
let header;
|
|
25889
|
+
try {
|
|
25890
|
+
header = JSON.parse(Buffer.from(headerB64, "base64url").toString("utf-8"));
|
|
25891
|
+
} catch {
|
|
25892
|
+
throw new Error("Failed to parse JWT header.");
|
|
25893
|
+
}
|
|
25894
|
+
if (header.alg !== "RS256") {
|
|
25895
|
+
throw new Error(`Only RS256 algorithm is supported, got: ${header.alg}`);
|
|
25896
|
+
}
|
|
25897
|
+
const kid = header.kid;
|
|
25898
|
+
if (!kid) {
|
|
25899
|
+
throw new Error("JWT header missing key ID (kid).");
|
|
25900
|
+
}
|
|
25901
|
+
const keys = await this.getJwksKeys(providerConfig);
|
|
25902
|
+
const jwk = keys.find((key) => key.kid === kid);
|
|
25903
|
+
if (!jwk) {
|
|
25904
|
+
throw new Error(`JWK public key matching kid '${kid}' not found.`);
|
|
25905
|
+
}
|
|
25906
|
+
const publicKey = createPublicKey({
|
|
25907
|
+
key: jwk,
|
|
25908
|
+
format: "jwk"
|
|
25909
|
+
});
|
|
25910
|
+
const signature = Buffer.from(signatureB64, "base64url");
|
|
25911
|
+
const data = Buffer.from(`${headerB64}.${payloadB64}`);
|
|
25912
|
+
const isValid = verify("sha256", data, publicKey, signature);
|
|
25913
|
+
if (!isValid) {
|
|
25914
|
+
throw new Error("Cryptographic signature verification failed.");
|
|
25915
|
+
}
|
|
25916
|
+
try {
|
|
25917
|
+
return JSON.parse(Buffer.from(payloadB64, "base64url").toString("utf-8"));
|
|
25918
|
+
} catch {
|
|
25919
|
+
throw new Error("Failed to parse JWT payload.");
|
|
25920
|
+
}
|
|
25921
|
+
}
|
|
25922
|
+
/**
|
|
25923
|
+
* Internal: Retrieves JWKS keys from cache or fetches them from the provider's jwksUrl.
|
|
25924
|
+
*
|
|
25925
|
+
* @param providerConfig The provider config containing the target `jwksUrl`.
|
|
25926
|
+
* - Visibility: Private (Contains client secrets).
|
|
25927
|
+
* - Generator: Server (From environment config).
|
|
25928
|
+
*/
|
|
25929
|
+
async getJwksKeys(providerConfig) {
|
|
25930
|
+
const providerId = providerConfig.providerId;
|
|
25931
|
+
const cached = this.jwksCache.get(providerId);
|
|
25932
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
25933
|
+
return cached.keys;
|
|
25934
|
+
}
|
|
25935
|
+
if (!providerConfig.jwksUrl) {
|
|
25936
|
+
throw new Error(`Provider '${providerId}' does not define a jwksUrl for JWT signature validation.`);
|
|
25937
|
+
}
|
|
25938
|
+
const res = await fetch(providerConfig.jwksUrl);
|
|
25939
|
+
if (!res.ok) {
|
|
25940
|
+
throw new Error(`Failed to fetch JWKS from ${providerConfig.jwksUrl} (HTTP ${res.status})`);
|
|
25941
|
+
}
|
|
25942
|
+
const body = await res.json();
|
|
25943
|
+
if (!body.keys || !Array.isArray(body.keys)) {
|
|
25944
|
+
throw new Error(`Invalid JWKS response structure from ${providerConfig.jwksUrl}`);
|
|
25945
|
+
}
|
|
25946
|
+
this.jwksCache.set(providerId, {
|
|
25947
|
+
keys: body.keys,
|
|
25948
|
+
expiresAt: Date.now() + this.jwksTtlMs
|
|
25949
|
+
});
|
|
25950
|
+
return body.keys;
|
|
25951
|
+
}
|
|
25952
|
+
/**
|
|
25953
|
+
* Derives a server-delegate master secret deterministically using the server's private KDF secret,
|
|
25954
|
+
* the providerKey, and the user's public nonce.
|
|
25955
|
+
*
|
|
25956
|
+
* @param providerKey The unique key identifying the provider and user sub ID (e.g. `google:123...`).
|
|
25957
|
+
* - Visibility: Public (Identifier saved on parent's delegate claim, safe for log/ledger storage).
|
|
25958
|
+
* - Generator: Provider/Server (Derived by mapping provider user sub ID after authentication).
|
|
25959
|
+
*
|
|
25960
|
+
* @param userNonce The public, high-entropy nonce generated by the client to salt the KDF derivation.
|
|
25961
|
+
* - Visibility: Public (Salt value stored on parent's delegate claim, safe for ledger storage).
|
|
25962
|
+
* - Generator: Client (Generated offline on user's device).
|
|
25963
|
+
*/
|
|
25964
|
+
async deriveServerDelegateSecret(providerKey, userNonce) {
|
|
25965
|
+
const salt = await hash({
|
|
25966
|
+
s: `${providerKey}:${userNonce}`,
|
|
25967
|
+
algorithm: HashAlgorithm.sha_512
|
|
25968
|
+
});
|
|
25969
|
+
return await kdf_recursiveSaltWrap({
|
|
25970
|
+
masterSecret: this.config.ssoServerKdfSecret,
|
|
25971
|
+
salt,
|
|
25972
|
+
rounds: 1e3,
|
|
25973
|
+
algorithm: HashAlgorithm.sha_512
|
|
25974
|
+
});
|
|
25975
|
+
}
|
|
25976
|
+
/**
|
|
25977
|
+
* Creates a public custodian challenge pool (custodian-manage) derived deterministically
|
|
25978
|
+
* from the server's private KDF secret, providerKey, and userNonce.
|
|
25979
|
+
*
|
|
25980
|
+
* @param providerKey The unique key identifying the provider and user sub ID (e.g. `google:123...`).
|
|
25981
|
+
* - Visibility: Public (Mapped identifier, safe for ledger).
|
|
25982
|
+
* - Generator: Provider/Server (Derived after successful token exchange).
|
|
25983
|
+
*
|
|
25984
|
+
* @param userNonce The public, high-entropy nonce.
|
|
25985
|
+
* - Visibility: Public (Derivation salt, safe for ledger).
|
|
25986
|
+
* - Generator: Client (Generated on user's device).
|
|
25987
|
+
*/
|
|
25988
|
+
async createCustodianChallengePool(providerKey, userNonce) {
|
|
25989
|
+
const custodianSecret = await this.deriveServerDelegateSecret(providerKey, userNonce);
|
|
25990
|
+
const poolSalt = await hash({
|
|
25991
|
+
s: `${providerKey}:${userNonce}:salt`,
|
|
25992
|
+
algorithm: HashAlgorithm.sha_256
|
|
25993
|
+
});
|
|
25994
|
+
const poolSaltShort = poolSalt.substring(0, 16);
|
|
25995
|
+
const config = createStandardPoolConfig({
|
|
25996
|
+
id: POOL_ID_CUSTODIAN_MANAGE,
|
|
25997
|
+
salt: poolSaltShort
|
|
25998
|
+
});
|
|
25999
|
+
config.allowedVerbs = [KEYSTONE_VERB_MANAGE, KEYSTONE_VERB_REVOKE];
|
|
26000
|
+
const strategy = KeystoneStrategyFactory.create({ config });
|
|
26001
|
+
const poolSecret = await strategy.derivePoolSecret({ masterSecret: custodianSecret });
|
|
26002
|
+
const challenges = {};
|
|
26003
|
+
const timestamp = Date.now().toString();
|
|
26004
|
+
for (let i = 0; i < config.behavior.size; i++) {
|
|
26005
|
+
const challengeId = await generateOpaqueChallengeId({
|
|
26006
|
+
salt: config.salt,
|
|
26007
|
+
timestamp,
|
|
26008
|
+
index: i
|
|
26009
|
+
});
|
|
26010
|
+
const solution = await strategy.generateSolution({
|
|
26011
|
+
poolSecret,
|
|
26012
|
+
poolId: config.id,
|
|
26013
|
+
challengeId
|
|
26014
|
+
});
|
|
26015
|
+
challenges[challengeId] = await strategy.generateChallenge({ solution });
|
|
26016
|
+
}
|
|
26017
|
+
return {
|
|
26018
|
+
id: POOL_ID_CUSTODIAN_MANAGE,
|
|
26019
|
+
config,
|
|
26020
|
+
challenges,
|
|
26021
|
+
isForeign: true,
|
|
26022
|
+
metadata: {
|
|
26023
|
+
owner: "custodian",
|
|
26024
|
+
providerKey
|
|
26025
|
+
}
|
|
26026
|
+
};
|
|
26027
|
+
}
|
|
26028
|
+
/**
|
|
26029
|
+
* Maps private server configuration to safe public client configurations.
|
|
26030
|
+
*
|
|
26031
|
+
* @param hostUrl The root URL host address of the server (e.g. `https://space-gib.localhost` or `https://ibgib.space`).
|
|
26032
|
+
* - Visibility: Public (Non-sensitive hostname configuration).
|
|
26033
|
+
* - Generator: Server (Extracted from the HTTP request context headers/protocol).
|
|
26034
|
+
*/
|
|
26035
|
+
getSsoClientConfigs(hostUrl) {
|
|
26036
|
+
const configs = [];
|
|
26037
|
+
if (this.config.providers.google) {
|
|
26038
|
+
configs.push({
|
|
26039
|
+
providerId: "google",
|
|
26040
|
+
clientId: this.config.providers.google.clientId,
|
|
26041
|
+
authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
|
|
26042
|
+
redirectUri: `${hostUrl}/`,
|
|
26043
|
+
scope: "openid email profile"
|
|
26044
|
+
});
|
|
26045
|
+
}
|
|
26046
|
+
if (this.config.providers.github) {
|
|
26047
|
+
configs.push({
|
|
26048
|
+
providerId: "github",
|
|
26049
|
+
clientId: this.config.providers.github.clientId,
|
|
26050
|
+
authUrl: "https://github.com/login/oauth/authorize",
|
|
26051
|
+
redirectUri: `${hostUrl}/`,
|
|
26052
|
+
scope: "read:user user:email"
|
|
26053
|
+
});
|
|
26054
|
+
}
|
|
26055
|
+
return configs;
|
|
26056
|
+
}
|
|
26057
|
+
};
|
|
26058
|
+
|
|
26059
|
+
// src/server/serve-gib/handlers/api/keystone/sso-link.handler.mts
|
|
26060
|
+
var logalot60 = GLOBAL_LOG_A_LOT || true;
|
|
26061
|
+
var custodian;
|
|
26062
|
+
function getCustodian() {
|
|
26063
|
+
if (!custodian) {
|
|
26064
|
+
const config = loadSsoServerConfig();
|
|
26065
|
+
custodian = new SsoCustodianService(config);
|
|
26066
|
+
}
|
|
26067
|
+
return custodian;
|
|
26068
|
+
}
|
|
26069
|
+
var SsoLinkHandler = class _SsoLinkHandler extends ServeGibHandlerWithMetaspaceBase {
|
|
26070
|
+
lc = `[${_SsoLinkHandler.name}]`;
|
|
26071
|
+
method = "POST";
|
|
26072
|
+
regex = API_PATH_REGEXES.IDENTITY_SSO_LINK;
|
|
26073
|
+
async parseParamsImpl(reqCtx) {
|
|
26074
|
+
let parsed;
|
|
26075
|
+
try {
|
|
26076
|
+
parsed = JSON.parse(reqCtx.body);
|
|
26077
|
+
} catch {
|
|
26078
|
+
return void 0;
|
|
26079
|
+
}
|
|
26080
|
+
const parentTjpAddr = parsed?.parentTjpAddr;
|
|
26081
|
+
if (!parentTjpAddr) {
|
|
26082
|
+
return void 0;
|
|
26083
|
+
}
|
|
26084
|
+
return {
|
|
26085
|
+
parentTjpAddr,
|
|
26086
|
+
domainInfo: this.getDomainInfo({ domainAddr: parentTjpAddr })
|
|
26087
|
+
};
|
|
26088
|
+
}
|
|
26089
|
+
async validateQueryParams({ queryParams }) {
|
|
26090
|
+
return [];
|
|
26091
|
+
}
|
|
26092
|
+
async handleRouteImpl(reqCtx) {
|
|
26093
|
+
const lc2 = `${this.lc}[${this.handleRouteImpl.name}]`;
|
|
26094
|
+
try {
|
|
26095
|
+
if (logalot60) {
|
|
26096
|
+
console.log(`${lc2} starting...`);
|
|
26097
|
+
}
|
|
26098
|
+
if (!reqCtx.params) {
|
|
26099
|
+
return this.error(400, "Invalid parameters: parentTjpAddr missing");
|
|
26100
|
+
}
|
|
26101
|
+
if (!reqCtx.metaspace) {
|
|
26102
|
+
return this.error(500, "Metaspace not initialized");
|
|
26103
|
+
}
|
|
26104
|
+
let parsed;
|
|
26105
|
+
try {
|
|
26106
|
+
parsed = JSON.parse(reqCtx.body);
|
|
26107
|
+
} catch {
|
|
26108
|
+
return this.error(400, "Request body must be valid JSON");
|
|
26109
|
+
}
|
|
26110
|
+
const { code, providerId, parentTjpAddr, userNonce, redirectUri } = parsed;
|
|
26111
|
+
if (!code || !providerId || !parentTjpAddr || !userNonce || !redirectUri) {
|
|
26112
|
+
return this.error(400, "Body missing required parameters (code, providerId, parentTjpAddr, userNonce, redirectUri)");
|
|
26113
|
+
}
|
|
26114
|
+
const space = await reqCtx.metaspace.getLocalUserSpace({ lock: false });
|
|
26115
|
+
if (!space) {
|
|
26116
|
+
return this.error(500, "No local user space found in domain metaspace");
|
|
26117
|
+
}
|
|
26118
|
+
const service = getCustodian();
|
|
26119
|
+
const userInfo = await service.getOAuthUserInfo(providerId, code, redirectUri);
|
|
26120
|
+
const custodianPool = await service.createCustodianChallengePool(
|
|
26121
|
+
userInfo.providerKey,
|
|
26122
|
+
userNonce
|
|
26123
|
+
);
|
|
26124
|
+
console.log(`${lc2} created custodian pool for: ${userInfo.providerKey}`);
|
|
26125
|
+
return this.ok({
|
|
26126
|
+
success: true,
|
|
26127
|
+
custodianPool
|
|
26128
|
+
}, 200);
|
|
26129
|
+
} catch (error) {
|
|
26130
|
+
const emsg = extractErrorMsg(error);
|
|
26131
|
+
console.error(`${lc2} Link failed: ${emsg}`);
|
|
26132
|
+
return this.error(500, `Link failed: ${emsg}`);
|
|
26133
|
+
} finally {
|
|
26134
|
+
if (logalot60) {
|
|
26135
|
+
console.log(`${lc2} complete.`);
|
|
26136
|
+
}
|
|
26137
|
+
}
|
|
26138
|
+
}
|
|
26139
|
+
};
|
|
26140
|
+
|
|
26141
|
+
// src/server/serve-gib/handlers/api/keystone/sso-login.handler.mts
|
|
26142
|
+
import { createHmac } from "node:crypto";
|
|
26143
|
+
var logalot61 = GLOBAL_LOG_A_LOT || true;
|
|
26144
|
+
var custodian2;
|
|
26145
|
+
function getCustodian2() {
|
|
26146
|
+
if (!custodian2) {
|
|
26147
|
+
const config = loadSsoServerConfig();
|
|
26148
|
+
custodian2 = new SsoCustodianService(config);
|
|
26149
|
+
}
|
|
26150
|
+
return custodian2;
|
|
26151
|
+
}
|
|
26152
|
+
function signSessionToken(payload, secret) {
|
|
26153
|
+
const header = { alg: "HS256", typ: "JWT" };
|
|
26154
|
+
const headerB64 = Buffer.from(JSON.stringify(header)).toString("base64url");
|
|
26155
|
+
const payloadB64 = Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
26156
|
+
const data = `${headerB64}.${payloadB64}`;
|
|
26157
|
+
const signature = createHmac("sha256", secret).update(data).digest("base64url");
|
|
26158
|
+
return `${data}.${signature}`;
|
|
26159
|
+
}
|
|
26160
|
+
var SsoLoginHandler = class _SsoLoginHandler extends ServeGibHandlerWithMetaspaceBase {
|
|
26161
|
+
lc = `[${_SsoLoginHandler.name}]`;
|
|
26162
|
+
method = "POST";
|
|
26163
|
+
regex = API_PATH_REGEXES.IDENTITY_SSO_LOGIN;
|
|
26164
|
+
async parseParamsImpl(reqCtx) {
|
|
26165
|
+
let parsed;
|
|
26166
|
+
try {
|
|
26167
|
+
parsed = JSON.parse(reqCtx.body);
|
|
26168
|
+
} catch {
|
|
26169
|
+
return void 0;
|
|
26170
|
+
}
|
|
26171
|
+
const parentTjpAddr = parsed?.parentTjpAddr;
|
|
26172
|
+
if (!parentTjpAddr) {
|
|
26173
|
+
return void 0;
|
|
26174
|
+
}
|
|
26175
|
+
return {
|
|
26176
|
+
parentTjpAddr,
|
|
26177
|
+
domainInfo: this.getDomainInfo({ domainAddr: parentTjpAddr })
|
|
26178
|
+
};
|
|
26179
|
+
}
|
|
26180
|
+
async validateQueryParams({ queryParams }) {
|
|
26181
|
+
return [];
|
|
26182
|
+
}
|
|
26183
|
+
async handleRouteImpl(reqCtx) {
|
|
26184
|
+
const lc2 = `${this.lc}[${this.handleRouteImpl.name}]`;
|
|
26185
|
+
try {
|
|
26186
|
+
if (logalot61) {
|
|
26187
|
+
console.log(`${lc2} starting...`);
|
|
26188
|
+
}
|
|
26189
|
+
if (!reqCtx.params) {
|
|
26190
|
+
return this.error(400, "Invalid parameters: parentTjpAddr missing");
|
|
26191
|
+
}
|
|
26192
|
+
if (!reqCtx.metaspace) {
|
|
26193
|
+
return this.error(500, "Metaspace not initialized");
|
|
26194
|
+
}
|
|
26195
|
+
let parsed;
|
|
26196
|
+
try {
|
|
26197
|
+
parsed = JSON.parse(reqCtx.body);
|
|
26198
|
+
} catch {
|
|
26199
|
+
return this.error(400, "Request body must be valid JSON");
|
|
26200
|
+
}
|
|
26201
|
+
const { code, providerId, parentTjpAddr, userNonce, redirectUri } = parsed;
|
|
26202
|
+
if (!code || !providerId || !parentTjpAddr || !userNonce || !redirectUri) {
|
|
26203
|
+
return this.error(400, "Body missing required parameters (code, providerId, parentTjpAddr, userNonce, redirectUri)");
|
|
26204
|
+
}
|
|
26205
|
+
const space = await reqCtx.metaspace.getLocalUserSpace({ lock: false });
|
|
26206
|
+
if (!space) {
|
|
26207
|
+
return this.error(500, "No local user space found in domain metaspace");
|
|
26208
|
+
}
|
|
26209
|
+
const service = getCustodian2();
|
|
26210
|
+
const userInfo = await service.getOAuthUserInfo(providerId, code, redirectUri);
|
|
26211
|
+
const custodianSecret = await service.deriveServerDelegateSecret(userInfo.providerKey, userNonce);
|
|
26212
|
+
const latestParentAddr = await reqCtx.metaspace.getLatestAddr({ addr: parentTjpAddr, space });
|
|
26213
|
+
if (!latestParentAddr) {
|
|
26214
|
+
return this.error(404, `Parent identity keystone not found for address: ${parentTjpAddr}`);
|
|
26215
|
+
}
|
|
26216
|
+
const resGet = await reqCtx.metaspace.get({ addr: latestParentAddr, space });
|
|
26217
|
+
const parentKeystone = resGet.ibGibs?.[0];
|
|
26218
|
+
if (!parentKeystone) {
|
|
26219
|
+
return this.error(404, `Parent identity keystone not found in space for address: ${latestParentAddr}`);
|
|
26220
|
+
}
|
|
26221
|
+
const custodianPool = parentKeystone.data?.challengePools?.find((p) => p.id === POOL_ID_CUSTODIAN_MANAGE);
|
|
26222
|
+
if (!custodianPool) {
|
|
26223
|
+
return this.error(401, `Unauthorized: Custodian pool '${POOL_ID_CUSTODIAN_MANAGE}' is not registered in the parent identity keystone.`);
|
|
26224
|
+
}
|
|
26225
|
+
const allowedVerbs = custodianPool.config.allowedVerbs || [];
|
|
26226
|
+
if (!allowedVerbs.includes(KEYSTONE_VERB_MANAGE)) {
|
|
26227
|
+
return this.error(401, `Unauthorized: Custodian pool does not have the required manage permission.`);
|
|
26228
|
+
}
|
|
26229
|
+
const strategy = KeystoneStrategyFactory.create({ config: custodianPool.config });
|
|
26230
|
+
const poolSecret = await strategy.derivePoolSecret({ masterSecret: custodianSecret });
|
|
26231
|
+
for (const [id, challenge] of Object.entries(custodianPool.challenges)) {
|
|
26232
|
+
const solution = await strategy.generateSolution({
|
|
26233
|
+
poolSecret,
|
|
26234
|
+
poolId: custodianPool.id,
|
|
26235
|
+
challengeId: id
|
|
26236
|
+
});
|
|
26237
|
+
const hashValue = await strategy.generateChallenge({ solution });
|
|
26238
|
+
if (hashValue.hash !== challenge.hash) {
|
|
26239
|
+
return this.error(401, "Unauthorized: Cryptographic verification of custodian pool challenges failed.");
|
|
26240
|
+
}
|
|
26241
|
+
}
|
|
26242
|
+
const sessionPayload = {
|
|
26243
|
+
parentTjpAddr,
|
|
26244
|
+
providerKey: userInfo.providerKey,
|
|
26245
|
+
exp: Math.floor(Date.now() / 1e3) + 24 * 3600
|
|
26246
|
+
// 24-hour expiration
|
|
26247
|
+
};
|
|
26248
|
+
const config = loadSsoServerConfig();
|
|
26249
|
+
const sessionToken = signSessionToken(sessionPayload, config.sessionSecret);
|
|
26250
|
+
const headers = {
|
|
26251
|
+
"Set-Cookie": `space_gib_session=${sessionToken}; Path=/; HttpOnly; SameSite=Strict; Max-Age=86400`
|
|
26252
|
+
};
|
|
26253
|
+
console.log(`${lc2} login successful for parent: ${parentTjpAddr}, custodian providerKey: ${userInfo.providerKey}`);
|
|
26254
|
+
return {
|
|
26255
|
+
status: 200,
|
|
26256
|
+
headers,
|
|
26257
|
+
body: {
|
|
26258
|
+
success: true,
|
|
26259
|
+
parentTjpAddr,
|
|
26260
|
+
custodianPoolId: custodianPool.id
|
|
26261
|
+
},
|
|
26262
|
+
isJson: true
|
|
26263
|
+
};
|
|
26264
|
+
} catch (error) {
|
|
26265
|
+
const emsg = extractErrorMsg(error);
|
|
26266
|
+
console.error(`${lc2} Login failed: ${emsg}`);
|
|
26267
|
+
return this.error(500, `Login failed: ${emsg}`);
|
|
26268
|
+
} finally {
|
|
26269
|
+
if (logalot61) {
|
|
26270
|
+
console.log(`${lc2} complete.`);
|
|
26271
|
+
}
|
|
26272
|
+
}
|
|
26273
|
+
}
|
|
26274
|
+
};
|
|
26275
|
+
|
|
26276
|
+
// src/server/serve-gib/handlers/api/keystone/sso-config.handler.mts
|
|
26277
|
+
var logalot62 = GLOBAL_LOG_A_LOT || true;
|
|
26278
|
+
var custodian3;
|
|
26279
|
+
function getCustodian3() {
|
|
26280
|
+
if (!custodian3) {
|
|
26281
|
+
const config = loadSsoServerConfig();
|
|
26282
|
+
custodian3 = new SsoCustodianService(config);
|
|
26283
|
+
}
|
|
26284
|
+
return custodian3;
|
|
26285
|
+
}
|
|
26286
|
+
var SsoConfigHandler = class _SsoConfigHandler extends ServeGibHandlerBase {
|
|
26287
|
+
lc = `[${_SsoConfigHandler.name}]`;
|
|
26288
|
+
method = "GET";
|
|
26289
|
+
regex = API_PATH_REGEXES.IDENTITY_SSO_CONFIG;
|
|
26290
|
+
async parseParamsImpl(reqCtx) {
|
|
26291
|
+
return void 0;
|
|
26292
|
+
}
|
|
26293
|
+
async validateQueryParams({ queryParams }) {
|
|
26294
|
+
return [];
|
|
26295
|
+
}
|
|
26296
|
+
/**
|
|
26297
|
+
* @param reqCtx Context details of the incoming request.
|
|
26298
|
+
* - Visibility: Public/Private (Contextual request information containing headers).
|
|
26299
|
+
* - Generator: Server (Constructed dynamically by serve-gib-v1 pipeline).
|
|
26300
|
+
*/
|
|
26301
|
+
async handleRouteImpl(reqCtx) {
|
|
26302
|
+
const lc2 = `${this.lc}[${this.handleRouteImpl.name}]`;
|
|
26303
|
+
try {
|
|
26304
|
+
if (logalot62) {
|
|
26305
|
+
console.log(`${lc2} starting...`);
|
|
26306
|
+
}
|
|
26307
|
+
const service = getCustodian3();
|
|
26308
|
+
const hostUrl = `${reqCtx.protocol}://${reqCtx.headers.host}`;
|
|
26309
|
+
const providers = service.getSsoClientConfigs(hostUrl);
|
|
26310
|
+
return this.ok({
|
|
26311
|
+
success: true,
|
|
26312
|
+
providers
|
|
26313
|
+
}, 200);
|
|
26314
|
+
} catch (error) {
|
|
26315
|
+
const emsg = extractErrorMsg(error);
|
|
26316
|
+
console.error(`${lc2} Fetching SSO config failed: ${emsg}`);
|
|
26317
|
+
return this.error(500, `SSO configuration retrieval failed: ${emsg}`);
|
|
26318
|
+
} finally {
|
|
26319
|
+
if (logalot62) {
|
|
26320
|
+
console.log(`${lc2} complete.`);
|
|
26321
|
+
}
|
|
26322
|
+
}
|
|
26323
|
+
}
|
|
26324
|
+
};
|
|
26325
|
+
|
|
25228
26326
|
// src/server/server.mts
|
|
25229
26327
|
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
25230
26328
|
var PORT = parseInt(process.env.PORT ?? "3000", 10);
|
|
@@ -25246,6 +26344,9 @@ async function main() {
|
|
|
25246
26344
|
new KeystoneEvolveHandler(),
|
|
25247
26345
|
new KeystonePostHandler(),
|
|
25248
26346
|
new KeystoneGetHandler(),
|
|
26347
|
+
new SsoLinkHandler(),
|
|
26348
|
+
new SsoLoginHandler(),
|
|
26349
|
+
new SsoConfigHandler(),
|
|
25249
26350
|
new StaticFileHandler(CLIENT_DIR)
|
|
25250
26351
|
],
|
|
25251
26352
|
errorHandler: new ErrorHandler()
|