@palbase/backend 39.0.0 → 39.1.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/dist/bin/palbase-backend.cjs +70 -40
- package/dist/bin/palbase-backend.cjs.map +1 -1
- package/dist/bin/palbase-backend.js +1 -1
- package/dist/{chunk-JJSR4BUX.js → chunk-C525N4OW.js} +71 -41
- package/dist/chunk-C525N4OW.js.map +1 -0
- package/dist/engine/index.cjs +70 -40
- package/dist/engine/index.cjs.map +1 -1
- package/dist/engine/index.d.cts +1 -1
- package/dist/engine/index.d.ts +1 -1
- package/dist/engine/index.js +1 -1
- package/dist/{index-B0mjUnxy.d.cts → index-Brhp8cxj.d.cts} +9 -15
- package/dist/{index-yDno9Ju9.d.ts → index-Dlu2b9-f.d.ts} +9 -15
- package/dist/index.cjs +3 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +45 -12
- package/dist/index.d.ts +45 -12
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/docs/auth.md +20 -0
- package/docs/database.md +50 -0
- package/docs/llms-full.txt +70 -0
- package/package.json +1 -1
- package/dist/chunk-JJSR4BUX.js.map +0 -1
package/dist/engine/index.cjs
CHANGED
|
@@ -1398,6 +1398,11 @@ function isDeclarationRefused(e) {
|
|
|
1398
1398
|
__name(isDeclarationRefused, "isDeclarationRefused");
|
|
1399
1399
|
|
|
1400
1400
|
// src/engine/auth.ts
|
|
1401
|
+
var KEYSET_RETRY_INTERVAL_MS = 1e3;
|
|
1402
|
+
function authUnavailable() {
|
|
1403
|
+
return markEngineRaised(new HttpError(503, "auth_unavailable", "Authentication is temporarily unavailable"));
|
|
1404
|
+
}
|
|
1405
|
+
__name(authUnavailable, "authUnavailable");
|
|
1401
1406
|
function b64urlToBytes(s) {
|
|
1402
1407
|
const pad = s.replace(/-/g, "+").replace(/_/g, "/");
|
|
1403
1408
|
const full = pad.padEnd(Math.ceil(pad.length / 4) * 4, "=");
|
|
@@ -1414,6 +1419,8 @@ var AuthVerifier = class {
|
|
|
1414
1419
|
keys = /* @__PURE__ */ new Map();
|
|
1415
1420
|
fetchedAt = 0;
|
|
1416
1421
|
inflight = null;
|
|
1422
|
+
nextRefreshAt = 0;
|
|
1423
|
+
refreshFailed = false;
|
|
1417
1424
|
jwksUrl;
|
|
1418
1425
|
issuer;
|
|
1419
1426
|
fetchImpl;
|
|
@@ -1424,54 +1431,65 @@ var AuthVerifier = class {
|
|
|
1424
1431
|
this.fetchImpl = opts.fetchImpl ?? ((...a) => fetch(...a));
|
|
1425
1432
|
this.ttl = opts.keysetTtlMs ?? 5 * 6e4;
|
|
1426
1433
|
}
|
|
1427
|
-
/** Fetch
|
|
1428
|
-
|
|
1434
|
+
/** Fetch once concurrently. Known keys use their trust TTL; misses and
|
|
1435
|
+
* failures share a short cooldown so arbitrary kids cannot force a fetch
|
|
1436
|
+
* per request, and recovery does not wait for an entire trust TTL. */
|
|
1437
|
+
refresh() {
|
|
1429
1438
|
if (this.inflight) return this.inflight;
|
|
1430
|
-
this.inflight = (
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
}
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1439
|
+
this.inflight = this.fetchKeyset().finally(() => {
|
|
1440
|
+
this.nextRefreshAt = Date.now() + KEYSET_RETRY_INTERVAL_MS;
|
|
1441
|
+
this.inflight = null;
|
|
1442
|
+
});
|
|
1443
|
+
return this.inflight;
|
|
1444
|
+
}
|
|
1445
|
+
async fetchKeyset() {
|
|
1446
|
+
try {
|
|
1447
|
+
const res = await this.fetchImpl(this.jwksUrl);
|
|
1448
|
+
if (!res.ok) throw authUnavailable();
|
|
1449
|
+
const body = await res.json();
|
|
1450
|
+
if (!body || !Array.isArray(body.keys)) throw authUnavailable();
|
|
1451
|
+
const next = /* @__PURE__ */ new Map();
|
|
1452
|
+
for (const jwk of body.keys) {
|
|
1453
|
+
if (!jwk || jwk.kty !== "EC" || jwk.crv !== "P-256" || typeof jwk.kid !== "string" || !jwk.kid) continue;
|
|
1454
|
+
try {
|
|
1455
|
+
next.set(jwk.kid, await crypto.subtle.importKey("jwk", {
|
|
1456
|
+
kty: "EC",
|
|
1457
|
+
crv: jwk.crv,
|
|
1458
|
+
x: jwk.x,
|
|
1459
|
+
y: jwk.y,
|
|
1460
|
+
ext: true
|
|
1461
|
+
}, {
|
|
1462
|
+
name: "ECDSA",
|
|
1463
|
+
namedCurve: "P-256"
|
|
1464
|
+
}, true, [
|
|
1465
|
+
"verify"
|
|
1466
|
+
]));
|
|
1467
|
+
} catch {
|
|
1457
1468
|
}
|
|
1458
|
-
} finally {
|
|
1459
|
-
this.inflight = null;
|
|
1460
1469
|
}
|
|
1461
|
-
|
|
1462
|
-
|
|
1470
|
+
if (next.size === 0) throw authUnavailable();
|
|
1471
|
+
this.keys = next;
|
|
1472
|
+
this.fetchedAt = Date.now();
|
|
1473
|
+
this.refreshFailed = false;
|
|
1474
|
+
} catch {
|
|
1475
|
+
this.refreshFailed = true;
|
|
1476
|
+
throw authUnavailable();
|
|
1477
|
+
}
|
|
1463
1478
|
}
|
|
1464
1479
|
async key(kid) {
|
|
1465
1480
|
const stale = Date.now() - this.fetchedAt > this.ttl;
|
|
1466
|
-
if (
|
|
1481
|
+
if (this.keys.has(kid) && !stale) return this.keys.get(kid);
|
|
1482
|
+
if (this.inflight || Date.now() >= this.nextRefreshAt) await this.refresh();
|
|
1483
|
+
else if (this.refreshFailed || stale) throw authUnavailable();
|
|
1467
1484
|
return this.keys.get(kid) ?? null;
|
|
1468
1485
|
}
|
|
1469
1486
|
/**
|
|
1470
1487
|
* Verify an `Authorization` header value.
|
|
1471
1488
|
*
|
|
1472
1489
|
* @returns the verified claims, or `null` for absent / malformed / expired /
|
|
1473
|
-
* wrong-issuer / bad-signature.
|
|
1474
|
-
*
|
|
1490
|
+
* wrong-issuer / bad-signature. Credential refusals deliberately share one
|
|
1491
|
+
* result. An unavailable keyset throws 503 instead: no authentication
|
|
1492
|
+
* decision was possible, and the caller's session must not be invalidated.
|
|
1475
1493
|
*/
|
|
1476
1494
|
async verify(authorization) {
|
|
1477
1495
|
if (!authorization || !authorization.startsWith("Bearer ")) return null;
|
|
@@ -1489,7 +1507,10 @@ var AuthVerifier = class {
|
|
|
1489
1507
|
} catch {
|
|
1490
1508
|
return null;
|
|
1491
1509
|
}
|
|
1492
|
-
if (header.alg !== "ES256" || !header.kid) return null;
|
|
1510
|
+
if (!header || typeof header !== "object" || header.alg !== "ES256" || typeof header.kid !== "string" || !header.kid) return null;
|
|
1511
|
+
if (!claims || typeof claims !== "object" || Array.isArray(claims)) return null;
|
|
1512
|
+
if (typeof claims.exp === "number" && claims.exp * 1e3 <= Date.now()) return null;
|
|
1513
|
+
if (this.issuer && claims.iss !== this.issuer) return null;
|
|
1493
1514
|
const key2 = await this.key(header.kid);
|
|
1494
1515
|
if (!key2) return null;
|
|
1495
1516
|
let ok = false;
|
|
@@ -1503,7 +1524,6 @@ var AuthVerifier = class {
|
|
|
1503
1524
|
}
|
|
1504
1525
|
if (!ok) return null;
|
|
1505
1526
|
if (typeof claims.exp === "number" && claims.exp * 1e3 <= Date.now()) return null;
|
|
1506
|
-
if (this.issuer && claims.iss !== this.issuer) return null;
|
|
1507
1527
|
return claims;
|
|
1508
1528
|
}
|
|
1509
1529
|
};
|
|
@@ -6905,7 +6925,12 @@ async function createApp(opts) {
|
|
|
6905
6925
|
}
|
|
6906
6926
|
const target = matchRoute(routes, body.method ?? "POST", body.path);
|
|
6907
6927
|
const spec = effectiveAuth(target?.entry.meta.options?.auth, target?.entry.controllerAuth);
|
|
6908
|
-
|
|
6928
|
+
let callerClaims;
|
|
6929
|
+
try {
|
|
6930
|
+
callerClaims = await auth.verify(req.headers.get("authorization"));
|
|
6931
|
+
} catch (err) {
|
|
6932
|
+
return errorResponse(err, "upload authentication", requestId);
|
|
6933
|
+
}
|
|
6909
6934
|
if (spec.required && !callerClaims) {
|
|
6910
6935
|
return envelope("unauthorized", "A valid access token is required", 401, requestId);
|
|
6911
6936
|
}
|
|
@@ -6955,7 +6980,12 @@ async function createApp(opts) {
|
|
|
6955
6980
|
let attestedDevice = null;
|
|
6956
6981
|
let attestChallengeOut = null;
|
|
6957
6982
|
const spec = effectiveAuth(meta.options?.auth, hit.entry.controllerAuth);
|
|
6958
|
-
|
|
6983
|
+
let claims;
|
|
6984
|
+
try {
|
|
6985
|
+
claims = await auth.verify(req.headers.get("authorization"));
|
|
6986
|
+
} catch (err) {
|
|
6987
|
+
return errorResponse(err, "authentication", requestId);
|
|
6988
|
+
}
|
|
6959
6989
|
if (spec.required && !claims) {
|
|
6960
6990
|
return envelope("unauthorized", "A valid access token is required", 401, requestId);
|
|
6961
6991
|
}
|