@cauthyhq/node 1.1.0 → 1.2.1
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/CHANGELOG.md +37 -0
- package/README.md +79 -24
- package/dist/{cauthy-Wy-YDGrM.d.ts → cauthy-C7gZ-pbv.d.ts} +64 -2
- package/dist/{cauthy-Bn-i7cKD.d.mts → cauthy-CCSSj3Lg.d.mts} +64 -2
- package/dist/express.d.mts +2 -1
- package/dist/express.d.ts +2 -1
- package/dist/fastify.d.mts +2 -1
- package/dist/fastify.d.ts +2 -1
- package/dist/hono.d.mts +2 -1
- package/dist/hono.d.ts +2 -1
- package/dist/identity-BTJ8NB_w.d.mts +27 -0
- package/dist/identity-BTJ8NB_w.d.ts +27 -0
- package/dist/index.d.mts +21 -2
- package/dist/index.d.ts +21 -2
- package/dist/index.js +688 -67
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +683 -67
- package/dist/index.mjs.map +1 -1
- package/dist/keys.js.map +1 -1
- package/dist/keys.mjs.map +1 -1
- package/dist/mcp.js.map +1 -1
- package/dist/mcp.mjs.map +1 -1
- package/dist/next.d.mts +2 -1
- package/dist/next.d.ts +2 -1
- package/dist/testing.d.mts +36 -0
- package/dist/testing.d.ts +36 -0
- package/dist/testing.js +395 -0
- package/dist/testing.js.map +1 -1
- package/dist/testing.mjs +395 -0
- package/dist/testing.mjs.map +1 -1
- package/package.json +3 -1
package/dist/index.js
CHANGED
|
@@ -22,10 +22,12 @@ var src_exports = {};
|
|
|
22
22
|
__export(src_exports, {
|
|
23
23
|
CauthyApiError: () => CauthyApiError,
|
|
24
24
|
CauthyConfigError: () => CauthyConfigError,
|
|
25
|
+
CauthyKeyError: () => CauthyKeyError,
|
|
25
26
|
CauthyTokenError: () => CauthyTokenError,
|
|
26
27
|
CauthyUnavailableError: () => CauthyUnavailableError,
|
|
27
28
|
CauthyWebhookError: () => CauthyWebhookError,
|
|
28
29
|
DEFAULT_PASSWORD_POLICY: () => DEFAULT_PASSWORD_POLICY,
|
|
30
|
+
KEY_REPLACES: () => KEY_REPLACES,
|
|
29
31
|
MIN_PASSWORD: () => MIN_PASSWORD,
|
|
30
32
|
PROTOCOL_VERSION: () => PROTOCOL_VERSION,
|
|
31
33
|
STANDARD_VARIABLES: () => STANDARD_VARIABLES,
|
|
@@ -35,15 +37,18 @@ __export(src_exports, {
|
|
|
35
37
|
checkName: () => checkName,
|
|
36
38
|
checkNewPassword: () => checkNewPassword,
|
|
37
39
|
checkPassword: () => checkPassword,
|
|
40
|
+
composeKey: () => composeKey,
|
|
38
41
|
connectSystem: () => connectSystem,
|
|
39
42
|
createCauthy: () => createCauthy,
|
|
40
43
|
createSystem: () => createSystem,
|
|
41
44
|
createTokenVerifier: () => createTokenVerifier,
|
|
45
|
+
createVerifierFromEnv: () => createVerifierFromEnv,
|
|
42
46
|
definePolicy: () => definePolicy,
|
|
43
47
|
describePasswordPolicy: () => describePasswordPolicy,
|
|
44
48
|
explainPasswordProblems: () => explainPasswordProblems,
|
|
45
49
|
failClosed: () => failClosed,
|
|
46
50
|
memoryReplayStore: () => memoryReplayStore,
|
|
51
|
+
parseKey: () => parseKey,
|
|
47
52
|
passwordPolicyOf: () => passwordPolicyOf,
|
|
48
53
|
passwordProblems: () => passwordProblems,
|
|
49
54
|
policySource: () => policySource,
|
|
@@ -117,9 +122,12 @@ async function unseal(sealed, key) {
|
|
|
117
122
|
}
|
|
118
123
|
}
|
|
119
124
|
async function unsealAny(sealed, ring) {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
125
|
+
return (await unsealIndexed(sealed, ring))?.value ?? null;
|
|
126
|
+
}
|
|
127
|
+
async function unsealIndexed(sealed, ring) {
|
|
128
|
+
for (let index = 0; index < ring.length; index++) {
|
|
129
|
+
const value = await unseal(sealed, ring[index]);
|
|
130
|
+
if (value !== null) return { value, index };
|
|
123
131
|
}
|
|
124
132
|
return null;
|
|
125
133
|
}
|
|
@@ -131,6 +139,14 @@ var CauthyConfigError = class extends Error {
|
|
|
131
139
|
this.name = "CauthyConfigError";
|
|
132
140
|
}
|
|
133
141
|
};
|
|
142
|
+
var CauthyKeyError = class extends CauthyConfigError {
|
|
143
|
+
constructor(reason, message) {
|
|
144
|
+
super(`CAUTHY_KEY: ${message} (${reason})`);
|
|
145
|
+
this.reason = reason;
|
|
146
|
+
this.name = "CauthyKeyError";
|
|
147
|
+
}
|
|
148
|
+
reason;
|
|
149
|
+
};
|
|
134
150
|
|
|
135
151
|
// src/url.ts
|
|
136
152
|
function withoutTrailingSlashes(s) {
|
|
@@ -1041,6 +1057,7 @@ function createSystem(given = {}) {
|
|
|
1041
1057
|
};
|
|
1042
1058
|
const tokenEndpoint = `${issuer}/v1/oauth/token`;
|
|
1043
1059
|
function configured() {
|
|
1060
|
+
if (given.signer) return;
|
|
1044
1061
|
if (!/^https?:\/\//.test(issuer)) throw new CauthyConfigError("issuer (CAUTHY_ISSUER) must be Cauthy's address");
|
|
1045
1062
|
if (clientId === "") throw new CauthyConfigError("clientId (CAUTHY_CLIENT_ID) is required");
|
|
1046
1063
|
if (given.keys) {
|
|
@@ -1053,7 +1070,7 @@ function createSystem(given = {}) {
|
|
|
1053
1070
|
}
|
|
1054
1071
|
let imported;
|
|
1055
1072
|
const signingKey = () => imported ??= loadKey(key);
|
|
1056
|
-
async function
|
|
1073
|
+
async function fail2(res, what) {
|
|
1057
1074
|
const body = await res.json().catch(() => null);
|
|
1058
1075
|
const first = body?.errors?.[0];
|
|
1059
1076
|
const code = typeof body?.error === "string" ? body.error : first?.code;
|
|
@@ -1064,9 +1081,13 @@ function createSystem(given = {}) {
|
|
|
1064
1081
|
let asking;
|
|
1065
1082
|
async function ask() {
|
|
1066
1083
|
configured();
|
|
1067
|
-
const self = given.keys ? given.keysClientId : key?.clientId || clientId;
|
|
1084
|
+
const self = given.signer ? given.signer.clientId : given.keys ? given.keysClientId : key?.clientId || clientId;
|
|
1068
1085
|
const form = new URLSearchParams({ grant_type: "client_credentials", client_id: self });
|
|
1069
|
-
if (given.
|
|
1086
|
+
if (given.signer) {
|
|
1087
|
+
const s = await given.signer.signer;
|
|
1088
|
+
form.set("client_assertion_type", ASSERTION_TYPE);
|
|
1089
|
+
form.set("client_assertion", await s.assertion(self, tokenEndpoint));
|
|
1090
|
+
} else if (given.keys || key) {
|
|
1070
1091
|
const signer = given.keys ? await given.keys.current() : { kid: key.keyId, privateKey: await signingKey() };
|
|
1071
1092
|
const assertion = await new import_jose3.SignJWT({}).setProtectedHeader({ alg: "RS256", kid: signer.kid }).setIssuer(self).setSubject(self).setAudience(tokenEndpoint).setJti(crypto.randomUUID()).setIssuedAt().setExpirationTime("2m").sign(signer.privateKey);
|
|
1072
1093
|
form.set("client_assertion_type", ASSERTION_TYPE);
|
|
@@ -1075,7 +1096,7 @@ function createSystem(given = {}) {
|
|
|
1075
1096
|
form.set("client_secret", clientSecret);
|
|
1076
1097
|
}
|
|
1077
1098
|
const res = await doFetch(tokenEndpoint, { method: "POST", headers: { "content-type": "application/x-www-form-urlencoded" }, body: form });
|
|
1078
|
-
if (!res.ok) await
|
|
1099
|
+
if (!res.ok) await fail2(res, "the application's own token");
|
|
1079
1100
|
const t = await res.json();
|
|
1080
1101
|
held = { value: t.access_token, until: Date.now() + t.expires_in * 1e3 - SKEW_MS };
|
|
1081
1102
|
return t.access_token;
|
|
@@ -1097,7 +1118,7 @@ function createSystem(given = {}) {
|
|
|
1097
1118
|
held = void 0;
|
|
1098
1119
|
res = await send();
|
|
1099
1120
|
}
|
|
1100
|
-
if (!res.ok) await
|
|
1121
|
+
if (!res.ok) await fail2(res, `${method} ${path}`);
|
|
1101
1122
|
return res.status === 204 ? void 0 : res.json();
|
|
1102
1123
|
}
|
|
1103
1124
|
const data = async (method, path, body, extra) => (await call(method, path, body, extra))?.data;
|
|
@@ -1171,7 +1192,7 @@ function createSystem(given = {}) {
|
|
|
1171
1192
|
}
|
|
1172
1193
|
},
|
|
1173
1194
|
async check(q) {
|
|
1174
|
-
const system = q.system ?? ownSystem;
|
|
1195
|
+
const system = q.system ?? (ownSystem || given.currentSystem?.() || "");
|
|
1175
1196
|
if (system === "") throw new CauthyConfigError("name the system to check in (system, or CAUTHY_SYSTEM)");
|
|
1176
1197
|
return checkResultOf(await data("POST", `/v1/systems/${id(system)}/check`, { organization_id: q.organizationId, user_id: q.userId, ...checkWire(q) }));
|
|
1177
1198
|
},
|
|
@@ -1341,6 +1362,219 @@ function firstProblem(...checks) {
|
|
|
1341
1362
|
|
|
1342
1363
|
// src/webhook.ts
|
|
1343
1364
|
var import_jose4 = require("jose");
|
|
1365
|
+
|
|
1366
|
+
// src/sha256.ts
|
|
1367
|
+
var K = new Uint32Array([
|
|
1368
|
+
1116352408,
|
|
1369
|
+
1899447441,
|
|
1370
|
+
3049323471,
|
|
1371
|
+
3921009573,
|
|
1372
|
+
961987163,
|
|
1373
|
+
1508970993,
|
|
1374
|
+
2453635748,
|
|
1375
|
+
2870763221,
|
|
1376
|
+
3624381080,
|
|
1377
|
+
310598401,
|
|
1378
|
+
607225278,
|
|
1379
|
+
1426881987,
|
|
1380
|
+
1925078388,
|
|
1381
|
+
2162078206,
|
|
1382
|
+
2614888103,
|
|
1383
|
+
3248222580,
|
|
1384
|
+
3835390401,
|
|
1385
|
+
4022224774,
|
|
1386
|
+
264347078,
|
|
1387
|
+
604807628,
|
|
1388
|
+
770255983,
|
|
1389
|
+
1249150122,
|
|
1390
|
+
1555081692,
|
|
1391
|
+
1996064986,
|
|
1392
|
+
2554220882,
|
|
1393
|
+
2821834349,
|
|
1394
|
+
2952996808,
|
|
1395
|
+
3210313671,
|
|
1396
|
+
3336571891,
|
|
1397
|
+
3584528711,
|
|
1398
|
+
113926993,
|
|
1399
|
+
338241895,
|
|
1400
|
+
666307205,
|
|
1401
|
+
773529912,
|
|
1402
|
+
1294757372,
|
|
1403
|
+
1396182291,
|
|
1404
|
+
1695183700,
|
|
1405
|
+
1986661051,
|
|
1406
|
+
2177026350,
|
|
1407
|
+
2456956037,
|
|
1408
|
+
2730485921,
|
|
1409
|
+
2820302411,
|
|
1410
|
+
3259730800,
|
|
1411
|
+
3345764771,
|
|
1412
|
+
3516065817,
|
|
1413
|
+
3600352804,
|
|
1414
|
+
4094571909,
|
|
1415
|
+
275423344,
|
|
1416
|
+
430227734,
|
|
1417
|
+
506948616,
|
|
1418
|
+
659060556,
|
|
1419
|
+
883997877,
|
|
1420
|
+
958139571,
|
|
1421
|
+
1322822218,
|
|
1422
|
+
1537002063,
|
|
1423
|
+
1747873779,
|
|
1424
|
+
1955562222,
|
|
1425
|
+
2024104815,
|
|
1426
|
+
2227730452,
|
|
1427
|
+
2361852424,
|
|
1428
|
+
2428436474,
|
|
1429
|
+
2756734187,
|
|
1430
|
+
3204031479,
|
|
1431
|
+
3329325298
|
|
1432
|
+
]);
|
|
1433
|
+
function sha256(data) {
|
|
1434
|
+
const h = new Uint32Array([1779033703, 3144134277, 1013904242, 2773480762, 1359893119, 2600822924, 528734635, 1541459225]);
|
|
1435
|
+
const len = data.length;
|
|
1436
|
+
const padded = new Uint8Array(len + 9 + 63 >> 6 << 6);
|
|
1437
|
+
padded.set(data);
|
|
1438
|
+
padded[len] = 128;
|
|
1439
|
+
const view = new DataView(padded.buffer);
|
|
1440
|
+
view.setUint32(padded.length - 8, Math.floor(len / 536870912));
|
|
1441
|
+
view.setUint32(padded.length - 4, len << 3 >>> 0);
|
|
1442
|
+
const w = new Uint32Array(64);
|
|
1443
|
+
const rotr = (x, n) => x >>> n | x << 32 - n;
|
|
1444
|
+
for (let off = 0; off < padded.length; off += 64) {
|
|
1445
|
+
for (let i = 0; i < 16; i++) w[i] = view.getUint32(off + i * 4);
|
|
1446
|
+
for (let i = 16; i < 64; i++) {
|
|
1447
|
+
const s0 = rotr(w[i - 15], 7) ^ rotr(w[i - 15], 18) ^ w[i - 15] >>> 3;
|
|
1448
|
+
const s1 = rotr(w[i - 2], 17) ^ rotr(w[i - 2], 19) ^ w[i - 2] >>> 10;
|
|
1449
|
+
w[i] = w[i - 16] + s0 + w[i - 7] + s1 >>> 0;
|
|
1450
|
+
}
|
|
1451
|
+
let [a, b, c, d, e, f, g, hh] = h;
|
|
1452
|
+
for (let i = 0; i < 64; i++) {
|
|
1453
|
+
const t1 = hh + (rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25)) + (e & f ^ ~e & g) + K[i] + w[i] >>> 0;
|
|
1454
|
+
const t2 = (rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22)) + (a & b ^ a & c ^ b & c) >>> 0;
|
|
1455
|
+
hh = g;
|
|
1456
|
+
g = f;
|
|
1457
|
+
f = e;
|
|
1458
|
+
e = d + t1 >>> 0;
|
|
1459
|
+
d = c;
|
|
1460
|
+
c = b;
|
|
1461
|
+
b = a;
|
|
1462
|
+
a = t1 + t2 >>> 0;
|
|
1463
|
+
}
|
|
1464
|
+
h[0] += a;
|
|
1465
|
+
h[1] += b;
|
|
1466
|
+
h[2] += c;
|
|
1467
|
+
h[3] += d;
|
|
1468
|
+
h[4] += e;
|
|
1469
|
+
h[5] += f;
|
|
1470
|
+
h[6] += g;
|
|
1471
|
+
h[7] += hh;
|
|
1472
|
+
}
|
|
1473
|
+
const out2 = new Uint8Array(32);
|
|
1474
|
+
const ov = new DataView(out2.buffer);
|
|
1475
|
+
for (let i = 0; i < 8; i++) ov.setUint32(i * 4, h[i]);
|
|
1476
|
+
return out2;
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
// src/cauthykey.ts
|
|
1480
|
+
var KEY_PREFIX = "cauthy1";
|
|
1481
|
+
var KEY_REPLACES = [
|
|
1482
|
+
"CAUTHY_ISSUER",
|
|
1483
|
+
"CAUTHY_CLIENT_ID",
|
|
1484
|
+
"CAUTHY_CLIENT_SECRET",
|
|
1485
|
+
"CAUTHY_SYSTEM",
|
|
1486
|
+
"CAUTHY_TENANT",
|
|
1487
|
+
"CAUTHY_SYSTEM_KEY",
|
|
1488
|
+
"CAUTHY_SYSTEM_KEY_ID",
|
|
1489
|
+
"CAUTHY_BASE_URL",
|
|
1490
|
+
"CAUTHY_AUDIENCE",
|
|
1491
|
+
"CAUTHY_TENANT_ID"
|
|
1492
|
+
];
|
|
1493
|
+
function refuseBesideKey(environment) {
|
|
1494
|
+
const set = KEY_REPLACES.filter((n) => (environment?.[n] ?? "") !== "");
|
|
1495
|
+
if (set.length > 0) {
|
|
1496
|
+
throw new CauthyConfigError(`the application runs on CAUTHY_KEY, and ${set.join(", ")} ${set.length === 1 ? "is" : "are"} also set: remove ${set.length === 1 ? "it" : "them"} from the environment. Everything comes from the key.`);
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
var MAX_LEN = 512;
|
|
1500
|
+
var enc = new TextEncoder();
|
|
1501
|
+
var dec = new TextDecoder("utf-8", { fatal: true });
|
|
1502
|
+
function b64uEncode(b) {
|
|
1503
|
+
let bin = "";
|
|
1504
|
+
for (const x of b) bin += String.fromCharCode(x);
|
|
1505
|
+
return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
1506
|
+
}
|
|
1507
|
+
function b64uDecode(s) {
|
|
1508
|
+
if (!/^[A-Za-z0-9_-]*$/.test(s) || s.length % 4 === 1) return null;
|
|
1509
|
+
const bin = atob(s.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat((4 - s.length % 4) % 4));
|
|
1510
|
+
const out2 = Uint8Array.from(bin, (c) => c.charCodeAt(0));
|
|
1511
|
+
return b64uEncode(out2) === s ? out2 : null;
|
|
1512
|
+
}
|
|
1513
|
+
function checksum(body) {
|
|
1514
|
+
return b64uEncode(sha256(enc.encode(body)).slice(0, 6));
|
|
1515
|
+
}
|
|
1516
|
+
function printableAscii(s) {
|
|
1517
|
+
return /^[\x21-\x7e]*$/.test(s);
|
|
1518
|
+
}
|
|
1519
|
+
function validIssuer(s) {
|
|
1520
|
+
if (s === "" || !printableAscii(s)) return false;
|
|
1521
|
+
let u;
|
|
1522
|
+
try {
|
|
1523
|
+
u = new URL(s);
|
|
1524
|
+
} catch {
|
|
1525
|
+
return false;
|
|
1526
|
+
}
|
|
1527
|
+
if (u.origin !== s) return false;
|
|
1528
|
+
if (/^\[::ffff:/i.test(u.host)) return false;
|
|
1529
|
+
if (u.protocol === "https:") return true;
|
|
1530
|
+
return u.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(u.hostname);
|
|
1531
|
+
}
|
|
1532
|
+
var fail = (reason, message) => {
|
|
1533
|
+
throw new CauthyKeyError(reason, message);
|
|
1534
|
+
};
|
|
1535
|
+
var NOT_A_KEY = "not a Cauthy key (copy it again from the dashboard)";
|
|
1536
|
+
function parseKey(text2) {
|
|
1537
|
+
const s = text2.replace(/^[ \t\r\n]+|[ \t\r\n]+$/g, "");
|
|
1538
|
+
if (s === "" || s.length > MAX_LEN || !printableAscii(s)) fail("key_format", NOT_A_KEY);
|
|
1539
|
+
const head = s.split(".", 1)[0];
|
|
1540
|
+
if (head !== KEY_PREFIX) {
|
|
1541
|
+
if (/^cauthy[0-9]+$/.test(head)) fail("key_version", `this key is for a newer SDK (${head}): update @cauthyhq/node`);
|
|
1542
|
+
fail("key_format", NOT_A_KEY);
|
|
1543
|
+
}
|
|
1544
|
+
const parts = s.split(".");
|
|
1545
|
+
if (parts.length !== 5 || parts.some((p) => p === "")) fail("key_format", NOT_A_KEY);
|
|
1546
|
+
if (!constantTimeEqual(checksum(parts.slice(0, 4).join(".")), parts[4])) {
|
|
1547
|
+
fail("key_checksum", "the key is damaged (part of it is missing or changed): copy it again from the dashboard");
|
|
1548
|
+
}
|
|
1549
|
+
const issuerBytes = b64uDecode(parts[1]);
|
|
1550
|
+
let issuer = "";
|
|
1551
|
+
try {
|
|
1552
|
+
issuer = issuerBytes ? dec.decode(issuerBytes) : "";
|
|
1553
|
+
} catch {
|
|
1554
|
+
issuer = "";
|
|
1555
|
+
}
|
|
1556
|
+
if (!validIssuer(issuer)) fail("key_issuer", "the key names no valid Cauthy address");
|
|
1557
|
+
const clientBytes = b64uDecode(parts[2]);
|
|
1558
|
+
const clientId = clientBytes ? String.fromCharCode(...clientBytes) : "";
|
|
1559
|
+
if (!/^[A-Za-z0-9._-]{1,128}$/.test(clientId)) fail("key_client", "the key names no valid client");
|
|
1560
|
+
const secret = b64uDecode(parts[3]);
|
|
1561
|
+
if (parts[3].length !== 43 || !secret || secret.length !== 32) fail("key_secret", "the key's secret is not 32 bytes");
|
|
1562
|
+
return { issuer, clientId, secret: parts[3] };
|
|
1563
|
+
}
|
|
1564
|
+
function composeKey(issuer, clientId, secret) {
|
|
1565
|
+
const body = `${KEY_PREFIX}.${b64uEncode(enc.encode(issuer))}.${b64uEncode(enc.encode(clientId))}.${secret}`;
|
|
1566
|
+
const key = `${body}.${checksum(body)}`;
|
|
1567
|
+
parseKey(key);
|
|
1568
|
+
return key;
|
|
1569
|
+
}
|
|
1570
|
+
function constantTimeEqual(a, b) {
|
|
1571
|
+
if (a.length !== b.length) return false;
|
|
1572
|
+
let d = 0;
|
|
1573
|
+
for (let i = 0; i < a.length; i++) d |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
1574
|
+
return d === 0;
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
// src/webhook.ts
|
|
1344
1578
|
var CauthyWebhookError = class extends Error {
|
|
1345
1579
|
constructor(reason) {
|
|
1346
1580
|
super(`@cauthyhq/node: webhook refused: ${reason}`);
|
|
@@ -1375,7 +1609,8 @@ function keysOf(issuer) {
|
|
|
1375
1609
|
}
|
|
1376
1610
|
var hex = (s) => typeof s === "string" && s !== "" ? s : void 0;
|
|
1377
1611
|
async function verifyWebhook(request, options = {}) {
|
|
1378
|
-
const
|
|
1612
|
+
const key = typeof process !== "undefined" ? process.env?.CAUTHY_KEY : void 0;
|
|
1613
|
+
const env4 = key ? parseKey(key).issuer : typeof process !== "undefined" ? process.env?.CAUTHY_ISSUER : void 0;
|
|
1379
1614
|
const issuer = withoutTrailingSlashes(options.issuer ?? env4 ?? "");
|
|
1380
1615
|
if (issuer === "") throw new CauthyWebhookError("issuer (CAUTHY_ISSUER) is not set");
|
|
1381
1616
|
const signature = request.headers.get("x-cauthy-signature-jwt");
|
|
@@ -1504,7 +1739,7 @@ var LIMIT = 2n ** 53n;
|
|
|
1504
1739
|
function parseStrict(text2, integerMembers = []) {
|
|
1505
1740
|
let i = 0;
|
|
1506
1741
|
let depth = 0;
|
|
1507
|
-
const
|
|
1742
|
+
const fail2 = (what) => {
|
|
1508
1743
|
throw new SyntaxError(`${what} at ${i}`);
|
|
1509
1744
|
};
|
|
1510
1745
|
const skip = () => {
|
|
@@ -1523,19 +1758,19 @@ function parseStrict(text2, integerMembers = []) {
|
|
|
1523
1758
|
return v;
|
|
1524
1759
|
}
|
|
1525
1760
|
}
|
|
1526
|
-
return
|
|
1761
|
+
return fail2("unexpected character");
|
|
1527
1762
|
};
|
|
1528
1763
|
const string = () => {
|
|
1529
1764
|
const start = i;
|
|
1530
1765
|
i++;
|
|
1531
1766
|
while (i < text2.length && text2[i] !== '"') i += text2[i] === "\\" ? 2 : 1;
|
|
1532
|
-
if (text2[i] !== '"')
|
|
1767
|
+
if (text2[i] !== '"') fail2("unterminated string");
|
|
1533
1768
|
i++;
|
|
1534
1769
|
return JSON.parse(text2.slice(start, i));
|
|
1535
1770
|
};
|
|
1536
1771
|
const number = (integer) => {
|
|
1537
1772
|
const m = /^-?(0|[1-9]\d*)(\.\d+)?([eE][+-]?\d+)?/.exec(text2.slice(i));
|
|
1538
|
-
if (!m) return
|
|
1773
|
+
if (!m) return fail2("bad number");
|
|
1539
1774
|
i += m[0].length;
|
|
1540
1775
|
if (integer && (m[2] !== void 0 || m[3] !== void 0)) return Number.NaN;
|
|
1541
1776
|
if (m[2] === void 0 && m[3] === void 0) {
|
|
@@ -1562,7 +1797,7 @@ function parseStrict(text2, integerMembers = []) {
|
|
|
1562
1797
|
i++;
|
|
1563
1798
|
depth--;
|
|
1564
1799
|
return out2;
|
|
1565
|
-
} else
|
|
1800
|
+
} else fail2("expected , or ]");
|
|
1566
1801
|
}
|
|
1567
1802
|
};
|
|
1568
1803
|
const object = () => {
|
|
@@ -1578,12 +1813,12 @@ function parseStrict(text2, integerMembers = []) {
|
|
|
1578
1813
|
}
|
|
1579
1814
|
for (; ; ) {
|
|
1580
1815
|
skip();
|
|
1581
|
-
if (text2[i] !== '"')
|
|
1816
|
+
if (text2[i] !== '"') fail2("expected a name");
|
|
1582
1817
|
const name = string();
|
|
1583
|
-
if (seen.has(name))
|
|
1818
|
+
if (seen.has(name)) fail2(`repeated name ${JSON.stringify(name)}`);
|
|
1584
1819
|
seen.add(name);
|
|
1585
1820
|
skip();
|
|
1586
|
-
if (text2[i] !== ":")
|
|
1821
|
+
if (text2[i] !== ":") fail2("expected :");
|
|
1587
1822
|
i++;
|
|
1588
1823
|
Object.defineProperty(out2, name, { value: value(depth === 1 && integerMembers.includes(name)), enumerable: true, writable: true, configurable: true });
|
|
1589
1824
|
skip();
|
|
@@ -1592,15 +1827,175 @@ function parseStrict(text2, integerMembers = []) {
|
|
|
1592
1827
|
i++;
|
|
1593
1828
|
depth--;
|
|
1594
1829
|
return out2;
|
|
1595
|
-
} else
|
|
1830
|
+
} else fail2("expected , or }");
|
|
1596
1831
|
}
|
|
1597
1832
|
};
|
|
1598
1833
|
const result = value();
|
|
1599
1834
|
skip();
|
|
1600
|
-
if (i < text2.length)
|
|
1835
|
+
if (i < text2.length) fail2("trailing characters");
|
|
1601
1836
|
return result;
|
|
1602
1837
|
}
|
|
1603
1838
|
|
|
1839
|
+
// src/deprecate.ts
|
|
1840
|
+
var told = /* @__PURE__ */ new Set();
|
|
1841
|
+
function deprecated(what, instead) {
|
|
1842
|
+
if (told.has(what)) return;
|
|
1843
|
+
told.add(what);
|
|
1844
|
+
const message = `@cauthyhq/node: ${what} is deprecated and leaves in 2.0. ${instead}`;
|
|
1845
|
+
if (typeof process !== "undefined" && typeof process.emitWarning === "function") {
|
|
1846
|
+
process.emitWarning(message, { type: "DeprecationWarning", code: "CAUTHY_DEPRECATED" });
|
|
1847
|
+
} else {
|
|
1848
|
+
console.warn(message);
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1852
|
+
// src/keyderive.ts
|
|
1853
|
+
var enc2 = new TextEncoder();
|
|
1854
|
+
var PKCS8_ED25519 = Uint8Array.from([48, 46, 2, 1, 0, 48, 5, 6, 3, 43, 101, 112, 4, 34, 4, 32]);
|
|
1855
|
+
var ASSERTION_TYPE2 = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer";
|
|
1856
|
+
async function seedOf(secret) {
|
|
1857
|
+
const ikm = await crypto.subtle.importKey("raw", enc2.encode(secret), "HKDF", false, ["deriveBits"]);
|
|
1858
|
+
return new Uint8Array(await crypto.subtle.deriveBits(
|
|
1859
|
+
{ name: "HKDF", hash: "SHA-256", salt: new Uint8Array(0), info: enc2.encode("cauthy/sign/v1") },
|
|
1860
|
+
ikm,
|
|
1861
|
+
256
|
|
1862
|
+
));
|
|
1863
|
+
}
|
|
1864
|
+
async function thumbprint(x) {
|
|
1865
|
+
return b64uEncode(new Uint8Array(await crypto.subtle.digest("SHA-256", enc2.encode(`{"crv":"Ed25519","kty":"OKP","x":"${x}"}`))));
|
|
1866
|
+
}
|
|
1867
|
+
async function signerOf(secret) {
|
|
1868
|
+
const seed = await seedOf(secret);
|
|
1869
|
+
const der = new Uint8Array(PKCS8_ED25519.length + 32);
|
|
1870
|
+
der.set(PKCS8_ED25519);
|
|
1871
|
+
der.set(seed, PKCS8_ED25519.length);
|
|
1872
|
+
const readable = await crypto.subtle.importKey("pkcs8", der, { name: "Ed25519" }, true, ["sign"]);
|
|
1873
|
+
const { x } = await crypto.subtle.exportKey("jwk", readable);
|
|
1874
|
+
const signing = await crypto.subtle.importKey("pkcs8", der, { name: "Ed25519" }, false, ["sign"]);
|
|
1875
|
+
der.fill(0);
|
|
1876
|
+
seed.fill(0);
|
|
1877
|
+
const publicJwk = { kty: "OKP", crv: "Ed25519", x };
|
|
1878
|
+
const kid = await thumbprint(publicJwk.x);
|
|
1879
|
+
const header = b64uEncode(enc2.encode(JSON.stringify({ alg: "EdDSA", kid })));
|
|
1880
|
+
return {
|
|
1881
|
+
kid,
|
|
1882
|
+
publicJwk,
|
|
1883
|
+
async assertion(clientId, tokenEndpoint, jti = crypto.randomUUID(), iat = Math.floor(Date.now() / 1e3)) {
|
|
1884
|
+
const claims = { iss: clientId, sub: clientId, aud: tokenEndpoint, jti, iat, exp: iat + 120 };
|
|
1885
|
+
const input = `${header}.${b64uEncode(enc2.encode(JSON.stringify(claims)))}`;
|
|
1886
|
+
const sig = new Uint8Array(await crypto.subtle.sign({ name: "Ed25519" }, signing, enc2.encode(input)));
|
|
1887
|
+
return `${input}.${b64uEncode(sig)}`;
|
|
1888
|
+
}
|
|
1889
|
+
};
|
|
1890
|
+
}
|
|
1891
|
+
function deriveFromKey(key, previous) {
|
|
1892
|
+
const other = previous && previous.secret !== key.secret ? previous : void 0;
|
|
1893
|
+
const rings = deriveKeys(key.secret, other?.secret);
|
|
1894
|
+
return {
|
|
1895
|
+
cookie: rings.cookie,
|
|
1896
|
+
flow: rings.flow,
|
|
1897
|
+
signer: signerOf(key.secret),
|
|
1898
|
+
...other ? { previousJwk: signerOf(other.secret).then((s) => ({ ...s.publicJwk, kid: s.kid })) } : {}
|
|
1899
|
+
};
|
|
1900
|
+
}
|
|
1901
|
+
|
|
1902
|
+
// src/identity.ts
|
|
1903
|
+
var REVALIDATE_MS = 3e5;
|
|
1904
|
+
var sleep = (ms) => new Promise((resolve2) => {
|
|
1905
|
+
const t = setTimeout(resolve2, ms);
|
|
1906
|
+
t.unref?.();
|
|
1907
|
+
});
|
|
1908
|
+
function createIdentity(o) {
|
|
1909
|
+
const doFetch = o.fetch ?? globalThis.fetch;
|
|
1910
|
+
const now = o.now ?? Date.now;
|
|
1911
|
+
const retry = o.retryMs ?? [1e3, 2e3, 5e3, 1e4, 3e4];
|
|
1912
|
+
let held;
|
|
1913
|
+
let etag;
|
|
1914
|
+
let checkedAt = 0;
|
|
1915
|
+
let first;
|
|
1916
|
+
let refused;
|
|
1917
|
+
let refreshing;
|
|
1918
|
+
let failed = false;
|
|
1919
|
+
let attemptEnded;
|
|
1920
|
+
let attemptOut;
|
|
1921
|
+
const kid = () => (typeof o.kid === "function" ? o.kid() : o.kid) ?? "?";
|
|
1922
|
+
async function ask() {
|
|
1923
|
+
const headers = { authorization: `Bearer ${await o.token()}`, accept: "application/json" };
|
|
1924
|
+
if (etag && held) headers["if-none-match"] = etag;
|
|
1925
|
+
const res = await doFetch(`${o.issuer}/v1/connect/self`, { headers });
|
|
1926
|
+
if (res.status === 304 && held) return "same";
|
|
1927
|
+
if (res.status === 410) throw Object.assign(new Error("client_inactive"), { code: "client_inactive" });
|
|
1928
|
+
if (!res.ok) throw new CauthyApiError(503, "unavailable", `Cauthy answered ${res.status} to /v1/connect/self`);
|
|
1929
|
+
const body = await res.json();
|
|
1930
|
+
if (!body?.data || typeof body.data.client_id !== "string") throw new CauthyApiError(503, "unavailable", "Cauthy's answer to /v1/connect/self has no identity");
|
|
1931
|
+
etag = res.headers.get("etag") ?? void 0;
|
|
1932
|
+
return body.data;
|
|
1933
|
+
}
|
|
1934
|
+
function refusalOf(e) {
|
|
1935
|
+
const code = e?.code;
|
|
1936
|
+
if (code !== "invalid_client" && code !== "client_inactive") return void 0;
|
|
1937
|
+
const message = code === "client_inactive" ? `Cauthy says this application's client is no longer active (kid ${kid()}). Connect it again in Cauthy's dashboard.` : `Cauthy refuses this application's key (kid ${kid()}): it was revoked or never registered. Make a new key in Cauthy's dashboard.`;
|
|
1938
|
+
return new CauthyApiError(503, "misconfigured", message);
|
|
1939
|
+
}
|
|
1940
|
+
function refuse(err) {
|
|
1941
|
+
if (refused === void 0) o.log?.(err.message.replace(/^@cauthyhq\/node: /, ""));
|
|
1942
|
+
refused = err;
|
|
1943
|
+
checkedAt = now();
|
|
1944
|
+
return err;
|
|
1945
|
+
}
|
|
1946
|
+
async function firstAnswer() {
|
|
1947
|
+
for (let attempt = 0; ; attempt++) {
|
|
1948
|
+
attemptOut = new Promise((resolve2) => {
|
|
1949
|
+
attemptEnded = resolve2;
|
|
1950
|
+
});
|
|
1951
|
+
try {
|
|
1952
|
+
const got = await ask();
|
|
1953
|
+
if (got !== "same") held = got;
|
|
1954
|
+
checkedAt = now();
|
|
1955
|
+
failed = false;
|
|
1956
|
+
attemptEnded?.(true);
|
|
1957
|
+
return held;
|
|
1958
|
+
} catch (e) {
|
|
1959
|
+
const r = refusalOf(e);
|
|
1960
|
+
failed = true;
|
|
1961
|
+
attemptEnded?.(false);
|
|
1962
|
+
if (r) throw refuse(r);
|
|
1963
|
+
await sleep(retry[Math.min(attempt, retry.length - 1)]);
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
async function refresh() {
|
|
1968
|
+
checkedAt = now();
|
|
1969
|
+
try {
|
|
1970
|
+
const got = await ask();
|
|
1971
|
+
if (got !== "same") held = got;
|
|
1972
|
+
refused = void 0;
|
|
1973
|
+
} catch (e) {
|
|
1974
|
+
const r = refusalOf(e);
|
|
1975
|
+
if (r) refuse(r);
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
return {
|
|
1979
|
+
ready() {
|
|
1980
|
+
if ((held || refused) && now() - checkedAt >= REVALIDATE_MS) {
|
|
1981
|
+
refreshing ??= refresh().finally(() => {
|
|
1982
|
+
refreshing = void 0;
|
|
1983
|
+
});
|
|
1984
|
+
}
|
|
1985
|
+
if (refused) return Promise.reject(refused);
|
|
1986
|
+
if (held) return Promise.resolve(held);
|
|
1987
|
+
first ??= firstAnswer();
|
|
1988
|
+
return first;
|
|
1989
|
+
},
|
|
1990
|
+
current: () => held,
|
|
1991
|
+
refresh: () => refreshing ??= refresh().finally(() => {
|
|
1992
|
+
refreshing = void 0;
|
|
1993
|
+
}),
|
|
1994
|
+
failing: () => held === void 0 && failed && refused === void 0,
|
|
1995
|
+
attempt: () => held !== void 0 ? Promise.resolve(true) : attemptOut ?? Promise.resolve(false)
|
|
1996
|
+
};
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1604
1999
|
// src/verify.ts
|
|
1605
2000
|
var CauthyUnavailableError = class extends Error {
|
|
1606
2001
|
constructor(detail) {
|
|
@@ -1743,6 +2138,10 @@ function decodeJson(segment) {
|
|
|
1743
2138
|
return parseStrict(Buffer.from(padded, "base64").toString("utf8"));
|
|
1744
2139
|
}
|
|
1745
2140
|
function createTokenVerifier(options = {}) {
|
|
2141
|
+
const fromEnv = ["CAUTHY_ISSUER", "CAUTHY_AUDIENCE", "CAUTHY_TENANT_ID"].filter((name, i) => [options.issuer, options.audience, options.tenantId][i] === void 0 && (env2(name) ?? "") !== "");
|
|
2142
|
+
if (fromEnv.length > 0) {
|
|
2143
|
+
deprecated("Reading CAUTHY_ISSUER, CAUTHY_AUDIENCE and CAUTHY_TENANT_ID to verify tokens", "Set CAUTHY_KEY and use createVerifierFromEnv().");
|
|
2144
|
+
}
|
|
1746
2145
|
const issuerGiven = options.issuer ?? env2("CAUTHY_ISSUER") ?? "";
|
|
1747
2146
|
if (issuerGiven === "") throw new CauthyConfigError("issuer (CAUTHY_ISSUER) is not set");
|
|
1748
2147
|
const issuer = issuerAddress(issuerGiven, "issuer");
|
|
@@ -1838,18 +2237,18 @@ function createTokenVerifier(options = {}) {
|
|
|
1838
2237
|
async function verifyToken(from, options = {}) {
|
|
1839
2238
|
return createTokenVerifier(options)(from);
|
|
1840
2239
|
}
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
const message = `@cauthyhq/node: ${what} is deprecated and leaves in 2.0. ${instead}`;
|
|
1848
|
-
if (typeof process !== "undefined" && typeof process.emitWarning === "function") {
|
|
1849
|
-
process.emitWarning(message, { type: "DeprecationWarning", code: "CAUTHY_DEPRECATED" });
|
|
1850
|
-
} else {
|
|
1851
|
-
console.warn(message);
|
|
2240
|
+
async function createVerifierFromEnv(options = {}) {
|
|
2241
|
+
const text2 = options.key ?? env2("CAUTHY_KEY");
|
|
2242
|
+
if (text2 === void 0 || text2 === "") return createTokenVerifier(options);
|
|
2243
|
+
refuseBesideKey(typeof process !== "undefined" ? process.env : void 0);
|
|
2244
|
+
for (const name of ["issuer", "audience", "tenantId"]) {
|
|
2245
|
+
if (options[name] !== void 0) throw new CauthyConfigError(`with CAUTHY_KEY, ${name} comes from CAUTHY_KEY: do not pass it`);
|
|
1852
2246
|
}
|
|
2247
|
+
const key = parseKey(text2);
|
|
2248
|
+
const system = createSystem({ issuer: key.issuer, clientId: key.clientId, signer: { clientId: key.clientId, signer: deriveFromKey(key).signer }, fetch: options.fetch });
|
|
2249
|
+
const id = await createIdentity({ issuer: key.issuer, token: () => system.token(), fetch: deadline(options.fetch, KEYS_TIMEOUT_MS) }).ready();
|
|
2250
|
+
const { key: _, ...rest } = options;
|
|
2251
|
+
return createTokenVerifier({ ...rest, issuer: id.issuer, audience: id.audience, tenantId: id.tenant.id });
|
|
1853
2252
|
}
|
|
1854
2253
|
|
|
1855
2254
|
// src/mount.ts
|
|
@@ -1942,6 +2341,64 @@ function redirect(location, cookies = []) {
|
|
|
1942
2341
|
function conditionalOf(c) {
|
|
1943
2342
|
return { resource: String(c?.resource ?? ""), action: String(c?.action ?? ""), effect: c?.effect === "deny" ? "deny" : "allow" };
|
|
1944
2343
|
}
|
|
2344
|
+
function byKey(given) {
|
|
2345
|
+
const text2 = given.key ?? env3("CAUTHY_KEY");
|
|
2346
|
+
if (text2 === void 0 || text2 === "") return void 0;
|
|
2347
|
+
refuseBesideKey(typeof process !== "undefined" ? process.env : void 0);
|
|
2348
|
+
for (const [name, value] of [
|
|
2349
|
+
["issuer", given.issuer],
|
|
2350
|
+
["clientId", given.clientId],
|
|
2351
|
+
["clientSecret", given.clientSecret],
|
|
2352
|
+
["secret", given.secret],
|
|
2353
|
+
["system", given.system],
|
|
2354
|
+
["tenant", given.tenant],
|
|
2355
|
+
["audience", given.audience],
|
|
2356
|
+
["tenantId", given.tenantId],
|
|
2357
|
+
["systemKey", given.systemKey],
|
|
2358
|
+
["rootKey", given.rootKey]
|
|
2359
|
+
]) {
|
|
2360
|
+
if (value !== void 0) throw new CauthyConfigError(`with CAUTHY_KEY, ${name} comes from CAUTHY_KEY: do not pass it`);
|
|
2361
|
+
}
|
|
2362
|
+
const key = parseKey(text2);
|
|
2363
|
+
const prevText = given.keyPrevious ?? env3("CAUTHY_KEY_PREVIOUS");
|
|
2364
|
+
let previous;
|
|
2365
|
+
if (prevText) {
|
|
2366
|
+
try {
|
|
2367
|
+
previous = parseKey(prevText);
|
|
2368
|
+
} catch (e) {
|
|
2369
|
+
if (e instanceof CauthyKeyError) throw new CauthyKeyError(e.reason, `CAUTHY_KEY_PREVIOUS is refused: ${e.message.replace(/^@cauthyhq\/node: CAUTHY_KEY: /, "").replace(/ \(key_[a-z_]+\)$/, "")}`);
|
|
2370
|
+
throw e;
|
|
2371
|
+
}
|
|
2372
|
+
}
|
|
2373
|
+
if (previous && (previous.issuer !== key.issuer || previous.clientId !== key.clientId)) {
|
|
2374
|
+
throw new CauthyKeyError("key_previous_mismatch", "CAUTHY_KEY_PREVIOUS names another Cauthy or client than CAUTHY_KEY");
|
|
2375
|
+
}
|
|
2376
|
+
const legacySecret = env3("CAUTHY_SECRET") || void 0;
|
|
2377
|
+
const legacyRoot = env3("PACT_ROOT_KEY") || void 0;
|
|
2378
|
+
const legacyRootPrevious = legacyRoot ? env3("PACT_ROOT_KEY_PREVIOUS") || void 0 : void 0;
|
|
2379
|
+
if (legacySecret) deprecated("CAUTHY_SECRET beside CAUTHY_KEY", "It is read only for sessions made before the move: remove it after the rotation window.");
|
|
2380
|
+
if (legacyRoot) deprecated("PACT_ROOT_KEY beside CAUTHY_KEY", "It is read only for sessions made before the move: remove it, the keys option and its table after the rotation window.");
|
|
2381
|
+
if (given.keys && !(legacyRoot && given.contract)) {
|
|
2382
|
+
throw new CauthyConfigError("with CAUTHY_KEY the application keeps no key store: remove keys (it is read only beside PACT_ROOT_KEY and a contract, while moving)");
|
|
2383
|
+
}
|
|
2384
|
+
return { key, previous, legacySecret, legacyRoot, legacyRootPrevious };
|
|
2385
|
+
}
|
|
2386
|
+
var UnknownOriginError = class extends Error {
|
|
2387
|
+
constructor(origin, registered) {
|
|
2388
|
+
super(`${origin} is not one of the application's addresses in Cauthy (${registered.join(", ") || "none"}): add it on the application's card, or set baseURL`);
|
|
2389
|
+
this.origin = origin;
|
|
2390
|
+
this.registered = registered;
|
|
2391
|
+
}
|
|
2392
|
+
origin;
|
|
2393
|
+
registered;
|
|
2394
|
+
};
|
|
2395
|
+
function contractOnKey(given, keyed) {
|
|
2396
|
+
const contract = readContract(given.contract);
|
|
2397
|
+
if (contract.issuer !== keyed.key.issuer) {
|
|
2398
|
+
throw new CauthyKeyError("key_contract_issuer", `the key names ${keyed.key.issuer}, and the contract's Cauthy is ${contract.issuer}: make the key in that Cauthy's dashboard`);
|
|
2399
|
+
}
|
|
2400
|
+
return contract;
|
|
2401
|
+
}
|
|
1945
2402
|
function byContract(given) {
|
|
1946
2403
|
failClosed(typeof process !== "undefined" ? process.env : void 0);
|
|
1947
2404
|
for (const [name, value] of [
|
|
@@ -1960,27 +2417,54 @@ function byContract(given) {
|
|
|
1960
2417
|
if (contract.serviceClientId && !given.keys) {
|
|
1961
2418
|
throw new CauthyConfigError(`the link ${contract.link} gives ${contract.system} its own identity: pass keys, where its key is kept (@cauthyhq/node/keys)`);
|
|
1962
2419
|
}
|
|
2420
|
+
deprecated("PACT_ROOT_KEY", "Make the application's CAUTHY_KEY in Cauthy's dashboard (the application's card), then remove PACT_ROOT_KEY, the keys option and its table.");
|
|
1963
2421
|
return { contract, root, previous: given.rootKeyPrevious ?? env3("PACT_ROOT_KEY_PREVIOUS") };
|
|
1964
2422
|
}
|
|
1965
2423
|
var toldOfHeader = false;
|
|
2424
|
+
var toldOfUnopened = false;
|
|
2425
|
+
function toldUnopened() {
|
|
2426
|
+
if (toldOfUnopened) return;
|
|
2427
|
+
toldOfUnopened = true;
|
|
2428
|
+
console.warn("@cauthyhq/node: a session cookie arrived that no key of this application opens; it was cleared and the person is signed out. Moving to CAUTHY_KEY, keep the secret that sealed the old sessions (CAUTHY_SECRET, or PACT_ROOT_KEY and PACT_ROOT_KEY_PREVIOUS) beside it for the rotation window; rotating the key, set CAUTHY_KEY_PREVIOUS.");
|
|
2429
|
+
}
|
|
1966
2430
|
function createCauthy(given = {}) {
|
|
1967
|
-
const
|
|
1968
|
-
const
|
|
1969
|
-
const
|
|
1970
|
-
const
|
|
1971
|
-
const
|
|
1972
|
-
const
|
|
1973
|
-
const
|
|
1974
|
-
const
|
|
2431
|
+
const keyed = byKey(given);
|
|
2432
|
+
const pacted = given.contract && !keyed ? byContract(given) : void 0;
|
|
2433
|
+
const contract = keyed && given.contract ? contractOnKey(given, keyed) : pacted?.contract;
|
|
2434
|
+
const issuer = withoutTrailingSlashes(keyed?.key.issuer ?? given.issuer ?? contract?.issuer ?? env3("CAUTHY_ISSUER") ?? "");
|
|
2435
|
+
const clientId = contract?.clientId ?? keyed?.key.clientId ?? given.clientId ?? env3("CAUTHY_CLIENT_ID") ?? "";
|
|
2436
|
+
const clientSecret = contract || keyed ? void 0 : given.clientSecret ?? env3("CAUTHY_CLIENT_SECRET");
|
|
2437
|
+
const secret = contract || keyed ? "" : given.secret ?? env3("CAUTHY_SECRET") ?? "";
|
|
2438
|
+
const askSelf = keyed !== void 0 && contract === void 0;
|
|
2439
|
+
const system = askSelf ? "" : contract?.system ?? given.system ?? env3("CAUTHY_SYSTEM") ?? "";
|
|
2440
|
+
const tenant = askSelf ? "" : contract?.tenant ?? given.tenant ?? env3("CAUTHY_TENANT") ?? "";
|
|
1975
2441
|
if (!/^https?:\/\//.test(issuer)) throw new CauthyConfigError("issuer (CAUTHY_ISSUER) must be Cauthy's address, e.g. https://id.example.com");
|
|
1976
2442
|
if (clientId === "") throw new CauthyConfigError("clientId (CAUTHY_CLIENT_ID) is required");
|
|
1977
|
-
if (!contract && secret.length < 32) throw new CauthyConfigError("secret (CAUTHY_SECRET) must be at least 32 characters: it encrypts the session cookie");
|
|
2443
|
+
if (!contract && !keyed && secret.length < 32) throw new CauthyConfigError("secret (CAUTHY_SECRET) must be at least 32 characters: it encrypts the session cookie");
|
|
2444
|
+
const fromKey = keyed ? deriveFromKey(keyed.key, keyed.previous) : void 0;
|
|
2445
|
+
let signingKid;
|
|
2446
|
+
fromKey?.signer.then((s) => {
|
|
2447
|
+
signingKid = s.kid;
|
|
2448
|
+
}, () => void 0);
|
|
1978
2449
|
const derived = pacted ? deriveKeys(pacted.root, pacted.previous) : void 0;
|
|
1979
|
-
const single = derived ? void 0 : keyOf(secret).then((k) => [k]);
|
|
1980
|
-
const
|
|
1981
|
-
const
|
|
1982
|
-
const
|
|
1983
|
-
|
|
2450
|
+
const single = derived || fromKey ? void 0 : keyOf(secret).then((k) => [k]);
|
|
2451
|
+
const legacyRings = keyed?.legacyRoot ? deriveKeys(keyed.legacyRoot, keyed.legacyRootPrevious) : void 0;
|
|
2452
|
+
const legacySingle = keyed?.legacySecret ? keyOf(keyed.legacySecret) : void 0;
|
|
2453
|
+
const withLegacy = async (own, purpose) => [
|
|
2454
|
+
...await own,
|
|
2455
|
+
...legacyRings ? await legacyRings[purpose] : [],
|
|
2456
|
+
...legacySingle ? [await legacySingle] : []
|
|
2457
|
+
];
|
|
2458
|
+
const cookieKeys = fromKey ? withLegacy(fromKey.cookie, "cookie") : derived?.cookie ?? single;
|
|
2459
|
+
const flowKeys = fromKey ? withLegacy(fromKey.flow, "flow") : derived?.flow ?? single;
|
|
2460
|
+
if (!keyed && !pacted) {
|
|
2461
|
+
deprecated(
|
|
2462
|
+
"Configuring with CAUTHY_ISSUER, CAUTHY_CLIENT_ID, CAUTHY_CLIENT_SECRET, CAUTHY_SECRET and the other CAUTHY_* variables (or their options)",
|
|
2463
|
+
"Connect the application in Cauthy's dashboard and use the one variable CAUTHY_KEY."
|
|
2464
|
+
);
|
|
2465
|
+
}
|
|
2466
|
+
const ownKeys = derived && given.keys ? pactKeys(given.keys, derived.seal) : legacyRings && contract && given.keys ? pactKeys(given.keys, legacyRings.seal) : void 0;
|
|
2467
|
+
const baseURL = given.baseURL ?? (keyed ? void 0 : env3("CAUTHY_BASE_URL"));
|
|
1984
2468
|
if (baseURL !== void 0 && baseURL !== "" && !/^https?:\/\/[^/]+\/*$/.test(baseURL)) {
|
|
1985
2469
|
throw new CauthyConfigError("baseURL (CAUTHY_BASE_URL) must be the application's public origin, e.g. https://app.example.com");
|
|
1986
2470
|
}
|
|
@@ -2016,18 +2500,65 @@ function createCauthy(given = {}) {
|
|
|
2016
2500
|
acceptInviteUrl,
|
|
2017
2501
|
fetch: given.fetch,
|
|
2018
2502
|
timeoutMs: systemTimeout,
|
|
2019
|
-
...ownKeys ? { keys: ownKeys, keysClientId: contract?.serviceClientId } : {}
|
|
2503
|
+
...ownKeys ? { keys: ownKeys, keysClientId: contract?.serviceClientId } : {},
|
|
2504
|
+
// With a contract the key proves the link's service client; without, the key's own client.
|
|
2505
|
+
...fromKey ? {
|
|
2506
|
+
signer: { clientId: contract ? contract.serviceClientId ?? keyed.key.clientId : clientId, signer: fromKey.signer },
|
|
2507
|
+
currentSystem: () => identity?.current()?.system ?? ""
|
|
2508
|
+
} : {}
|
|
2020
2509
|
});
|
|
2021
|
-
const
|
|
2022
|
-
|
|
2510
|
+
const selfOptions = () => ({
|
|
2511
|
+
issuer,
|
|
2512
|
+
token: () => ownSystem.token(),
|
|
2513
|
+
fetch: deadline(given.fetch, requestTimeout),
|
|
2514
|
+
kid: () => signingKid,
|
|
2515
|
+
log: (m) => console.error(`@cauthyhq/node: ${m}`)
|
|
2516
|
+
});
|
|
2517
|
+
const identity = askSelf ? createIdentity({
|
|
2518
|
+
issuer,
|
|
2519
|
+
token: () => ownSystem.token(),
|
|
2520
|
+
fetch: deadline(given.fetch, requestTimeout),
|
|
2521
|
+
kid: () => signingKid,
|
|
2522
|
+
log: (m) => console.error(`@cauthyhq/node: ${m}`)
|
|
2523
|
+
}) : void 0;
|
|
2524
|
+
identity?.ready().catch(() => {
|
|
2525
|
+
});
|
|
2526
|
+
const tenantNow = () => identity?.current()?.tenant.slug ?? tenant;
|
|
2527
|
+
const systemNow = () => identity?.current()?.system ?? system;
|
|
2528
|
+
const requestOrigin = (request) => {
|
|
2023
2529
|
const url = new URL(request.url);
|
|
2024
2530
|
const proto = request.headers.get("x-forwarded-proto")?.split(",")[0]?.trim().toLowerCase();
|
|
2025
2531
|
return proto === "https" || proto === "http" ? `${proto}://${url.host}` : url.origin;
|
|
2026
2532
|
};
|
|
2027
|
-
const
|
|
2533
|
+
const originOf = (request) => {
|
|
2534
|
+
if (baseURL) return withoutTrailingSlashes(baseURL);
|
|
2535
|
+
const asked = requestOrigin(request);
|
|
2536
|
+
if (!identity) return asked;
|
|
2537
|
+
const urls2 = identity.current()?.app_urls ?? [];
|
|
2538
|
+
if (urls2.includes(asked)) return asked;
|
|
2539
|
+
if (urls2.length === 1) return urls2[0];
|
|
2540
|
+
throw new UnknownOriginError(asked, urls2);
|
|
2541
|
+
};
|
|
2542
|
+
const safeOrigin = (request) => {
|
|
2543
|
+
try {
|
|
2544
|
+
return originOf(request);
|
|
2545
|
+
} catch {
|
|
2546
|
+
return requestOrigin(request);
|
|
2547
|
+
}
|
|
2548
|
+
};
|
|
2549
|
+
const attrs = (request, maxAge) => ({ secure: safeOrigin(request).startsWith("https://"), maxAge });
|
|
2550
|
+
async function clientAuth(form) {
|
|
2551
|
+
if (fromKey && !contract) {
|
|
2552
|
+
const s = await fromKey.signer;
|
|
2553
|
+
form.set("client_assertion_type", ASSERTION_TYPE2);
|
|
2554
|
+
form.set("client_assertion", await s.assertion(clientId, `${issuer}/v1/oauth/token`));
|
|
2555
|
+
} else if (clientSecret) {
|
|
2556
|
+
form.set("client_secret", clientSecret);
|
|
2557
|
+
}
|
|
2558
|
+
return form;
|
|
2559
|
+
}
|
|
2028
2560
|
async function token(form, proof = {}) {
|
|
2029
|
-
const body2 = new URLSearchParams({ client_id: clientId, ...form });
|
|
2030
|
-
if (clientSecret) body2.set("client_secret", clientSecret);
|
|
2561
|
+
const body2 = await clientAuth(new URLSearchParams({ client_id: clientId, ...form }));
|
|
2031
2562
|
try {
|
|
2032
2563
|
const res = await doFetch(`${issuer}/v1/oauth/token`, {
|
|
2033
2564
|
method: "POST",
|
|
@@ -2066,8 +2597,7 @@ function createCauthy(given = {}) {
|
|
|
2066
2597
|
}
|
|
2067
2598
|
const kindOf = (body2, was) => (typeof body2?.persistent === "boolean" ? body2.persistent : was?.p === 1) ? { p: 1 } : {};
|
|
2068
2599
|
async function revoke(refreshToken) {
|
|
2069
|
-
const form = new URLSearchParams({ client_id: clientId, token: refreshToken });
|
|
2070
|
-
if (clientSecret) form.set("client_secret", clientSecret);
|
|
2600
|
+
const form = await clientAuth(new URLSearchParams({ client_id: clientId, token: refreshToken }));
|
|
2071
2601
|
await doFetch(`${issuer}/v1/oauth/revoke`, { method: "POST", headers: { "content-type": "application/x-www-form-urlencoded" }, body: form }).catch(() => void 0);
|
|
2072
2602
|
}
|
|
2073
2603
|
const unverified = (u) => contract?.requireVerifiedEmail === true && !u.emailVerified;
|
|
@@ -2088,10 +2618,11 @@ function createCauthy(given = {}) {
|
|
|
2088
2618
|
async function read(request) {
|
|
2089
2619
|
const had = readCookies(request.headers.get("cookie"));
|
|
2090
2620
|
const raw = readParts(had, cookieName);
|
|
2091
|
-
if (raw === null) return { had, value: null, present: false };
|
|
2092
|
-
const
|
|
2621
|
+
if (raw === null) return { had, value: null, present: false, stale: false };
|
|
2622
|
+
const opened = await unsealIndexed(raw, await cookieKeys);
|
|
2623
|
+
const value = opened?.value ?? null;
|
|
2093
2624
|
const sound = value !== null && typeof value.at === "string" && typeof value.rt === "string" && typeof value.exp === "number";
|
|
2094
|
-
return { had, value: sound ? value : null, present: true };
|
|
2625
|
+
return { had, value: sound ? value : null, present: true, stale: sound && opened.index > 0 };
|
|
2095
2626
|
}
|
|
2096
2627
|
async function write(request, s, had) {
|
|
2097
2628
|
return writeParts(cookieName, await seal(s, (await cookieKeys)[0]), attrs(request, s.p === 1 ? PERSISTENT_SECONDS : void 0), had);
|
|
@@ -2128,12 +2659,13 @@ function createCauthy(given = {}) {
|
|
|
2128
2659
|
return running;
|
|
2129
2660
|
}
|
|
2130
2661
|
async function resolve2(request) {
|
|
2131
|
-
const { had, value, present } = await read(request);
|
|
2662
|
+
const { had, value, present, stale } = await read(request);
|
|
2132
2663
|
if (value === null) {
|
|
2664
|
+
if (present) toldUnopened();
|
|
2133
2665
|
return { session: null, status: "unauthenticated", cookies: present ? clearParts(cookieName, attrs(request, 0), had) : [] };
|
|
2134
2666
|
}
|
|
2135
2667
|
if (unverified(value.u)) return { session: null, status: "unauthenticated", cookies: clearParts(cookieName, attrs(request, 0), had) };
|
|
2136
|
-
if (fresh(value)) return { session: toSession(value), status: "authenticated", cookies: [] };
|
|
2668
|
+
if (fresh(value)) return { session: toSession(value), status: "authenticated", cookies: stale ? await write(request, value, had) : [] };
|
|
2137
2669
|
const renewed = await renew(value);
|
|
2138
2670
|
if (renewed === "ended") {
|
|
2139
2671
|
return { session: null, status: "unauthenticated", cookies: clearParts(cookieName, attrs(request, 0), had) };
|
|
@@ -2159,7 +2691,8 @@ function createCauthy(given = {}) {
|
|
|
2159
2691
|
async function organizations(session) {
|
|
2160
2692
|
const hit = orgCache.get(session.accessToken);
|
|
2161
2693
|
if (hit && Date.now() - hit.at < ORGANIZATIONS_TTL_MS) return hit.value;
|
|
2162
|
-
const
|
|
2694
|
+
const sys = systemNow();
|
|
2695
|
+
const url = `${issuer}/v1/oauth/organizations${sys ? `?system=${encodeURIComponent(sys)}` : ""}`;
|
|
2163
2696
|
const res = await doFetch(url, { headers: { authorization: `Bearer ${session.accessToken}` } });
|
|
2164
2697
|
if (!res.ok) throw new Error(`@cauthyhq/node: reading organizations: ${res.status}`);
|
|
2165
2698
|
const body2 = await res.json();
|
|
@@ -2313,8 +2846,7 @@ function createCauthy(given = {}) {
|
|
|
2313
2846
|
async function logout(request) {
|
|
2314
2847
|
const { had, value } = await read(request);
|
|
2315
2848
|
if (value !== null) {
|
|
2316
|
-
const body2 = new URLSearchParams({ client_id: clientId, token: value.rt });
|
|
2317
|
-
if (clientSecret) body2.set("client_secret", clientSecret);
|
|
2849
|
+
const body2 = await clientAuth(new URLSearchParams({ client_id: clientId, token: value.rt }));
|
|
2318
2850
|
await doFetch(`${issuer}/v1/oauth/revoke`, {
|
|
2319
2851
|
method: "POST",
|
|
2320
2852
|
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
@@ -2438,7 +2970,7 @@ function createCauthy(given = {}) {
|
|
|
2438
2970
|
const res = await doFetch(`${issuer}${path}`, {
|
|
2439
2971
|
method,
|
|
2440
2972
|
headers: {
|
|
2441
|
-
"x-tenant-key":
|
|
2973
|
+
"x-tenant-key": tenantNow(),
|
|
2442
2974
|
accept: "application/json",
|
|
2443
2975
|
// A link Cauthy mails opens the application's page for it when it
|
|
2444
2976
|
// has one; otherwise Cauthy's own. Either way in the reader's language.
|
|
@@ -2584,7 +3116,7 @@ function createCauthy(given = {}) {
|
|
|
2584
3116
|
}
|
|
2585
3117
|
if (route === "/sign-in/anonymous" || route === "/me/upgrade") deprecated(`POST ${route}`, "Guest sessions leave the auth-bff protocol in version 2.");
|
|
2586
3118
|
if (route.startsWith("/me/upgrade/social/")) deprecated("GET /me/upgrade/social/{provider}", "Guest sessions leave the auth-bff protocol in version 2.");
|
|
2587
|
-
if (
|
|
3119
|
+
if (tenantNow() === "") {
|
|
2588
3120
|
return problem(500, "not_configured", "@cauthyhq/node: tenant (CAUTHY_TENANT) is required for the application's own sign-in screens");
|
|
2589
3121
|
}
|
|
2590
3122
|
if (route === "/look" && !post) {
|
|
@@ -2819,7 +3351,7 @@ function createCauthy(given = {}) {
|
|
|
2819
3351
|
let upstream;
|
|
2820
3352
|
try {
|
|
2821
3353
|
upstream = await doFetch(`${issuer}/v1/auth/mfa/push/events`, {
|
|
2822
|
-
headers: { "x-tenant-key":
|
|
3354
|
+
headers: { "x-tenant-key": tenantNow(), "x-mfa-token": step.token, accept: "text/event-stream" },
|
|
2823
3355
|
signal: request.signal
|
|
2824
3356
|
});
|
|
2825
3357
|
} catch {
|
|
@@ -2839,8 +3371,7 @@ function createCauthy(given = {}) {
|
|
|
2839
3371
|
if (route === "/sign-out") {
|
|
2840
3372
|
const { had, value } = await read(request);
|
|
2841
3373
|
if (value !== null) {
|
|
2842
|
-
const form = new URLSearchParams({ client_id: clientId, token: value.rt });
|
|
2843
|
-
if (clientSecret) form.set("client_secret", clientSecret);
|
|
3374
|
+
const form = await clientAuth(new URLSearchParams({ client_id: clientId, token: value.rt }));
|
|
2844
3375
|
await doFetch(`${issuer}/v1/oauth/revoke`, { method: "POST", headers: { "content-type": "application/x-www-form-urlencoded" }, body: form }).catch(() => void 0);
|
|
2845
3376
|
orgCache.delete(value.at);
|
|
2846
3377
|
}
|
|
@@ -3239,14 +3770,38 @@ function createCauthy(given = {}) {
|
|
|
3239
3770
|
const inside = url.pathname === basePath || url.pathname.startsWith(`${basePath}/`);
|
|
3240
3771
|
const route = inside ? withoutTrailingSlashes(url.pathname.slice(basePath.length)) || "/" : null;
|
|
3241
3772
|
const published3 = pact !== void 0 && url.pathname === contract?.jwksPath && (request.method === "GET" || request.method === "HEAD");
|
|
3773
|
+
if (identity && route !== null) {
|
|
3774
|
+
const unready = await identityGate();
|
|
3775
|
+
if (unready) return finish(unready, route);
|
|
3776
|
+
}
|
|
3242
3777
|
try {
|
|
3243
3778
|
return finish(await serve(request), route, published3);
|
|
3244
3779
|
} catch (err) {
|
|
3780
|
+
if (err instanceof UnknownOriginError) return finish(problem(400, "unknown_origin", err.message), route);
|
|
3245
3781
|
if (!(err instanceof CauthyApiError && err.code === "email_not_confirmed")) throw err;
|
|
3246
3782
|
const wantsPage = request.method === "GET" && (request.headers.get("sec-fetch-mode") ?? "navigate") === "navigate";
|
|
3247
3783
|
return finish(wantsPage ? new Response("Sign-in refused (email_not_confirmed). Confirm your email address first.", { status: 403, headers: { "content-type": "text/plain; charset=utf-8" } }) : problem(403, "email_not_confirmed", "Confirm your email address first."), route);
|
|
3248
3784
|
}
|
|
3249
3785
|
}
|
|
3786
|
+
async function identityGate() {
|
|
3787
|
+
if (!identity) return null;
|
|
3788
|
+
const unavailable = () => problem(503, "unavailable", "Cauthy cannot be reached yet. Try again in a moment.");
|
|
3789
|
+
if (identity.failing()) return unavailable();
|
|
3790
|
+
let timer;
|
|
3791
|
+
const waited = new Promise((resolve3) => {
|
|
3792
|
+
timer = setTimeout(() => resolve3("timeout"), requestTimeout);
|
|
3793
|
+
});
|
|
3794
|
+
try {
|
|
3795
|
+
const ready = identity.ready();
|
|
3796
|
+
const got = await Promise.race([ready.then(() => "ok"), identity.attempt().then((ok) => ok ? "ok" : "failed"), waited]);
|
|
3797
|
+
if (got === "failed") await Promise.race([ready, Promise.resolve()]);
|
|
3798
|
+
return got === "ok" ? null : unavailable();
|
|
3799
|
+
} catch (e) {
|
|
3800
|
+
return e.code === "misconfigured" ? problem(503, "misconfigured", "Cauthy refuses this application's key: make a new one in Cauthy's dashboard.") : unavailable();
|
|
3801
|
+
} finally {
|
|
3802
|
+
clearTimeout(timer);
|
|
3803
|
+
}
|
|
3804
|
+
}
|
|
3250
3805
|
async function serve(request) {
|
|
3251
3806
|
const url = new URL(request.url);
|
|
3252
3807
|
if (pact && url.pathname === contract?.jwksPath && (request.method === "GET" || request.method === "HEAD")) return pact.jwks();
|
|
@@ -3329,7 +3884,28 @@ function createCauthy(given = {}) {
|
|
|
3329
3884
|
return { response: redirect(to) };
|
|
3330
3885
|
}
|
|
3331
3886
|
const replay = given.webhookReplay ?? memoryReplayStore();
|
|
3887
|
+
let lateSelf;
|
|
3332
3888
|
const tokenVerifier = (o = {}) => {
|
|
3889
|
+
if (keyed) {
|
|
3890
|
+
if (contract && o.clientId !== void 0 && o.clientId !== contract.clientId) {
|
|
3891
|
+
throw new CauthyConfigError(`clientId is the contract's (${contract.clientId}): remove it from the call`);
|
|
3892
|
+
}
|
|
3893
|
+
let built;
|
|
3894
|
+
return async (from) => {
|
|
3895
|
+
const id = await (identity ?? (lateSelf ??= createIdentity(selfOptions()))).ready();
|
|
3896
|
+
if (built?.from !== id) {
|
|
3897
|
+
built = { from: id, verify: createTokenVerifier({
|
|
3898
|
+
issuer,
|
|
3899
|
+
tenantId: contract?.tenantId ?? id.tenant.id,
|
|
3900
|
+
audience: id.audience,
|
|
3901
|
+
fetch: given.fetch,
|
|
3902
|
+
...contract ? { clientId: contract.clientId } : {},
|
|
3903
|
+
...o
|
|
3904
|
+
}) };
|
|
3905
|
+
}
|
|
3906
|
+
return built.verify(from);
|
|
3907
|
+
};
|
|
3908
|
+
}
|
|
3333
3909
|
if (contract && o.clientId !== void 0 && o.clientId !== contract.clientId) {
|
|
3334
3910
|
throw new CauthyConfigError(`clientId is the contract's (${contract.clientId}): remove it from the call`);
|
|
3335
3911
|
}
|
|
@@ -3343,7 +3919,30 @@ function createCauthy(given = {}) {
|
|
|
3343
3919
|
});
|
|
3344
3920
|
};
|
|
3345
3921
|
const webhook = (request, o = {}) => verifyWebhook(request, { issuer, url: o.url ?? `${originOf(request)}${new URL(request.url).pathname}`, jwks: o.jwks, now: o.now, replay });
|
|
3346
|
-
|
|
3922
|
+
async function keyJwks() {
|
|
3923
|
+
const s = await fromKey.signer;
|
|
3924
|
+
const keys2 = [{ ...s.publicJwk, kid: s.kid, alg: "EdDSA", use: "sig" }];
|
|
3925
|
+
if (fromKey.previousJwk) keys2.push({ ...await fromKey.previousJwk, alg: "EdDSA", use: "sig" });
|
|
3926
|
+
if (ownKeys) keys2.push(...(await ownKeys.published()).keys);
|
|
3927
|
+
return new Response(JSON.stringify({ keys: keys2 }), {
|
|
3928
|
+
headers: { "content-type": "application/json; charset=utf-8", "cache-control": `public, max-age=${JWKS_MAX_AGE_S}` }
|
|
3929
|
+
});
|
|
3930
|
+
}
|
|
3931
|
+
const noStore = () => {
|
|
3932
|
+
throw new CauthyConfigError("on CAUTHY_KEY the application keeps no key of its own: make a new key on the application's card in Cauthy's dashboard");
|
|
3933
|
+
};
|
|
3934
|
+
const keyOnly = {
|
|
3935
|
+
current: async () => noStore(),
|
|
3936
|
+
published: async () => ({ keys: [] }),
|
|
3937
|
+
rotate: async () => noStore(),
|
|
3938
|
+
reseal: async () => 0
|
|
3939
|
+
};
|
|
3940
|
+
const pact = contract && fromKey ? {
|
|
3941
|
+
contract,
|
|
3942
|
+
keys: ownKeys ?? keyOnly,
|
|
3943
|
+
jwks: keyJwks,
|
|
3944
|
+
status: () => ownSystem.request("GET", `/v1/pact/status?system=${encodeURIComponent(contract.system)}`)
|
|
3945
|
+
} : contract && ownKeys ? {
|
|
3347
3946
|
contract,
|
|
3348
3947
|
keys: ownKeys,
|
|
3349
3948
|
async jwks() {
|
|
@@ -3353,8 +3952,25 @@ function createCauthy(given = {}) {
|
|
|
3353
3952
|
},
|
|
3354
3953
|
status: () => ownSystem.request("GET", `/v1/pact/status?system=${encodeURIComponent(contract.system)}`)
|
|
3355
3954
|
} : void 0;
|
|
3955
|
+
const configured = () => ({
|
|
3956
|
+
issuer,
|
|
3957
|
+
audience: given.audience ?? env3("CAUTHY_AUDIENCE") ?? "",
|
|
3958
|
+
tenant: { id: contract?.tenantId ?? given.tenantId ?? env3("CAUTHY_TENANT_ID") ?? "", slug: tenant },
|
|
3959
|
+
system,
|
|
3960
|
+
client_id: clientId,
|
|
3961
|
+
sign_in_client_id: clientId,
|
|
3962
|
+
app_urls: baseURL ? [withoutTrailingSlashes(baseURL)] : [],
|
|
3963
|
+
callback_path: `${basePath}/callback`,
|
|
3964
|
+
key: null,
|
|
3965
|
+
contract: contract ? { link: contract.link, system_proof: contract.systemProof, require_verified_email: contract.requireVerifiedEmail } : null
|
|
3966
|
+
});
|
|
3356
3967
|
return {
|
|
3357
3968
|
handler,
|
|
3969
|
+
ready: async () => {
|
|
3970
|
+
if (identity) return identity.ready();
|
|
3971
|
+
const id = configured();
|
|
3972
|
+
return fromKey ? { ...id, key: { kid: (await fromKey.signer).kid, retires_at: null } } : id;
|
|
3973
|
+
},
|
|
3358
3974
|
checkMount: (target) => checkMount(basePath, target),
|
|
3359
3975
|
webhook,
|
|
3360
3976
|
tokenVerifier,
|
|
@@ -3445,10 +4061,12 @@ async function connectSystem(o) {
|
|
|
3445
4061
|
0 && (module.exports = {
|
|
3446
4062
|
CauthyApiError,
|
|
3447
4063
|
CauthyConfigError,
|
|
4064
|
+
CauthyKeyError,
|
|
3448
4065
|
CauthyTokenError,
|
|
3449
4066
|
CauthyUnavailableError,
|
|
3450
4067
|
CauthyWebhookError,
|
|
3451
4068
|
DEFAULT_PASSWORD_POLICY,
|
|
4069
|
+
KEY_REPLACES,
|
|
3452
4070
|
MIN_PASSWORD,
|
|
3453
4071
|
PROTOCOL_VERSION,
|
|
3454
4072
|
STANDARD_VARIABLES,
|
|
@@ -3458,15 +4076,18 @@ async function connectSystem(o) {
|
|
|
3458
4076
|
checkName,
|
|
3459
4077
|
checkNewPassword,
|
|
3460
4078
|
checkPassword,
|
|
4079
|
+
composeKey,
|
|
3461
4080
|
connectSystem,
|
|
3462
4081
|
createCauthy,
|
|
3463
4082
|
createSystem,
|
|
3464
4083
|
createTokenVerifier,
|
|
4084
|
+
createVerifierFromEnv,
|
|
3465
4085
|
definePolicy,
|
|
3466
4086
|
describePasswordPolicy,
|
|
3467
4087
|
explainPasswordProblems,
|
|
3468
4088
|
failClosed,
|
|
3469
4089
|
memoryReplayStore,
|
|
4090
|
+
parseKey,
|
|
3470
4091
|
passwordPolicyOf,
|
|
3471
4092
|
passwordProblems,
|
|
3472
4093
|
policySource,
|