tina4-nodejs 3.13.138 → 3.13.139
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/CLAUDE.md +2 -2
- package/package.json +1 -1
- package/packages/cli/dist/bin.js +529 -70
- package/packages/core/dist/index.js +534 -70
- package/packages/core/src/api.ts +33 -4
- package/packages/core/src/auth.ts +73 -6
- package/packages/core/src/authGate.ts +27 -20
- package/packages/core/src/graphql.ts +92 -0
- package/packages/core/src/index.ts +1 -0
- package/packages/core/src/job.ts +8 -1
- package/packages/core/src/mcp.ts +3 -1
- package/packages/core/src/middleware.ts +4 -2
- package/packages/core/src/push.ts +16 -0
- package/packages/core/src/queue.ts +15 -2
- package/packages/core/src/queueBackends/liteBackend.ts +15 -0
- package/packages/core/src/queueBackends/mongoBackend.ts +40 -6
- package/packages/core/src/server.ts +6 -1
- package/packages/core/src/sessionHandlers/databaseHandler.ts +60 -3
- package/packages/core/src/sso.ts +12 -4
- package/packages/core/src/ssoIdentity.ts +19 -0
- package/packages/core/src/ssrf.ts +221 -0
- package/packages/core/src/testClient.ts +3 -0
- package/packages/core/src/websocket.ts +4 -2
- package/packages/frond/dist/index.js +6 -0
- package/packages/frond/src/engine.ts +9 -0
- package/packages/orm/dist/index.js +529 -70
- package/types/core/src/api.d.ts +6 -0
- package/types/core/src/auth.d.ts +30 -0
- package/types/core/src/graphql.d.ts +25 -0
- package/types/core/src/index.d.ts +1 -0
- package/types/core/src/job.d.ts +1 -0
- package/types/core/src/push.d.ts +5 -0
- package/types/core/src/queue.d.ts +7 -1
- package/types/core/src/queueBackends/liteBackend.d.ts +9 -0
- package/types/core/src/queueBackends/mongoBackend.d.ts +8 -1
- package/types/core/src/sessionHandlers/databaseHandler.d.ts +25 -1
- package/types/core/src/sso.d.ts +1 -0
- package/types/core/src/ssoIdentity.d.ts +10 -0
- package/types/core/src/ssrf.d.ts +19 -0
|
@@ -950,6 +950,8 @@ var auth_exports = {};
|
|
|
950
950
|
__export(auth_exports, {
|
|
951
951
|
Auth: () => Auth,
|
|
952
952
|
JWT_LEEWAY_SECONDS: () => JWT_LEEWAY_SECONDS,
|
|
953
|
+
MIN_SECRET_BYTES: () => MIN_SECRET_BYTES,
|
|
954
|
+
NON_IDENTITY_TOKEN_TYPES: () => NON_IDENTITY_TOKEN_TYPES,
|
|
953
955
|
algorithmAvailable: () => algorithmAvailable,
|
|
954
956
|
authMiddleware: () => authMiddleware,
|
|
955
957
|
authenticateRequest: () => authenticateRequest,
|
|
@@ -959,7 +961,10 @@ __export(auth_exports, {
|
|
|
959
961
|
getPayload: () => getPayload,
|
|
960
962
|
getToken: () => getToken,
|
|
961
963
|
hashPassword: () => hashPassword,
|
|
964
|
+
insecureSecretMessage: () => insecureSecretMessage,
|
|
965
|
+
isIdentityPayload: () => isIdentityPayload,
|
|
962
966
|
refreshToken: () => refreshToken,
|
|
967
|
+
requireBootSecret: () => requireBootSecret,
|
|
963
968
|
resolveAlgorithm: () => resolveAlgorithm,
|
|
964
969
|
validToken: () => validToken,
|
|
965
970
|
validateApiKey: () => validateApiKey
|
|
@@ -995,6 +1000,23 @@ async function _logWarning(message) {
|
|
|
995
1000
|
function _warnBlankSecret() {
|
|
996
1001
|
void _logWarning(BLANK_SECRET_WARNING);
|
|
997
1002
|
}
|
|
1003
|
+
function isIdentityPayload(payload) {
|
|
1004
|
+
return payload != null && !NON_IDENTITY_TOKEN_TYPES.includes(payload.type);
|
|
1005
|
+
}
|
|
1006
|
+
function insecureSecretMessage(secret) {
|
|
1007
|
+
const length = Buffer.byteLength(secret ?? "", "utf8");
|
|
1008
|
+
if (length >= MIN_SECRET_BYTES) return null;
|
|
1009
|
+
const what = length === 0 ? "is not set" : `is ${length} bytes`;
|
|
1010
|
+
return `Auth: TINA4_SECRET ${what}; an HMAC JWT secret must be at least ${MIN_SECRET_BYTES} bytes. Generate one with \`openssl rand -hex 32\` and set TINA4_SECRET in your environment or .env.`;
|
|
1011
|
+
}
|
|
1012
|
+
function requireBootSecret() {
|
|
1013
|
+
const algorithm = (process.env.TINA4_JWT_ALGORITHM ?? "").trim() || "HS256";
|
|
1014
|
+
if (RSA_SIGN_ALGORITHMS.has(algorithm)) return;
|
|
1015
|
+
const secret = process.env.TINA4_SECRET ?? "";
|
|
1016
|
+
if (secret === "" && _isDev()) return;
|
|
1017
|
+
const message = insecureSecretMessage(secret);
|
|
1018
|
+
if (message) throw new Error(message);
|
|
1019
|
+
}
|
|
998
1020
|
function ensureDevSecret(cwd) {
|
|
999
1021
|
if (process.env.TINA4_SECRET) return null;
|
|
1000
1022
|
if (!_isDev() || _isCi() || _isProduction()) {
|
|
@@ -1125,6 +1147,13 @@ function validToken(token, secret, algorithm) {
|
|
|
1125
1147
|
const [h, p, sig] = parts;
|
|
1126
1148
|
const header = JSON.parse(base64urlDecode(h).toString());
|
|
1127
1149
|
if (header.alg !== resolvedAlgorithm) return null;
|
|
1150
|
+
if (HMAC_DIGESTS.has(resolvedAlgorithm)) {
|
|
1151
|
+
const weak = insecureSecretMessage(resolvedSecret);
|
|
1152
|
+
if (weak) {
|
|
1153
|
+
void _logWarning(weak);
|
|
1154
|
+
return null;
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1128
1157
|
const signingInput = `${h}.${p}`;
|
|
1129
1158
|
if (!verifySignature(signingInput, sig, resolvedSecret, resolvedAlgorithm)) {
|
|
1130
1159
|
return null;
|
|
@@ -1156,6 +1185,8 @@ function getPayload(token) {
|
|
|
1156
1185
|
function sign(input, secret, algorithm) {
|
|
1157
1186
|
const digest = HMAC_DIGESTS.get(algorithm);
|
|
1158
1187
|
if (digest) {
|
|
1188
|
+
const weak = insecureSecretMessage(secret);
|
|
1189
|
+
if (weak) throw new Error(weak);
|
|
1159
1190
|
return base64urlEncode(createHmac(digest, secret).update(input).digest());
|
|
1160
1191
|
}
|
|
1161
1192
|
const rsaAlgorithm = RSA_SIGN_ALGORITHMS.get(algorithm);
|
|
@@ -1213,17 +1244,17 @@ function authMiddleware(secret, algorithm) {
|
|
|
1213
1244
|
return;
|
|
1214
1245
|
}
|
|
1215
1246
|
const token = authHeader.slice(7);
|
|
1216
|
-
|
|
1247
|
+
const payload = validToken(token, secret, algorithm);
|
|
1248
|
+
if (!isIdentityPayload(payload)) {
|
|
1217
1249
|
res({ error: "Unauthorized" }, 401);
|
|
1218
1250
|
return;
|
|
1219
1251
|
}
|
|
1220
|
-
req2.auth =
|
|
1252
|
+
req2.auth = payload;
|
|
1221
1253
|
next();
|
|
1222
1254
|
};
|
|
1223
1255
|
}
|
|
1224
1256
|
function refreshToken(token, expiresIn = 60) {
|
|
1225
|
-
|
|
1226
|
-
const payload = getPayload(token);
|
|
1257
|
+
const payload = validToken(token);
|
|
1227
1258
|
if (!payload) return null;
|
|
1228
1259
|
const { iat: _iat, exp: _exp, ...claims } = payload;
|
|
1229
1260
|
return getToken(claims, expiresIn);
|
|
@@ -1232,7 +1263,8 @@ function authenticateRequest(headers, secret, algorithm) {
|
|
|
1232
1263
|
const authHeader = headers.authorization ?? headers.Authorization ?? "";
|
|
1233
1264
|
if (!authHeader.startsWith("Bearer ")) return null;
|
|
1234
1265
|
const token = authHeader.slice(7);
|
|
1235
|
-
|
|
1266
|
+
const payload = validToken(token, secret, algorithm);
|
|
1267
|
+
if (payload) return isIdentityPayload(payload) ? payload : null;
|
|
1236
1268
|
if (validateApiKey(token)) {
|
|
1237
1269
|
return { _auth: "api_key" };
|
|
1238
1270
|
}
|
|
@@ -1246,12 +1278,14 @@ function validateApiKey(provided, expected) {
|
|
|
1246
1278
|
if (a.length !== b.length) return false;
|
|
1247
1279
|
return timingSafeEqual(a, b);
|
|
1248
1280
|
}
|
|
1249
|
-
var BLANK_SECRET_WARNING, HMAC_DIGESTS, RSA_SIGN_ALGORITHMS, KNOWN_ALGORITHMS, ALGORITHM_REMEDY, capabilityCache, JWT_LEEWAY_SECONDS, Auth;
|
|
1281
|
+
var BLANK_SECRET_WARNING, NON_IDENTITY_TOKEN_TYPES, MIN_SECRET_BYTES, HMAC_DIGESTS, RSA_SIGN_ALGORITHMS, KNOWN_ALGORITHMS, ALGORITHM_REMEDY, capabilityCache, JWT_LEEWAY_SECONDS, Auth;
|
|
1250
1282
|
var init_auth = __esm({
|
|
1251
1283
|
"src/auth.ts"() {
|
|
1252
1284
|
"use strict";
|
|
1253
1285
|
init_dotenv();
|
|
1254
1286
|
BLANK_SECRET_WARNING = "Auth: TINA4_SECRET is not set \u2014 JWT signing is insecure. Set TINA4_SECRET to a random value (e.g. `openssl rand -hex 32`) in your environment or .env before serving traffic. For LOCAL DEV, set TINA4_DEBUG=true and a per-machine secret is generated automatically into .env.local (gitignored). Seeing this warning means the run was NOT detected as dev \u2014 typically a container or CI without TINA4_DEBUG set, or TINA4_ENV=production.";
|
|
1287
|
+
NON_IDENTITY_TOKEN_TYPES = ["form"];
|
|
1288
|
+
MIN_SECRET_BYTES = 32;
|
|
1255
1289
|
HMAC_DIGESTS = /* @__PURE__ */ new Map([
|
|
1256
1290
|
["HS256", "sha256"],
|
|
1257
1291
|
["HS384", "sha384"],
|
|
@@ -10775,11 +10809,11 @@ async function eagerLoadBelongsToRelation(instances, model, relatedClass, relati
|
|
|
10775
10809
|
const relatedPkColumn = relatedClass.getDbColumn(relatedPk);
|
|
10776
10810
|
const related = await eagerQueryRelated(relatedClass, relatedPkColumn, values);
|
|
10777
10811
|
if (nested.length > 0 && related.length > 0) await relatedClass._eagerLoad(related, nested);
|
|
10778
|
-
const
|
|
10779
|
-
for (const record of related)
|
|
10812
|
+
const lookup2 = {};
|
|
10813
|
+
for (const record of related) lookup2[_joinKey(record[relatedPk])] = record;
|
|
10780
10814
|
for (const instance of instances) {
|
|
10781
10815
|
const value = instance[fkProp];
|
|
10782
|
-
relationshipCache(instance)[relationName] = value !== void 0 && value !== null ?
|
|
10816
|
+
relationshipCache(instance)[relationName] = value !== void 0 && value !== null ? lookup2[_joinKey(value)] ?? null : null;
|
|
10783
10817
|
}
|
|
10784
10818
|
}
|
|
10785
10819
|
async function eagerLoadRelation(instances, model, relationName, nested) {
|
|
@@ -15421,9 +15455,9 @@ var init_cache = __esm({
|
|
|
15421
15455
|
return entry.value;
|
|
15422
15456
|
}
|
|
15423
15457
|
async set(key, value, ttl) {
|
|
15424
|
-
const
|
|
15458
|
+
const expiresAt2 = ttl > 0 ? Date.now() + ttl * 1e3 : 0;
|
|
15425
15459
|
this.store.delete(key);
|
|
15426
|
-
this.store.set(key, { value, expiresAt });
|
|
15460
|
+
this.store.set(key, { value, expiresAt: expiresAt2 });
|
|
15427
15461
|
while (this.store.size > this.maxEntries) {
|
|
15428
15462
|
const firstKey = this.store.keys().next().value;
|
|
15429
15463
|
if (firstKey !== void 0) this.store.delete(firstKey);
|
|
@@ -15856,8 +15890,8 @@ ${s}\r
|
|
|
15856
15890
|
const oldest = files.shift();
|
|
15857
15891
|
if (oldest) fs.unlinkSync(path.join(this.dir, oldest.name));
|
|
15858
15892
|
}
|
|
15859
|
-
const
|
|
15860
|
-
const entry = { key, value, expiresAt };
|
|
15893
|
+
const expiresAt2 = ttl > 0 ? Date.now() / 1e3 + ttl : 0;
|
|
15894
|
+
const entry = { key, value, expiresAt: expiresAt2 };
|
|
15861
15895
|
fs.writeFileSync(this.keyPath(key), JSON.stringify(entry));
|
|
15862
15896
|
} catch {
|
|
15863
15897
|
}
|
|
@@ -17604,8 +17638,8 @@ function capCache(cache, maxEntries) {
|
|
|
17604
17638
|
}
|
|
17605
17639
|
function sweepExpiredCache(cache) {
|
|
17606
17640
|
const now = Date.now();
|
|
17607
|
-
for (const [key, [,
|
|
17608
|
-
if (
|
|
17641
|
+
for (const [key, [, expiresAt2]] of cache) {
|
|
17642
|
+
if (expiresAt2 <= now) cache.delete(key);
|
|
17609
17643
|
}
|
|
17610
17644
|
}
|
|
17611
17645
|
function tokenize(source) {
|
|
@@ -18577,6 +18611,12 @@ function setFormTokenSessionId(sessionId) {
|
|
|
18577
18611
|
}
|
|
18578
18612
|
function _buildFormTokenJwt(descriptor = "") {
|
|
18579
18613
|
const secret = process.env.TINA4_SECRET ?? "";
|
|
18614
|
+
const secretBytes = Buffer.byteLength(secret, "utf8");
|
|
18615
|
+
if (secretBytes < 32) {
|
|
18616
|
+
throw new Error(
|
|
18617
|
+
`Auth: TINA4_SECRET ${secretBytes === 0 ? "is not set" : `is ${secretBytes} bytes`}; an HMAC JWT secret must be at least 32 bytes. Generate one with \`openssl rand -hex 32\` and set TINA4_SECRET in your environment or .env.`
|
|
18618
|
+
);
|
|
18619
|
+
}
|
|
18580
18620
|
const ttlMinutes = parseInt(process.env.TINA4_TOKEN_LIMIT || "60", 10);
|
|
18581
18621
|
const header = { alg: "HS256", typ: "JWT" };
|
|
18582
18622
|
const now = Math.floor(Date.now() / 1e3);
|
|
@@ -20118,8 +20158,8 @@ var init_engine = __esm({
|
|
|
20118
20158
|
sweepExpiredCache(this.fragmentCache);
|
|
20119
20159
|
const cached = this.fragmentCache.get(cacheKey);
|
|
20120
20160
|
if (cached) {
|
|
20121
|
-
const [htmlContent,
|
|
20122
|
-
if (Date.now() <
|
|
20161
|
+
const [htmlContent, expiresAt2] = cached;
|
|
20162
|
+
if (Date.now() < expiresAt2) {
|
|
20123
20163
|
let i2 = start2 + 1;
|
|
20124
20164
|
let depth = 0;
|
|
20125
20165
|
while (i2 < tokens.length) {
|
|
@@ -20912,7 +20952,7 @@ function csrfBearerIsValid(req2, secret) {
|
|
|
20912
20952
|
const authHeader = req2.headers.authorization ?? "";
|
|
20913
20953
|
if (!authHeader.startsWith("Bearer ")) return false;
|
|
20914
20954
|
const bearerToken = authHeader.slice(7).trim();
|
|
20915
|
-
return Boolean(bearerToken && validToken(bearerToken, secret));
|
|
20955
|
+
return Boolean(bearerToken && isIdentityPayload(validToken(bearerToken, secret)));
|
|
20916
20956
|
}
|
|
20917
20957
|
function csrfRequestToken(req2) {
|
|
20918
20958
|
const query = req2.query ?? {};
|
|
@@ -22049,6 +22089,22 @@ var init_transport = __esm({
|
|
|
22049
22089
|
}
|
|
22050
22090
|
});
|
|
22051
22091
|
|
|
22092
|
+
// src/ssoIdentity.ts
|
|
22093
|
+
function liveSessionIdentity(stored) {
|
|
22094
|
+
if (!stored || typeof stored !== "object") return null;
|
|
22095
|
+
const identity = stored.identity;
|
|
22096
|
+
if (!identity || typeof identity !== "object" || !identity.issuer || !identity.subject) return null;
|
|
22097
|
+
const expiresAt2 = stored.expires_at ?? 0;
|
|
22098
|
+
if (typeof expiresAt2 !== "number") return null;
|
|
22099
|
+
if (expiresAt2 > 0 && Math.floor(Date.now() / 1e3) >= expiresAt2) return null;
|
|
22100
|
+
return identity;
|
|
22101
|
+
}
|
|
22102
|
+
var init_ssoIdentity = __esm({
|
|
22103
|
+
"src/ssoIdentity.ts"() {
|
|
22104
|
+
"use strict";
|
|
22105
|
+
}
|
|
22106
|
+
});
|
|
22107
|
+
|
|
22052
22108
|
// src/authGate.ts
|
|
22053
22109
|
function enforceRouteAuth(req2, res, match, isDevAdmin) {
|
|
22054
22110
|
if (!(match.secure === true && match.noAuth !== true && !isDevAdmin)) {
|
|
@@ -22056,38 +22112,50 @@ function enforceRouteAuth(req2, res, match, isDevAdmin) {
|
|
|
22056
22112
|
}
|
|
22057
22113
|
const authHeader = req2.headers.authorization ?? "";
|
|
22058
22114
|
const headerToken = authHeader.startsWith("Bearer ") ? authHeader.slice(7) : "";
|
|
22059
|
-
let
|
|
22115
|
+
let payload = null;
|
|
22060
22116
|
let tokenSource = "";
|
|
22061
|
-
|
|
22062
|
-
|
|
22063
|
-
|
|
22117
|
+
let resolvedToken = "";
|
|
22118
|
+
if (headerToken) {
|
|
22119
|
+
const candidate = validToken(headerToken);
|
|
22120
|
+
if (isIdentityPayload(candidate)) {
|
|
22121
|
+
payload = candidate;
|
|
22122
|
+
tokenSource = "header";
|
|
22123
|
+
resolvedToken = headerToken;
|
|
22124
|
+
}
|
|
22064
22125
|
}
|
|
22065
|
-
if (!
|
|
22126
|
+
if (!payload) {
|
|
22066
22127
|
const bodyToken = req2.body?.formToken;
|
|
22067
|
-
if (bodyToken
|
|
22068
|
-
|
|
22069
|
-
|
|
22128
|
+
if (bodyToken) {
|
|
22129
|
+
const candidate = validToken(bodyToken);
|
|
22130
|
+
if (isIdentityPayload(candidate)) {
|
|
22131
|
+
payload = candidate;
|
|
22132
|
+
tokenSource = "body";
|
|
22133
|
+
resolvedToken = bodyToken;
|
|
22134
|
+
}
|
|
22070
22135
|
}
|
|
22071
22136
|
}
|
|
22072
|
-
if (!
|
|
22073
|
-
const
|
|
22074
|
-
|
|
22075
|
-
if (identity?.issuer && identity?.subject) {
|
|
22137
|
+
if (!payload) {
|
|
22138
|
+
const identity = liveSessionIdentity(req2.session?.get?.("_tina4_sso"));
|
|
22139
|
+
if (identity) {
|
|
22076
22140
|
req2.user = identity;
|
|
22077
22141
|
return rbacForbidden(match, identity, res);
|
|
22078
22142
|
}
|
|
22079
22143
|
const sessionToken = req2.session?.get?.("token");
|
|
22080
|
-
if (sessionToken
|
|
22081
|
-
|
|
22082
|
-
|
|
22144
|
+
if (sessionToken) {
|
|
22145
|
+
const candidate = validToken(sessionToken);
|
|
22146
|
+
if (isIdentityPayload(candidate)) {
|
|
22147
|
+
payload = candidate;
|
|
22148
|
+
tokenSource = "session";
|
|
22149
|
+
resolvedToken = sessionToken;
|
|
22150
|
+
}
|
|
22083
22151
|
}
|
|
22084
22152
|
}
|
|
22085
|
-
if (!
|
|
22153
|
+
if (!payload) {
|
|
22086
22154
|
res.raw.writeHead(401, { "Content-Type": "application/json" });
|
|
22087
22155
|
res.raw.end(JSON.stringify({ error: "Unauthorized" }));
|
|
22088
22156
|
return true;
|
|
22089
22157
|
}
|
|
22090
|
-
req2.user =
|
|
22158
|
+
req2.user = payload;
|
|
22091
22159
|
if (tokenSource === "body") {
|
|
22092
22160
|
const fresh = refreshToken(resolvedToken);
|
|
22093
22161
|
if (fresh) {
|
|
@@ -22137,6 +22205,7 @@ var init_authGate = __esm({
|
|
|
22137
22205
|
"src/authGate.ts"() {
|
|
22138
22206
|
"use strict";
|
|
22139
22207
|
init_auth();
|
|
22208
|
+
init_ssoIdentity();
|
|
22140
22209
|
}
|
|
22141
22210
|
});
|
|
22142
22211
|
|
|
@@ -23210,8 +23279,8 @@ var init_mongoHandler = __esm({
|
|
|
23210
23279
|
if (!result || result === "__EMPTY__") return null;
|
|
23211
23280
|
try {
|
|
23212
23281
|
const doc = JSON.parse(result);
|
|
23213
|
-
const
|
|
23214
|
-
if (
|
|
23282
|
+
const expiresAt2 = Number(doc?.expires_at ?? 0);
|
|
23283
|
+
if (expiresAt2 > 0 && Date.now() / 1e3 > expiresAt2) {
|
|
23215
23284
|
this.destroy(sessionId);
|
|
23216
23285
|
return null;
|
|
23217
23286
|
}
|
|
@@ -23437,6 +23506,9 @@ parentPort.on("message", (message) => {
|
|
|
23437
23506
|
|
|
23438
23507
|
// src/sessionHandlers/databaseHandler.ts
|
|
23439
23508
|
import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
|
|
23509
|
+
function sleepSync2(ms) {
|
|
23510
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
23511
|
+
}
|
|
23440
23512
|
function column(row, name) {
|
|
23441
23513
|
if (name in row) return row[name];
|
|
23442
23514
|
return row[name.toUpperCase()];
|
|
@@ -23445,7 +23517,7 @@ function schemeOf2(url) {
|
|
|
23445
23517
|
const match = url.match(/^([a-zA-Z][a-zA-Z0-9+.-]*):/);
|
|
23446
23518
|
return match ? match[1].toLowerCase() : "unknown";
|
|
23447
23519
|
}
|
|
23448
|
-
var CREATE_TABLE, DatabaseSessionHandler;
|
|
23520
|
+
var CREATE_TABLE, CREATE_ATTEMPTS, CREATE_RETRY_DELAY_MS, DatabaseSessionHandler;
|
|
23449
23521
|
var init_databaseHandler = __esm({
|
|
23450
23522
|
"src/sessionHandlers/databaseHandler.ts"() {
|
|
23451
23523
|
"use strict";
|
|
@@ -23525,6 +23597,8 @@ var init_databaseHandler = __esm({
|
|
|
23525
23597
|
END
|
|
23526
23598
|
`
|
|
23527
23599
|
};
|
|
23600
|
+
CREATE_ATTEMPTS = 5;
|
|
23601
|
+
CREATE_RETRY_DELAY_MS = 50;
|
|
23528
23602
|
DatabaseSessionHandler = class {
|
|
23529
23603
|
sqliteHandle = null;
|
|
23530
23604
|
/** Set for SQLite. Null when this handler talks to a networked engine. */
|
|
@@ -23565,6 +23639,7 @@ var init_databaseHandler = __esm({
|
|
|
23565
23639
|
get sqlite() {
|
|
23566
23640
|
if (this.sqliteHandle === null) {
|
|
23567
23641
|
this.sqliteHandle = new DatabaseSync3(this.dbPath);
|
|
23642
|
+
this.sqliteHandle.exec("PRAGMA busy_timeout = 5000");
|
|
23568
23643
|
this.sqliteHandle.exec("PRAGMA journal_mode = WAL");
|
|
23569
23644
|
}
|
|
23570
23645
|
return this.sqliteHandle;
|
|
@@ -23652,15 +23727,55 @@ var init_databaseHandler = __esm({
|
|
|
23652
23727
|
sqlCommandSync(this.target, sql, params);
|
|
23653
23728
|
}
|
|
23654
23729
|
/**
|
|
23655
|
-
* Ensure the session table exists (called once on first use)
|
|
23730
|
+
* Ensure the session table exists (called once on first use) - safely when
|
|
23731
|
+
* several processes race to create it.
|
|
23732
|
+
*
|
|
23733
|
+
* Every per-engine statement above is check-then-act, so two processes can
|
|
23734
|
+
* both find the table absent and both CREATE; the loser errors, and every
|
|
23735
|
+
* engine spells that differently (a pg_type unique violation on PostgreSQL,
|
|
23736
|
+
* Msg 2714 on SQL Server, an RDB$RELATIONS unique violation on Firebird). So
|
|
23737
|
+
* a failure is judged by RE-CHECKING whether the table now exists, never by
|
|
23738
|
+
* parsing the message - the rescue the Firebird note above has always
|
|
23739
|
+
* assumed, and which was not here. MEASURED on the lab before it: six real
|
|
23740
|
+
* processes racing lost one to five of them on PostgreSQL, SQLite and
|
|
23741
|
+
* Firebird.
|
|
23742
|
+
*
|
|
23743
|
+
* The re-check is RETRIED because the loser can fail BEFORE the winner has
|
|
23744
|
+
* committed. On MySQL, CREATE TABLE IF NOT EXISTS takes a shared metadata
|
|
23745
|
+
* lock, checks, then upgrades it to exclusive; two upgrades deadlock, and when
|
|
23746
|
+
* MySQL cannot back the victim off silently it answers 1213 "Deadlock found"
|
|
23747
|
+
* while the winner's table is still invisible (tina4-php CI run 35972320442).
|
|
23748
|
+
* Bounded, so a genuine failure (no CREATE grant) still throws. Same attempts
|
|
23749
|
+
* and delays in all four frameworks.
|
|
23656
23750
|
*/
|
|
23657
23751
|
ensureTable() {
|
|
23658
23752
|
if (this.initialized) return;
|
|
23659
23753
|
const ddl = CREATE_TABLE[this.engine];
|
|
23660
|
-
|
|
23661
|
-
|
|
23754
|
+
for (let attempt = 1; ; attempt++) {
|
|
23755
|
+
try {
|
|
23756
|
+
if (this.target === null) this.sqlite.exec(ddl);
|
|
23757
|
+
else sqlCommandSync(this.target, ddl, []);
|
|
23758
|
+
break;
|
|
23759
|
+
} catch (error2) {
|
|
23760
|
+
if (this.tableExists()) break;
|
|
23761
|
+
if (attempt >= CREATE_ATTEMPTS) throw error2;
|
|
23762
|
+
sleepSync2(CREATE_RETRY_DELAY_MS * attempt);
|
|
23763
|
+
}
|
|
23764
|
+
}
|
|
23662
23765
|
this.initialized = true;
|
|
23663
23766
|
}
|
|
23767
|
+
/**
|
|
23768
|
+
* Whether tina4_session exists, asked the one way every engine answers alike:
|
|
23769
|
+
* a query against it that returns no rows succeeds only if it is there.
|
|
23770
|
+
*/
|
|
23771
|
+
tableExists() {
|
|
23772
|
+
try {
|
|
23773
|
+
this.query("SELECT 1 FROM tina4_session WHERE 1 = 0", []);
|
|
23774
|
+
return true;
|
|
23775
|
+
} catch {
|
|
23776
|
+
return false;
|
|
23777
|
+
}
|
|
23778
|
+
}
|
|
23664
23779
|
read(sessionId) {
|
|
23665
23780
|
this.ensureTable();
|
|
23666
23781
|
const rows = this.query(
|
|
@@ -23670,8 +23785,8 @@ var init_databaseHandler = __esm({
|
|
|
23670
23785
|
const row = rows[0];
|
|
23671
23786
|
if (!row) return null;
|
|
23672
23787
|
const now = Date.now() / 1e3;
|
|
23673
|
-
const
|
|
23674
|
-
if (
|
|
23788
|
+
const expiresAt2 = Number(column(row, "expires_at") ?? 0);
|
|
23789
|
+
if (expiresAt2 > 0 && expiresAt2 < now) {
|
|
23675
23790
|
this.destroy(sessionId);
|
|
23676
23791
|
return null;
|
|
23677
23792
|
}
|
|
@@ -23684,7 +23799,7 @@ var init_databaseHandler = __esm({
|
|
|
23684
23799
|
write(sessionId, data, ttl) {
|
|
23685
23800
|
this.ensureTable();
|
|
23686
23801
|
const json = JSON.stringify(data);
|
|
23687
|
-
const
|
|
23802
|
+
const expiresAt2 = ttl > 0 ? Date.now() / 1e3 + ttl : 0;
|
|
23688
23803
|
const existing = this.query(
|
|
23689
23804
|
"SELECT session_id FROM tina4_session WHERE session_id = ?",
|
|
23690
23805
|
[sessionId]
|
|
@@ -23692,12 +23807,12 @@ var init_databaseHandler = __esm({
|
|
|
23692
23807
|
if (existing.length > 0) {
|
|
23693
23808
|
this.exec(
|
|
23694
23809
|
"UPDATE tina4_session SET data = ?, expires_at = ? WHERE session_id = ?",
|
|
23695
|
-
[json,
|
|
23810
|
+
[json, expiresAt2, sessionId]
|
|
23696
23811
|
);
|
|
23697
23812
|
} else {
|
|
23698
23813
|
this.exec(
|
|
23699
23814
|
"INSERT INTO tina4_session (session_id, data, expires_at) VALUES (?, ?, ?)",
|
|
23700
|
-
[sessionId, json,
|
|
23815
|
+
[sessionId, json, expiresAt2]
|
|
23701
23816
|
);
|
|
23702
23817
|
}
|
|
23703
23818
|
}
|
|
@@ -27692,7 +27807,7 @@ function envTruthy(val) {
|
|
|
27692
27807
|
return ["true", "1", "yes", "on"].includes(val.trim().toLowerCase());
|
|
27693
27808
|
}
|
|
27694
27809
|
function isLoopback(ip) {
|
|
27695
|
-
if (ip == null || ip === "") return
|
|
27810
|
+
if (ip == null || ip === "") return false;
|
|
27696
27811
|
let addr = ip.trim().toLowerCase();
|
|
27697
27812
|
if (addr.startsWith("::ffff:")) addr = addr.slice(7);
|
|
27698
27813
|
return addr === "::1" || addr === "localhost" || addr.startsWith("127.");
|
|
@@ -29283,6 +29398,7 @@ __export(graphql_exports, {
|
|
|
29283
29398
|
graphqlAutoSchemaEnabled: () => graphqlAutoSchemaEnabled,
|
|
29284
29399
|
graphqlEndpoint: () => graphqlEndpoint,
|
|
29285
29400
|
graphqlMaxDepth: () => graphqlMaxDepth,
|
|
29401
|
+
graphqlMaxNodes: () => graphqlMaxNodes,
|
|
29286
29402
|
tokenize: () => tokenize2
|
|
29287
29403
|
});
|
|
29288
29404
|
function tokenize2(source) {
|
|
@@ -29322,7 +29438,12 @@ function graphqlMaxDepth() {
|
|
|
29322
29438
|
const n = parseInt(raw, 10);
|
|
29323
29439
|
return Number.isNaN(n) ? 50 : n;
|
|
29324
29440
|
}
|
|
29325
|
-
|
|
29441
|
+
function graphqlMaxNodes() {
|
|
29442
|
+
const raw = (process.env.TINA4_GRAPHQL_MAX_NODES ?? "1000").trim();
|
|
29443
|
+
const n = parseInt(raw, 10);
|
|
29444
|
+
return Number.isNaN(n) ? 1e3 : n;
|
|
29445
|
+
}
|
|
29446
|
+
var TOKEN_PATTERNS, ParseError, Parser, ComplexityError, GraphQL, GraphQLType;
|
|
29326
29447
|
var init_graphql = __esm({
|
|
29327
29448
|
"src/graphql.ts"() {
|
|
29328
29449
|
"use strict";
|
|
@@ -29361,9 +29482,16 @@ var init_graphql = __esm({
|
|
|
29361
29482
|
Parser = class {
|
|
29362
29483
|
tokens;
|
|
29363
29484
|
pos;
|
|
29485
|
+
// F2: bound the parser's own recursion. A deeply nested query
|
|
29486
|
+
// ("{a{a{a...}}}") otherwise recurses in parseSelectionSet until the call
|
|
29487
|
+
// stack overflows — BEFORE the execution-time depth guard can run. Reusing
|
|
29488
|
+
// TINA4_GRAPHQL_MAX_DEPTH keeps parse and execution on one bound.
|
|
29489
|
+
depth = 0;
|
|
29490
|
+
maxDepth;
|
|
29364
29491
|
constructor(tokens) {
|
|
29365
29492
|
this.tokens = tokens;
|
|
29366
29493
|
this.pos = 0;
|
|
29494
|
+
this.maxDepth = graphqlMaxDepth();
|
|
29367
29495
|
}
|
|
29368
29496
|
peek() {
|
|
29369
29497
|
return this.pos < this.tokens.length ? this.tokens[this.pos] : null;
|
|
@@ -29445,6 +29573,17 @@ var init_graphql = __esm({
|
|
|
29445
29573
|
};
|
|
29446
29574
|
}
|
|
29447
29575
|
parseSelectionSet() {
|
|
29576
|
+
this.depth++;
|
|
29577
|
+
if (this.maxDepth > 0 && this.depth > this.maxDepth) {
|
|
29578
|
+
throw new Error(`Query exceeds maximum depth of ${this.maxDepth}`);
|
|
29579
|
+
}
|
|
29580
|
+
try {
|
|
29581
|
+
return this.parseSelectionSetInner();
|
|
29582
|
+
} finally {
|
|
29583
|
+
this.depth--;
|
|
29584
|
+
}
|
|
29585
|
+
}
|
|
29586
|
+
parseSelectionSetInner() {
|
|
29448
29587
|
this.expect("LBRACE");
|
|
29449
29588
|
const selections = [];
|
|
29450
29589
|
while (!this.match("RBRACE")) {
|
|
@@ -29599,6 +29738,8 @@ var init_graphql = __esm({
|
|
|
29599
29738
|
return t;
|
|
29600
29739
|
}
|
|
29601
29740
|
};
|
|
29741
|
+
ComplexityError = class extends Error {
|
|
29742
|
+
};
|
|
29602
29743
|
GraphQL = class _GraphQL {
|
|
29603
29744
|
types = /* @__PURE__ */ new Map();
|
|
29604
29745
|
queries = /* @__PURE__ */ new Map();
|
|
@@ -29621,6 +29762,12 @@ var init_graphql = __esm({
|
|
|
29621
29762
|
* spread / inline fragment, so circular fragments are caught too.
|
|
29622
29763
|
*/
|
|
29623
29764
|
maxDepth = graphqlMaxDepth();
|
|
29765
|
+
/**
|
|
29766
|
+
* Maximum expanded selection nodes per query (from `TINA4_GRAPHQL_MAX_NODES`,
|
|
29767
|
+
* default 1000; `<= 0` disables). Bounds query WIDTH — fragment bombs and
|
|
29768
|
+
* alias explosions — which the depth guard does not. See {@link graphqlMaxNodes}.
|
|
29769
|
+
*/
|
|
29770
|
+
maxNodes = graphqlMaxNodes();
|
|
29624
29771
|
/**
|
|
29625
29772
|
* Decorator-style resolver registration.
|
|
29626
29773
|
*
|
|
@@ -29768,6 +29915,16 @@ var init_graphql = __esm({
|
|
|
29768
29915
|
return { data: null, errors: [{ message: "No operation found" }] };
|
|
29769
29916
|
}
|
|
29770
29917
|
const op = operations[0];
|
|
29918
|
+
if (this.maxNodes > 0) {
|
|
29919
|
+
try {
|
|
29920
|
+
this.assertComplexity(op.selections, fragments);
|
|
29921
|
+
} catch (e) {
|
|
29922
|
+
if (e instanceof ComplexityError) {
|
|
29923
|
+
return { data: null, errors: [{ message: e.message }] };
|
|
29924
|
+
}
|
|
29925
|
+
throw e;
|
|
29926
|
+
}
|
|
29927
|
+
}
|
|
29771
29928
|
const resolvers = op.operation === "query" ? this.queries : this.mutations;
|
|
29772
29929
|
for (const vdef of op.variables) {
|
|
29773
29930
|
if (!(vdef.name in vars) && vdef.default !== void 0) {
|
|
@@ -29793,6 +29950,36 @@ var init_graphql = __esm({
|
|
|
29793
29950
|
* instead of recursing until the interpreter stack overflows. Top-level
|
|
29794
29951
|
* starts at depth 1; `maxDepth <= 0` disables the guard.
|
|
29795
29952
|
*/
|
|
29953
|
+
/**
|
|
29954
|
+
* Count the expanded selection nodes, throwing {@link ComplexityError} past
|
|
29955
|
+
* `maxNodes` (F2). Fragment spreads are expanded (so a fragment bomb is
|
|
29956
|
+
* counted, not the small unexpanded document) and every field — including
|
|
29957
|
+
* each alias — counts once. The count is capped by `maxNodes` and the walk by
|
|
29958
|
+
* `maxDepth`, so a circular fragment trips the budget instead of looping and
|
|
29959
|
+
* the check itself can never cost more than the budget.
|
|
29960
|
+
*/
|
|
29961
|
+
assertComplexity(selections, fragments) {
|
|
29962
|
+
const depthCap = this.maxDepth > 0 ? this.maxDepth : 1e3;
|
|
29963
|
+
let count = 0;
|
|
29964
|
+
const walk2 = (sels, depth) => {
|
|
29965
|
+
if (depth > depthCap) return;
|
|
29966
|
+
for (const sel of sels) {
|
|
29967
|
+
count++;
|
|
29968
|
+
if (count > this.maxNodes) {
|
|
29969
|
+
throw new ComplexityError(`Query exceeds maximum complexity of ${this.maxNodes} nodes`);
|
|
29970
|
+
}
|
|
29971
|
+
if (sel.kind === "fragment_spread") {
|
|
29972
|
+
const frag = fragments.get(sel.name);
|
|
29973
|
+
if (frag) walk2(frag.selections, depth + 1);
|
|
29974
|
+
} else if (sel.kind === "inline_fragment") {
|
|
29975
|
+
walk2(sel.selections, depth + 1);
|
|
29976
|
+
} else if (sel.kind === "field" && sel.selections) {
|
|
29977
|
+
walk2(sel.selections, depth + 1);
|
|
29978
|
+
}
|
|
29979
|
+
}
|
|
29980
|
+
};
|
|
29981
|
+
walk2(selections, 1);
|
|
29982
|
+
}
|
|
29796
29983
|
async resolveSelectionsInto(selections, resolvers, parent, variables, context, fragments, target, depth) {
|
|
29797
29984
|
const errors = [];
|
|
29798
29985
|
if (this.maxDepth > 0 && depth > this.maxDepth) {
|
|
@@ -30995,7 +31182,8 @@ function wsAuthorized(route, headers, queryString = "", subprotocol = "") {
|
|
|
30995
31182
|
const token = wsToken(headers, queryString, subprotocol);
|
|
30996
31183
|
if (!token) return [null, false];
|
|
30997
31184
|
const payload = validToken(token);
|
|
30998
|
-
return [
|
|
31185
|
+
if (!isIdentityPayload(payload)) return [null, false];
|
|
31186
|
+
return [payload, true];
|
|
30999
31187
|
}
|
|
31000
31188
|
function offeredBearerSubprotocol(subprotocol) {
|
|
31001
31189
|
return subprotocol.split(",").some((p) => p.trim().toLowerCase() === "bearer");
|
|
@@ -32190,12 +32378,36 @@ var init_mongoBackend = __esm({
|
|
|
32190
32378
|
}
|
|
32191
32379
|
}
|
|
32192
32380
|
else if (operation === "size") {
|
|
32193
|
-
|
|
32194
|
-
|
|
32195
|
-
|
|
32196
|
-
|
|
32381
|
+
// ADR-0022 dec 7: size(status) must not answer a different question.
|
|
32382
|
+
// The dead aliases count the .dead_letter store (== deadLetters().length);
|
|
32383
|
+
// reserved/completed count by status; else pending. data = status.
|
|
32384
|
+
const status = data || "pending";
|
|
32385
|
+
let count: number;
|
|
32386
|
+
if (status === "dead" || status === "failed" || status === "dead_letter") {
|
|
32387
|
+
count = await col.countDocuments({ queue: queueName + ".dead_letter" });
|
|
32388
|
+
} else if (status === "reserved" || status === "completed") {
|
|
32389
|
+
count = await col.countDocuments({ queue: queueName, status });
|
|
32390
|
+
} else {
|
|
32391
|
+
count = await col.countDocuments({ queue: queueName, status: "pending" });
|
|
32392
|
+
}
|
|
32197
32393
|
process.stdout.write(String(count));
|
|
32198
32394
|
}
|
|
32395
|
+
else if (operation === "reject") {
|
|
32396
|
+
// Dead-letter NOW, no retry (ADR-0023). Floor attempts at maxRetries
|
|
32397
|
+
// so it reads as exhausted (parity with fail()'s dead-letter branch
|
|
32398
|
+
// and size("dead")). data = JSON { id, reason, maxRetries }.
|
|
32399
|
+
const info = JSON.parse(data);
|
|
32400
|
+
const doc = await col.findOne({ queue: queueName, id: info.id });
|
|
32401
|
+
if (doc) {
|
|
32402
|
+
const attempts = Math.max((doc.attempts || 0) + 1, info.maxRetries);
|
|
32403
|
+
await col.insertOne({
|
|
32404
|
+
...doc, _id: undefined, attempts,
|
|
32405
|
+
status: "dead", queue: queueName + ".dead_letter", error: info.reason,
|
|
32406
|
+
});
|
|
32407
|
+
await col.deleteOne({ _id: doc._id, queue: queueName });
|
|
32408
|
+
}
|
|
32409
|
+
process.stdout.write("__OK__");
|
|
32410
|
+
}
|
|
32199
32411
|
else if (operation === "clear") {
|
|
32200
32412
|
await col.deleteMany({ queue: queueName });
|
|
32201
32413
|
process.stdout.write("__CLEARED__");
|
|
@@ -32431,11 +32643,20 @@ var init_mongoBackend = __esm({
|
|
|
32431
32643
|
return null;
|
|
32432
32644
|
}
|
|
32433
32645
|
}
|
|
32434
|
-
size(queue) {
|
|
32435
|
-
const result = this.execSync("size", queue);
|
|
32646
|
+
size(queue, status2 = "pending") {
|
|
32647
|
+
const result = this.execSync("size", queue, status2);
|
|
32436
32648
|
const num = parseInt(result, 10);
|
|
32437
32649
|
return isNaN(num) ? 0 : num;
|
|
32438
32650
|
}
|
|
32651
|
+
/**
|
|
32652
|
+
* Dead-letter a job immediately — no retry (ADR-0023 reject()). Distinct from
|
|
32653
|
+
* fail(): fail() requeues until maxRetries is spent. attempts is floored at
|
|
32654
|
+
* maxRetries so deadLetters()/size("dead") (which filter/scope to the
|
|
32655
|
+
* dead-letter store) see it.
|
|
32656
|
+
*/
|
|
32657
|
+
reject(queue, id, reason, maxRetries) {
|
|
32658
|
+
this.execSync("reject", queue, JSON.stringify({ id, reason, maxRetries }));
|
|
32659
|
+
}
|
|
32439
32660
|
clear(queue) {
|
|
32440
32661
|
this.execSync("clear", queue);
|
|
32441
32662
|
}
|
|
@@ -32530,7 +32751,9 @@ function createJob(data, queue) {
|
|
|
32530
32751
|
queue._failJob(job.topic, job, reason, queue.getMaxRetries());
|
|
32531
32752
|
},
|
|
32532
32753
|
reject(reason = "") {
|
|
32533
|
-
job.
|
|
32754
|
+
job.status = "dead";
|
|
32755
|
+
job.error = reason;
|
|
32756
|
+
queue._rejectJob(job.topic, job, reason, queue.getMaxRetries());
|
|
32534
32757
|
},
|
|
32535
32758
|
retry(delaySeconds) {
|
|
32536
32759
|
queue._retryJob(job.topic, job, delaySeconds);
|
|
@@ -33139,6 +33362,20 @@ var init_liteBackend = __esm({
|
|
|
33139
33362
|
* dead-letter file -- two spellings of the same intent that previously
|
|
33140
33363
|
* diverged.
|
|
33141
33364
|
*/
|
|
33365
|
+
/**
|
|
33366
|
+
* Reject a job permanently — dead-letter it NOW, no retry (ADR-0023).
|
|
33367
|
+
*
|
|
33368
|
+
* Unlike failJob(), which requeues until maxRetries is spent, rejectJob()
|
|
33369
|
+
* moves a known-poison job straight to the dead-letter store. attempts is
|
|
33370
|
+
* floored at maxRetries so deadLetters() (attempts >= maxRetries) returns it
|
|
33371
|
+
* and size("dead") agrees.
|
|
33372
|
+
*/
|
|
33373
|
+
rejectJob(queue, job, reason, maxRetries) {
|
|
33374
|
+
this.clearReservation(queue, job.id);
|
|
33375
|
+
job.attempts = Math.max((job.attempts || 0) + 1, maxRetries);
|
|
33376
|
+
job.error = reason;
|
|
33377
|
+
this.deadLetter(queue, job, reason);
|
|
33378
|
+
}
|
|
33142
33379
|
retryJob(queue, job, delaySeconds) {
|
|
33143
33380
|
this.clearReservation(queue, job.id);
|
|
33144
33381
|
try {
|
|
@@ -33360,7 +33597,7 @@ var init_queue = __esm({
|
|
|
33360
33597
|
size(status2 = "pending") {
|
|
33361
33598
|
const q = this.topic;
|
|
33362
33599
|
if (this.externalBackend) {
|
|
33363
|
-
return this.externalBackend.size(q);
|
|
33600
|
+
return this.externalBackend.size(q, status2);
|
|
33364
33601
|
}
|
|
33365
33602
|
return this.liteBackend.size(q, status2);
|
|
33366
33603
|
}
|
|
@@ -33628,6 +33865,17 @@ var init_queue = __esm({
|
|
|
33628
33865
|
}
|
|
33629
33866
|
this.liteBackend.failJob(queue, job, error2, maxRetries, this._retryBackoff);
|
|
33630
33867
|
}
|
|
33868
|
+
/**
|
|
33869
|
+
* Dead-letter a job immediately — no retry (ADR-0023 reject()). Distinct from
|
|
33870
|
+
* _failJob, which retries until maxRetries is spent.
|
|
33871
|
+
*/
|
|
33872
|
+
_rejectJob(queue, job, reason, maxRetries) {
|
|
33873
|
+
if (this.externalBackend?.reject) {
|
|
33874
|
+
this.externalBackend.reject(queue, job.id, reason, maxRetries);
|
|
33875
|
+
return;
|
|
33876
|
+
}
|
|
33877
|
+
this.liteBackend.rejectJob(queue, job, reason, maxRetries);
|
|
33878
|
+
}
|
|
33631
33879
|
/**
|
|
33632
33880
|
* Re-queue a job back to the main queue directory with incremented attempts.
|
|
33633
33881
|
*/
|
|
@@ -39339,13 +39587,20 @@ var sso_exports = {};
|
|
|
39339
39587
|
__export(sso_exports, {
|
|
39340
39588
|
SSO: () => Sso,
|
|
39341
39589
|
Sso: () => Sso,
|
|
39342
|
-
SsoError: () => SsoError
|
|
39590
|
+
SsoError: () => SsoError,
|
|
39591
|
+
liveSessionIdentity: () => liveSessionIdentity
|
|
39343
39592
|
});
|
|
39344
39593
|
import { createHash as createHash9, randomBytes as randomBytes7, timingSafeEqual as timingSafeEqual3 } from "node:crypto";
|
|
39594
|
+
function expiresAt(tokens) {
|
|
39595
|
+
const lifetime = Number(tokens.expires_in ?? 0);
|
|
39596
|
+
return lifetime > 0 ? Math.floor(Date.now() / 1e3) + lifetime : 0;
|
|
39597
|
+
}
|
|
39345
39598
|
var SsoError, Sso;
|
|
39346
39599
|
var init_sso = __esm({
|
|
39347
39600
|
"src/sso.ts"() {
|
|
39348
39601
|
"use strict";
|
|
39602
|
+
init_ssoIdentity();
|
|
39603
|
+
init_ssoIdentity();
|
|
39349
39604
|
SsoError = class extends Error {
|
|
39350
39605
|
};
|
|
39351
39606
|
Sso = class _Sso {
|
|
@@ -39551,13 +39806,12 @@ var init_sso = __esm({
|
|
|
39551
39806
|
access_token: tokens.access_token,
|
|
39552
39807
|
refresh_token: tokens.refresh_token,
|
|
39553
39808
|
id_token: tokens.id_token,
|
|
39554
|
-
expires_at:
|
|
39809
|
+
expires_at: expiresAt(tokens)
|
|
39555
39810
|
});
|
|
39556
39811
|
return { identity, return_to: _Sso.safeReturn(pending.return_to) };
|
|
39557
39812
|
}
|
|
39558
39813
|
identity(requestOrSession) {
|
|
39559
|
-
const
|
|
39560
|
-
const identity = stored?.identity ?? null;
|
|
39814
|
+
const identity = liveSessionIdentity(this.session(requestOrSession)?.get(_Sso.SESSION_KEY));
|
|
39561
39815
|
if (identity && requestOrSession?.session) requestOrSession.user = identity;
|
|
39562
39816
|
return identity;
|
|
39563
39817
|
}
|
|
@@ -39584,7 +39838,7 @@ var init_sso = __esm({
|
|
|
39584
39838
|
access_token: tokens.access_token,
|
|
39585
39839
|
refresh_token: tokens.refresh_token ?? stored.refresh_token,
|
|
39586
39840
|
id_token: tokens.id_token ?? stored.id_token,
|
|
39587
|
-
expires_at:
|
|
39841
|
+
expires_at: expiresAt(tokens)
|
|
39588
39842
|
});
|
|
39589
39843
|
return identity;
|
|
39590
39844
|
} catch (error2) {
|
|
@@ -39726,12 +39980,12 @@ function bannerSurfaceLines(port, opts) {
|
|
|
39726
39980
|
];
|
|
39727
39981
|
}
|
|
39728
39982
|
function swaggerAdvertised() {
|
|
39729
|
-
const
|
|
39983
|
+
const TRUTHY3 = ["true", "1", "yes", "on"];
|
|
39730
39984
|
const raw = (process.env.TINA4_SWAGGER_ENABLED ?? "").trim().toLowerCase();
|
|
39731
39985
|
if (raw === "") {
|
|
39732
|
-
return
|
|
39986
|
+
return TRUTHY3.includes((process.env.TINA4_DEBUG ?? "").trim().toLowerCase());
|
|
39733
39987
|
}
|
|
39734
|
-
return
|
|
39988
|
+
return TRUTHY3.includes(raw);
|
|
39735
39989
|
}
|
|
39736
39990
|
async function autoMigrateOnStartup(migrationDir = "migrations", base = process.cwd()) {
|
|
39737
39991
|
const dir = resolve19(base, migrationDir);
|
|
@@ -40650,8 +40904,9 @@ function wireLocaleGlobal(frondEngine, base) {
|
|
|
40650
40904
|
async function startServer(config) {
|
|
40651
40905
|
loadEnv(".env.local");
|
|
40652
40906
|
loadEnv();
|
|
40653
|
-
const { ensureDevSecret: ensureDevSecret2 } = await Promise.resolve().then(() => (init_auth(), auth_exports));
|
|
40907
|
+
const { ensureDevSecret: ensureDevSecret2, requireBootSecret: requireBootSecret2 } = await Promise.resolve().then(() => (init_auth(), auth_exports));
|
|
40654
40908
|
ensureDevSecret2();
|
|
40909
|
+
requireBootSecret2();
|
|
40655
40910
|
_checkLegacyEnvVars();
|
|
40656
40911
|
const resolved = resolvePortAndHost(config);
|
|
40657
40912
|
const host = resolved.host;
|
|
@@ -42316,6 +42571,178 @@ var init_service = __esm({
|
|
|
42316
42571
|
}
|
|
42317
42572
|
});
|
|
42318
42573
|
|
|
42574
|
+
// src/ssrf.ts
|
|
42575
|
+
import { isIP } from "node:net";
|
|
42576
|
+
import { lookup } from "node:dns/promises";
|
|
42577
|
+
function v4ToBytes(ip) {
|
|
42578
|
+
const parts = ip.split(".");
|
|
42579
|
+
if (parts.length !== 4) return null;
|
|
42580
|
+
const bytes = new Uint8Array(4);
|
|
42581
|
+
for (let i = 0; i < 4; i++) {
|
|
42582
|
+
const n = Number(parts[i]);
|
|
42583
|
+
if (!Number.isInteger(n) || n < 0 || n > 255 || !/^\d+$/.test(parts[i])) return null;
|
|
42584
|
+
bytes[i] = n;
|
|
42585
|
+
}
|
|
42586
|
+
return bytes;
|
|
42587
|
+
}
|
|
42588
|
+
function v6ToBytes(input) {
|
|
42589
|
+
let ip = input.split("%")[0];
|
|
42590
|
+
const lastColon = ip.lastIndexOf(":");
|
|
42591
|
+
const tail = ip.slice(lastColon + 1);
|
|
42592
|
+
if (tail.includes(".")) {
|
|
42593
|
+
const v4 = v4ToBytes(tail);
|
|
42594
|
+
if (!v4) return null;
|
|
42595
|
+
const hextetA = (v4[0] << 8 | v4[1]).toString(16);
|
|
42596
|
+
const hextetB = (v4[2] << 8 | v4[3]).toString(16);
|
|
42597
|
+
ip = ip.slice(0, lastColon + 1) + hextetA + ":" + hextetB;
|
|
42598
|
+
}
|
|
42599
|
+
const halves = ip.split("::");
|
|
42600
|
+
if (halves.length > 2) return null;
|
|
42601
|
+
const head = halves[0] ? halves[0].split(":") : [];
|
|
42602
|
+
const back = halves.length === 2 ? halves[1] ? halves[1].split(":") : [] : [];
|
|
42603
|
+
let groups;
|
|
42604
|
+
if (halves.length === 1) {
|
|
42605
|
+
if (head.length !== 8) return null;
|
|
42606
|
+
groups = head;
|
|
42607
|
+
} else {
|
|
42608
|
+
const missing = 8 - (head.length + back.length);
|
|
42609
|
+
if (missing < 0) return null;
|
|
42610
|
+
groups = [...head, ...Array(missing).fill("0"), ...back];
|
|
42611
|
+
}
|
|
42612
|
+
if (groups.length !== 8) return null;
|
|
42613
|
+
const bytes = new Uint8Array(16);
|
|
42614
|
+
for (let i = 0; i < 8; i++) {
|
|
42615
|
+
if (!/^[0-9a-fA-F]{1,4}$/.test(groups[i])) return null;
|
|
42616
|
+
const g = parseInt(groups[i], 16);
|
|
42617
|
+
bytes[i * 2] = g >> 8 & 255;
|
|
42618
|
+
bytes[i * 2 + 1] = g & 255;
|
|
42619
|
+
}
|
|
42620
|
+
return bytes;
|
|
42621
|
+
}
|
|
42622
|
+
function ipToBytes(ip) {
|
|
42623
|
+
const cleaned = ip.split("%")[0];
|
|
42624
|
+
const family = isIP(cleaned);
|
|
42625
|
+
if (family === 4) return v4ToBytes(cleaned);
|
|
42626
|
+
if (family === 6) {
|
|
42627
|
+
const bytes = v6ToBytes(cleaned);
|
|
42628
|
+
if (!bytes) return null;
|
|
42629
|
+
const isMapped = bytes.subarray(0, 10).every((b) => b === 0) && bytes[10] === 255 && bytes[11] === 255;
|
|
42630
|
+
return isMapped ? bytes.subarray(12) : bytes;
|
|
42631
|
+
}
|
|
42632
|
+
return null;
|
|
42633
|
+
}
|
|
42634
|
+
function parseCidr(cidr) {
|
|
42635
|
+
const [netStr, bitsStr] = cidr.split("/");
|
|
42636
|
+
const net4 = ipToBytes(netStr);
|
|
42637
|
+
if (!net4) return null;
|
|
42638
|
+
return { net: net4, bits: Number(bitsStr) };
|
|
42639
|
+
}
|
|
42640
|
+
function inCidr(packed, cidr) {
|
|
42641
|
+
if (packed.length !== cidr.net.length) return false;
|
|
42642
|
+
const whole = Math.floor(cidr.bits / 8);
|
|
42643
|
+
const remainder = cidr.bits % 8;
|
|
42644
|
+
for (let i = 0; i < whole; i++) {
|
|
42645
|
+
if (packed[i] !== cidr.net[i]) return false;
|
|
42646
|
+
}
|
|
42647
|
+
if (remainder === 0) return true;
|
|
42648
|
+
const mask = 255 << 8 - remainder & 255;
|
|
42649
|
+
return (packed[whole] & mask) === (cidr.net[whole] & mask);
|
|
42650
|
+
}
|
|
42651
|
+
function allowPrivateRequests() {
|
|
42652
|
+
return TRUTHY2.has((process.env[ALLOW_PRIVATE_ENV] ?? "").trim().toLowerCase());
|
|
42653
|
+
}
|
|
42654
|
+
function isBlockedAddress(ip) {
|
|
42655
|
+
const packed = ipToBytes(ip);
|
|
42656
|
+
if (!packed) return true;
|
|
42657
|
+
return BLOCKED.some((cidr) => inCidr(packed, cidr));
|
|
42658
|
+
}
|
|
42659
|
+
function matchesAllowList(host, port, resolved, allowHosts) {
|
|
42660
|
+
const hostLower = host.toLowerCase();
|
|
42661
|
+
for (const raw of allowHosts ?? []) {
|
|
42662
|
+
const entry = String(raw).trim().toLowerCase();
|
|
42663
|
+
if (!entry) continue;
|
|
42664
|
+
if (entry === hostLower || entry === `${hostLower}:${port}`) return true;
|
|
42665
|
+
if (entry.includes("/")) {
|
|
42666
|
+
const cidr = parseCidr(entry);
|
|
42667
|
+
if (cidr && resolved.some((ip) => {
|
|
42668
|
+
const packed = ipToBytes(ip);
|
|
42669
|
+
return packed !== null && inCidr(packed, cidr);
|
|
42670
|
+
})) {
|
|
42671
|
+
return true;
|
|
42672
|
+
}
|
|
42673
|
+
}
|
|
42674
|
+
}
|
|
42675
|
+
return false;
|
|
42676
|
+
}
|
|
42677
|
+
function logBlock(host, ip) {
|
|
42678
|
+
void Promise.resolve().then(() => (init_logger(), logger_exports)).then((mod) => mod.Log?.warning?.(`SSRF guard blocked outbound request to ${host} (${ip})`)).catch(() => void 0);
|
|
42679
|
+
}
|
|
42680
|
+
async function guardUrl(url, allowHosts) {
|
|
42681
|
+
let parsed;
|
|
42682
|
+
try {
|
|
42683
|
+
parsed = new URL(url);
|
|
42684
|
+
} catch {
|
|
42685
|
+
throw new SsrfError(`Blocked request: '${url}' is not a valid URL`);
|
|
42686
|
+
}
|
|
42687
|
+
const scheme = parsed.protocol.replace(/:$/, "").toLowerCase();
|
|
42688
|
+
if (scheme !== "http" && scheme !== "https") {
|
|
42689
|
+
throw new SsrfError(`Blocked request: URL scheme '${scheme || "(none)"}' is not http or https`);
|
|
42690
|
+
}
|
|
42691
|
+
const host = parsed.hostname.replace(/^\[|\]$/g, "");
|
|
42692
|
+
if (!host) {
|
|
42693
|
+
throw new SsrfError("Blocked request: URL has no host");
|
|
42694
|
+
}
|
|
42695
|
+
const port = parsed.port ? Number(parsed.port) : scheme === "https" ? 443 : 80;
|
|
42696
|
+
if (allowPrivateRequests()) return;
|
|
42697
|
+
let resolved;
|
|
42698
|
+
try {
|
|
42699
|
+
const results = await lookup(host, { all: true });
|
|
42700
|
+
resolved = results.map((r) => r.address);
|
|
42701
|
+
} catch {
|
|
42702
|
+
throw new SsrfError(`Blocked request to ${host}: cannot resolve host`);
|
|
42703
|
+
}
|
|
42704
|
+
if (resolved.length === 0) {
|
|
42705
|
+
throw new SsrfError(`Blocked request to ${host}: cannot resolve host`);
|
|
42706
|
+
}
|
|
42707
|
+
if (matchesAllowList(host, port, resolved, allowHosts)) return;
|
|
42708
|
+
for (const ip of resolved) {
|
|
42709
|
+
if (isBlockedAddress(ip)) {
|
|
42710
|
+
logBlock(host, ip);
|
|
42711
|
+
throw new SsrfError(
|
|
42712
|
+
`Blocked request to private/internal address ${ip} (host ${host}): set ${ALLOW_PRIVATE_ENV}=true to allow, or pass an allow-list.`
|
|
42713
|
+
);
|
|
42714
|
+
}
|
|
42715
|
+
}
|
|
42716
|
+
}
|
|
42717
|
+
var ALLOW_PRIVATE_ENV, TRUTHY2, SsrfError, BLOCKED_CIDRS, BLOCKED;
|
|
42718
|
+
var init_ssrf = __esm({
|
|
42719
|
+
"src/ssrf.ts"() {
|
|
42720
|
+
"use strict";
|
|
42721
|
+
ALLOW_PRIVATE_ENV = "TINA4_ALLOW_PRIVATE_REQUESTS";
|
|
42722
|
+
TRUTHY2 = /* @__PURE__ */ new Set(["1", "true", "yes", "on"]);
|
|
42723
|
+
SsrfError = class extends Error {
|
|
42724
|
+
constructor(message) {
|
|
42725
|
+
super(message);
|
|
42726
|
+
this.name = "SsrfError";
|
|
42727
|
+
}
|
|
42728
|
+
};
|
|
42729
|
+
BLOCKED_CIDRS = [
|
|
42730
|
+
"0.0.0.0/8",
|
|
42731
|
+
"10.0.0.0/8",
|
|
42732
|
+
"127.0.0.0/8",
|
|
42733
|
+
"169.254.0.0/16",
|
|
42734
|
+
"172.16.0.0/12",
|
|
42735
|
+
"192.168.0.0/16",
|
|
42736
|
+
"100.64.0.0/10",
|
|
42737
|
+
"::1/128",
|
|
42738
|
+
"::/128",
|
|
42739
|
+
"fc00::/7",
|
|
42740
|
+
"fe80::/10"
|
|
42741
|
+
];
|
|
42742
|
+
BLOCKED = BLOCKED_CIDRS.map(parseCidr).filter((c) => c !== null);
|
|
42743
|
+
}
|
|
42744
|
+
});
|
|
42745
|
+
|
|
42319
42746
|
// src/api.ts
|
|
42320
42747
|
import http from "node:http";
|
|
42321
42748
|
import https from "node:https";
|
|
@@ -42472,6 +42899,7 @@ var init_api = __esm({
|
|
|
42472
42899
|
"src/api.ts"() {
|
|
42473
42900
|
"use strict";
|
|
42474
42901
|
init_version();
|
|
42902
|
+
init_ssrf();
|
|
42475
42903
|
ApiStreamError = class extends Error {
|
|
42476
42904
|
constructor(message, status2 = null) {
|
|
42477
42905
|
super(message);
|
|
@@ -42526,6 +42954,7 @@ var init_api = __esm({
|
|
|
42526
42954
|
transportFn;
|
|
42527
42955
|
cookiesEnabled;
|
|
42528
42956
|
cookies;
|
|
42957
|
+
allowHosts;
|
|
42529
42958
|
/**
|
|
42530
42959
|
* Construct an Api client.
|
|
42531
42960
|
*
|
|
@@ -42565,6 +42994,7 @@ var init_api = __esm({
|
|
|
42565
42994
|
this.transportFn = void 0;
|
|
42566
42995
|
this.cookiesEnabled = false;
|
|
42567
42996
|
this.cookies = {};
|
|
42997
|
+
this.allowHosts = [];
|
|
42568
42998
|
if (typeof authHeaderOrOptions === "object" && authHeaderOrOptions !== null) {
|
|
42569
42999
|
const opts = authHeaderOrOptions;
|
|
42570
43000
|
this.authHeader = opts.authHeader ?? "";
|
|
@@ -42574,6 +43004,7 @@ var init_api = __esm({
|
|
|
42574
43004
|
this.retryBackoff = opts.retryBackoff ?? 0.5;
|
|
42575
43005
|
this.transportFn = opts.transport;
|
|
42576
43006
|
this.cookiesEnabled = opts.cookies ?? false;
|
|
43007
|
+
this.allowHosts = opts.allowHosts ?? [];
|
|
42577
43008
|
if (opts.bearerToken != null) {
|
|
42578
43009
|
this.setBearerToken(opts.bearerToken);
|
|
42579
43010
|
} else if (opts.username != null && opts.password != null) {
|
|
@@ -42887,7 +43318,12 @@ var init_api = __esm({
|
|
|
42887
43318
|
* body-phase timeout is applied by the caller (streamBytes) via
|
|
42888
43319
|
* `res.destroy()`.
|
|
42889
43320
|
*/
|
|
42890
|
-
openStreamRequest(method, url, headers, data, connectSec) {
|
|
43321
|
+
async openStreamRequest(method, url, headers, data, connectSec) {
|
|
43322
|
+
try {
|
|
43323
|
+
await guardUrl(url, this.allowHosts);
|
|
43324
|
+
} catch (err) {
|
|
43325
|
+
throw new ApiStreamError(err instanceof Error ? err.message : String(err), null);
|
|
43326
|
+
}
|
|
42891
43327
|
return new Promise((resolve21, reject) => {
|
|
42892
43328
|
let parsed;
|
|
42893
43329
|
try {
|
|
@@ -42946,10 +43382,11 @@ var init_api = __esm({
|
|
|
42946
43382
|
*/
|
|
42947
43383
|
buildRequest(method, contentType, body, extraHeaders, targetUrl) {
|
|
42948
43384
|
const headers = { "User-Agent": `Tina4/${TINA4_VERSION}`, ...this.headers };
|
|
42949
|
-
|
|
43385
|
+
const sameOriginAsBase = targetUrl === void 0 || sameOrigin(targetUrl, this.baseUrl);
|
|
43386
|
+
if (this.authHeader && sameOriginAsBase) {
|
|
42950
43387
|
headers["Authorization"] = this.authHeader;
|
|
42951
43388
|
}
|
|
42952
|
-
if (this.cookiesEnabled) {
|
|
43389
|
+
if (this.cookiesEnabled && sameOriginAsBase) {
|
|
42953
43390
|
const cookieHeader = this.cookieHeader();
|
|
42954
43391
|
if (cookieHeader) {
|
|
42955
43392
|
headers["Cookie"] = cookieHeader;
|
|
@@ -43050,7 +43487,12 @@ var init_api = __esm({
|
|
|
43050
43487
|
* bearer token / session cookie never leaks to a host you didn't
|
|
43051
43488
|
* authenticate to.
|
|
43052
43489
|
*/
|
|
43053
|
-
performRequest(method, url, headers, data, redirectsLeft) {
|
|
43490
|
+
async performRequest(method, url, headers, data, redirectsLeft) {
|
|
43491
|
+
try {
|
|
43492
|
+
await guardUrl(url, this.allowHosts);
|
|
43493
|
+
} catch (err) {
|
|
43494
|
+
return { kind: "error", error: err instanceof Error ? err.message : String(err) };
|
|
43495
|
+
}
|
|
43054
43496
|
return new Promise((resolve21) => {
|
|
43055
43497
|
let parsed;
|
|
43056
43498
|
try {
|
|
@@ -45790,6 +46232,7 @@ var CURVE, RECORD_SIZE, MAX_PAYLOAD, PushError, Push;
|
|
|
45790
46232
|
var init_push = __esm({
|
|
45791
46233
|
"src/push.ts"() {
|
|
45792
46234
|
"use strict";
|
|
46235
|
+
init_ssrf();
|
|
45793
46236
|
CURVE = "prime256v1";
|
|
45794
46237
|
RECORD_SIZE = 4096;
|
|
45795
46238
|
MAX_PAYLOAD = RECORD_SIZE - 17;
|
|
@@ -45864,10 +46307,19 @@ var init_push = __esm({
|
|
|
45864
46307
|
return { rawPublic, rawPrivate };
|
|
45865
46308
|
}
|
|
45866
46309
|
async deliver(endpoint, config, keys, body) {
|
|
46310
|
+
try {
|
|
46311
|
+
await guardUrl(endpoint.toString(), this.options.allowHosts);
|
|
46312
|
+
} catch (error2) {
|
|
46313
|
+
if (error2 instanceof SsrfError) throw new PushError(error2.message);
|
|
46314
|
+
throw error2;
|
|
46315
|
+
}
|
|
45867
46316
|
let response;
|
|
45868
46317
|
try {
|
|
45869
46318
|
response = await fetch(endpoint, {
|
|
45870
46319
|
method: "POST",
|
|
46320
|
+
// A real push service answers the POST directly; a redirect from a push
|
|
46321
|
+
// endpoint is not followed to a private address (ADR-0084).
|
|
46322
|
+
redirect: "manual",
|
|
45871
46323
|
headers: {
|
|
45872
46324
|
Authorization: `vapid t=${vapidToken(endpoint.toString(), config.subject, keys.rawPrivate, keys.rawPublic)}, k=${config.publicKey}`,
|
|
45873
46325
|
"Content-Encoding": "aes128gcm",
|
|
@@ -47954,6 +48406,7 @@ var init_testClient = __esm({
|
|
|
47954
48406
|
reqHeaders["content-length"] = String(Buffer.byteLength(rawBody));
|
|
47955
48407
|
}
|
|
47956
48408
|
const socket = new Socket();
|
|
48409
|
+
Object.defineProperty(socket, "remoteAddress", { value: "127.0.0.1" });
|
|
47957
48410
|
const rawReq = new IncomingMessage2(socket);
|
|
47958
48411
|
rawReq.method = method.toUpperCase();
|
|
47959
48412
|
rawReq.url = path10;
|
|
@@ -48326,6 +48779,7 @@ var init_validator = __esm({
|
|
|
48326
48779
|
var index_exports = {};
|
|
48327
48780
|
__export(index_exports, {
|
|
48328
48781
|
AI_TOOLS: () => AI_TOOLS,
|
|
48782
|
+
ALLOW_PRIVATE_ENV: () => ALLOW_PRIVATE_ENV,
|
|
48329
48783
|
APPLICATION_FORM: () => APPLICATION_FORM,
|
|
48330
48784
|
APPLICATION_JSON: () => APPLICATION_JSON,
|
|
48331
48785
|
APPLICATION_OCTET: () => APPLICATION_OCTET,
|
|
@@ -48433,6 +48887,7 @@ __export(index_exports, {
|
|
|
48433
48887
|
Session: () => Session,
|
|
48434
48888
|
Sso: () => Sso,
|
|
48435
48889
|
SsoError: () => SsoError,
|
|
48890
|
+
SsrfError: () => SsrfError,
|
|
48436
48891
|
TAKEOVER_KILLED: () => TAKEOVER_KILLED,
|
|
48437
48892
|
TAKEOVER_NOTHING: () => TAKEOVER_NOTHING,
|
|
48438
48893
|
TAKEOVER_REFUSALS: () => TAKEOVER_REFUSALS,
|
|
@@ -48466,6 +48921,7 @@ __export(index_exports, {
|
|
|
48466
48921
|
addHtmlHelpers: () => addHtmlHelpers,
|
|
48467
48922
|
algorithmAvailable: () => algorithmAvailable,
|
|
48468
48923
|
allEnv: () => allEnv,
|
|
48924
|
+
allowPrivateRequests: () => allowPrivateRequests,
|
|
48469
48925
|
any: () => any,
|
|
48470
48926
|
attachCsrfFromEnv: () => attachCsrfFromEnv,
|
|
48471
48927
|
authMiddleware: () => authMiddleware,
|
|
@@ -48531,6 +48987,7 @@ __export(index_exports, {
|
|
|
48531
48987
|
graphqlAutoSchemaEnabled: () => graphqlAutoSchemaEnabled,
|
|
48532
48988
|
graphqlEndpoint: () => graphqlEndpoint,
|
|
48533
48989
|
graphqlMaxDepth: () => graphqlMaxDepth,
|
|
48990
|
+
guardUrl: () => guardUrl,
|
|
48534
48991
|
handle: () => handle,
|
|
48535
48992
|
handleFeedbackTurn: () => handleFeedbackTurn,
|
|
48536
48993
|
handleFeedbackWidgetJs: () => handleFeedbackWidgetJs,
|
|
@@ -48544,6 +49001,7 @@ __export(index_exports, {
|
|
|
48544
49001
|
installAll: () => installAll,
|
|
48545
49002
|
installSelected: () => installSelected,
|
|
48546
49003
|
isBannerSuppressed: () => isBannerSuppressed,
|
|
49004
|
+
isBlockedAddress: () => isBlockedAddress,
|
|
48547
49005
|
isDebugMode: () => isDebugMode,
|
|
48548
49006
|
isDev: () => isDev,
|
|
48549
49007
|
isInstalled: () => isInstalled,
|
|
@@ -48662,6 +49120,7 @@ var init_index = __esm({
|
|
|
48662
49120
|
init_service();
|
|
48663
49121
|
init_cache();
|
|
48664
49122
|
init_api();
|
|
49123
|
+
init_ssrf();
|
|
48665
49124
|
init_context();
|
|
48666
49125
|
init_events();
|
|
48667
49126
|
init_devAdmin();
|
|
@@ -48700,6 +49159,7 @@ var init_index = __esm({
|
|
|
48700
49159
|
init_index();
|
|
48701
49160
|
export {
|
|
48702
49161
|
AI_TOOLS,
|
|
49162
|
+
ALLOW_PRIVATE_ENV,
|
|
48703
49163
|
APPLICATION_FORM,
|
|
48704
49164
|
APPLICATION_JSON,
|
|
48705
49165
|
APPLICATION_OCTET,
|
|
@@ -48807,6 +49267,7 @@ export {
|
|
|
48807
49267
|
Session,
|
|
48808
49268
|
Sso,
|
|
48809
49269
|
SsoError,
|
|
49270
|
+
SsrfError,
|
|
48810
49271
|
TAKEOVER_KILLED,
|
|
48811
49272
|
TAKEOVER_NOTHING,
|
|
48812
49273
|
TAKEOVER_REFUSALS,
|
|
@@ -48840,6 +49301,7 @@ export {
|
|
|
48840
49301
|
addHtmlHelpers,
|
|
48841
49302
|
algorithmAvailable,
|
|
48842
49303
|
allEnv,
|
|
49304
|
+
allowPrivateRequests,
|
|
48843
49305
|
any,
|
|
48844
49306
|
attachCsrfFromEnv,
|
|
48845
49307
|
authMiddleware,
|
|
@@ -48905,6 +49367,7 @@ export {
|
|
|
48905
49367
|
graphqlAutoSchemaEnabled,
|
|
48906
49368
|
graphqlEndpoint,
|
|
48907
49369
|
graphqlMaxDepth,
|
|
49370
|
+
guardUrl,
|
|
48908
49371
|
handle,
|
|
48909
49372
|
handleFeedbackTurn,
|
|
48910
49373
|
handleFeedbackWidgetJs,
|
|
@@ -48918,6 +49381,7 @@ export {
|
|
|
48918
49381
|
installAll,
|
|
48919
49382
|
installSelected,
|
|
48920
49383
|
isBannerSuppressed,
|
|
49384
|
+
isBlockedAddress,
|
|
48921
49385
|
isDebugMode,
|
|
48922
49386
|
isDev,
|
|
48923
49387
|
isInstalled,
|