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
package/packages/cli/dist/bin.js
CHANGED
|
@@ -951,6 +951,8 @@ var auth_exports = {};
|
|
|
951
951
|
__export(auth_exports, {
|
|
952
952
|
Auth: () => Auth,
|
|
953
953
|
JWT_LEEWAY_SECONDS: () => JWT_LEEWAY_SECONDS,
|
|
954
|
+
MIN_SECRET_BYTES: () => MIN_SECRET_BYTES,
|
|
955
|
+
NON_IDENTITY_TOKEN_TYPES: () => NON_IDENTITY_TOKEN_TYPES,
|
|
954
956
|
algorithmAvailable: () => algorithmAvailable,
|
|
955
957
|
authMiddleware: () => authMiddleware,
|
|
956
958
|
authenticateRequest: () => authenticateRequest,
|
|
@@ -960,7 +962,10 @@ __export(auth_exports, {
|
|
|
960
962
|
getPayload: () => getPayload,
|
|
961
963
|
getToken: () => getToken,
|
|
962
964
|
hashPassword: () => hashPassword,
|
|
965
|
+
insecureSecretMessage: () => insecureSecretMessage,
|
|
966
|
+
isIdentityPayload: () => isIdentityPayload,
|
|
963
967
|
refreshToken: () => refreshToken,
|
|
968
|
+
requireBootSecret: () => requireBootSecret,
|
|
964
969
|
resolveAlgorithm: () => resolveAlgorithm,
|
|
965
970
|
validToken: () => validToken,
|
|
966
971
|
validateApiKey: () => validateApiKey
|
|
@@ -996,6 +1001,23 @@ async function _logWarning(message) {
|
|
|
996
1001
|
function _warnBlankSecret() {
|
|
997
1002
|
void _logWarning(BLANK_SECRET_WARNING);
|
|
998
1003
|
}
|
|
1004
|
+
function isIdentityPayload(payload) {
|
|
1005
|
+
return payload != null && !NON_IDENTITY_TOKEN_TYPES.includes(payload.type);
|
|
1006
|
+
}
|
|
1007
|
+
function insecureSecretMessage(secret) {
|
|
1008
|
+
const length = Buffer.byteLength(secret ?? "", "utf8");
|
|
1009
|
+
if (length >= MIN_SECRET_BYTES) return null;
|
|
1010
|
+
const what = length === 0 ? "is not set" : `is ${length} bytes`;
|
|
1011
|
+
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.`;
|
|
1012
|
+
}
|
|
1013
|
+
function requireBootSecret() {
|
|
1014
|
+
const algorithm = (process.env.TINA4_JWT_ALGORITHM ?? "").trim() || "HS256";
|
|
1015
|
+
if (RSA_SIGN_ALGORITHMS.has(algorithm)) return;
|
|
1016
|
+
const secret = process.env.TINA4_SECRET ?? "";
|
|
1017
|
+
if (secret === "" && _isDev()) return;
|
|
1018
|
+
const message = insecureSecretMessage(secret);
|
|
1019
|
+
if (message) throw new Error(message);
|
|
1020
|
+
}
|
|
999
1021
|
function ensureDevSecret(cwd) {
|
|
1000
1022
|
if (process.env.TINA4_SECRET) return null;
|
|
1001
1023
|
if (!_isDev() || _isCi() || _isProduction()) {
|
|
@@ -1126,6 +1148,13 @@ function validToken(token, secret, algorithm) {
|
|
|
1126
1148
|
const [h, p, sig] = parts;
|
|
1127
1149
|
const header = JSON.parse(base64urlDecode(h).toString());
|
|
1128
1150
|
if (header.alg !== resolvedAlgorithm) return null;
|
|
1151
|
+
if (HMAC_DIGESTS.has(resolvedAlgorithm)) {
|
|
1152
|
+
const weak = insecureSecretMessage(resolvedSecret);
|
|
1153
|
+
if (weak) {
|
|
1154
|
+
void _logWarning(weak);
|
|
1155
|
+
return null;
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1129
1158
|
const signingInput = `${h}.${p}`;
|
|
1130
1159
|
if (!verifySignature(signingInput, sig, resolvedSecret, resolvedAlgorithm)) {
|
|
1131
1160
|
return null;
|
|
@@ -1157,6 +1186,8 @@ function getPayload(token) {
|
|
|
1157
1186
|
function sign(input, secret, algorithm) {
|
|
1158
1187
|
const digest = HMAC_DIGESTS.get(algorithm);
|
|
1159
1188
|
if (digest) {
|
|
1189
|
+
const weak = insecureSecretMessage(secret);
|
|
1190
|
+
if (weak) throw new Error(weak);
|
|
1160
1191
|
return base64urlEncode(createHmac(digest, secret).update(input).digest());
|
|
1161
1192
|
}
|
|
1162
1193
|
const rsaAlgorithm = RSA_SIGN_ALGORITHMS.get(algorithm);
|
|
@@ -1214,17 +1245,17 @@ function authMiddleware(secret, algorithm) {
|
|
|
1214
1245
|
return;
|
|
1215
1246
|
}
|
|
1216
1247
|
const token = authHeader.slice(7);
|
|
1217
|
-
|
|
1248
|
+
const payload = validToken(token, secret, algorithm);
|
|
1249
|
+
if (!isIdentityPayload(payload)) {
|
|
1218
1250
|
res({ error: "Unauthorized" }, 401);
|
|
1219
1251
|
return;
|
|
1220
1252
|
}
|
|
1221
|
-
req2.auth =
|
|
1253
|
+
req2.auth = payload;
|
|
1222
1254
|
next();
|
|
1223
1255
|
};
|
|
1224
1256
|
}
|
|
1225
1257
|
function refreshToken(token, expiresIn = 60) {
|
|
1226
|
-
|
|
1227
|
-
const payload = getPayload(token);
|
|
1258
|
+
const payload = validToken(token);
|
|
1228
1259
|
if (!payload) return null;
|
|
1229
1260
|
const { iat: _iat, exp: _exp, ...claims } = payload;
|
|
1230
1261
|
return getToken(claims, expiresIn);
|
|
@@ -1233,7 +1264,8 @@ function authenticateRequest(headers, secret, algorithm) {
|
|
|
1233
1264
|
const authHeader = headers.authorization ?? headers.Authorization ?? "";
|
|
1234
1265
|
if (!authHeader.startsWith("Bearer ")) return null;
|
|
1235
1266
|
const token = authHeader.slice(7);
|
|
1236
|
-
|
|
1267
|
+
const payload = validToken(token, secret, algorithm);
|
|
1268
|
+
if (payload) return isIdentityPayload(payload) ? payload : null;
|
|
1237
1269
|
if (validateApiKey(token)) {
|
|
1238
1270
|
return { _auth: "api_key" };
|
|
1239
1271
|
}
|
|
@@ -1247,12 +1279,14 @@ function validateApiKey(provided, expected) {
|
|
|
1247
1279
|
if (a.length !== b.length) return false;
|
|
1248
1280
|
return timingSafeEqual(a, b);
|
|
1249
1281
|
}
|
|
1250
|
-
var BLANK_SECRET_WARNING, HMAC_DIGESTS, RSA_SIGN_ALGORITHMS, KNOWN_ALGORITHMS, ALGORITHM_REMEDY, capabilityCache, JWT_LEEWAY_SECONDS, Auth;
|
|
1282
|
+
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;
|
|
1251
1283
|
var init_auth = __esm({
|
|
1252
1284
|
"../core/src/auth.ts"() {
|
|
1253
1285
|
"use strict";
|
|
1254
1286
|
init_dotenv();
|
|
1255
1287
|
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.";
|
|
1288
|
+
NON_IDENTITY_TOKEN_TYPES = ["form"];
|
|
1289
|
+
MIN_SECRET_BYTES = 32;
|
|
1256
1290
|
HMAC_DIGESTS = /* @__PURE__ */ new Map([
|
|
1257
1291
|
["HS256", "sha256"],
|
|
1258
1292
|
["HS384", "sha384"],
|
|
@@ -10776,11 +10810,11 @@ async function eagerLoadBelongsToRelation(instances, model, relatedClass, relati
|
|
|
10776
10810
|
const relatedPkColumn = relatedClass.getDbColumn(relatedPk);
|
|
10777
10811
|
const related = await eagerQueryRelated(relatedClass, relatedPkColumn, values);
|
|
10778
10812
|
if (nested.length > 0 && related.length > 0) await relatedClass._eagerLoad(related, nested);
|
|
10779
|
-
const
|
|
10780
|
-
for (const record of related)
|
|
10813
|
+
const lookup2 = {};
|
|
10814
|
+
for (const record of related) lookup2[_joinKey(record[relatedPk])] = record;
|
|
10781
10815
|
for (const instance of instances) {
|
|
10782
10816
|
const value = instance[fkProp];
|
|
10783
|
-
relationshipCache(instance)[relationName] = value !== void 0 && value !== null ?
|
|
10817
|
+
relationshipCache(instance)[relationName] = value !== void 0 && value !== null ? lookup2[_joinKey(value)] ?? null : null;
|
|
10784
10818
|
}
|
|
10785
10819
|
}
|
|
10786
10820
|
async function eagerLoadRelation(instances, model, relationName, nested) {
|
|
@@ -15422,9 +15456,9 @@ var init_cache = __esm({
|
|
|
15422
15456
|
return entry.value;
|
|
15423
15457
|
}
|
|
15424
15458
|
async set(key, value, ttl) {
|
|
15425
|
-
const
|
|
15459
|
+
const expiresAt2 = ttl > 0 ? Date.now() + ttl * 1e3 : 0;
|
|
15426
15460
|
this.store.delete(key);
|
|
15427
|
-
this.store.set(key, { value, expiresAt });
|
|
15461
|
+
this.store.set(key, { value, expiresAt: expiresAt2 });
|
|
15428
15462
|
while (this.store.size > this.maxEntries) {
|
|
15429
15463
|
const firstKey = this.store.keys().next().value;
|
|
15430
15464
|
if (firstKey !== void 0) this.store.delete(firstKey);
|
|
@@ -15857,8 +15891,8 @@ ${s}\r
|
|
|
15857
15891
|
const oldest = files.shift();
|
|
15858
15892
|
if (oldest) fs.unlinkSync(path.join(this.dir, oldest.name));
|
|
15859
15893
|
}
|
|
15860
|
-
const
|
|
15861
|
-
const entry = { key, value, expiresAt };
|
|
15894
|
+
const expiresAt2 = ttl > 0 ? Date.now() / 1e3 + ttl : 0;
|
|
15895
|
+
const entry = { key, value, expiresAt: expiresAt2 };
|
|
15862
15896
|
fs.writeFileSync(this.keyPath(key), JSON.stringify(entry));
|
|
15863
15897
|
} catch {
|
|
15864
15898
|
}
|
|
@@ -17605,8 +17639,8 @@ function capCache(cache, maxEntries) {
|
|
|
17605
17639
|
}
|
|
17606
17640
|
function sweepExpiredCache(cache) {
|
|
17607
17641
|
const now = Date.now();
|
|
17608
|
-
for (const [key, [,
|
|
17609
|
-
if (
|
|
17642
|
+
for (const [key, [, expiresAt2]] of cache) {
|
|
17643
|
+
if (expiresAt2 <= now) cache.delete(key);
|
|
17610
17644
|
}
|
|
17611
17645
|
}
|
|
17612
17646
|
function tokenize(source) {
|
|
@@ -18578,6 +18612,12 @@ function setFormTokenSessionId(sessionId) {
|
|
|
18578
18612
|
}
|
|
18579
18613
|
function _buildFormTokenJwt(descriptor = "") {
|
|
18580
18614
|
const secret = process.env.TINA4_SECRET ?? "";
|
|
18615
|
+
const secretBytes = Buffer.byteLength(secret, "utf8");
|
|
18616
|
+
if (secretBytes < 32) {
|
|
18617
|
+
throw new Error(
|
|
18618
|
+
`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.`
|
|
18619
|
+
);
|
|
18620
|
+
}
|
|
18581
18621
|
const ttlMinutes = parseInt(process.env.TINA4_TOKEN_LIMIT || "60", 10);
|
|
18582
18622
|
const header = { alg: "HS256", typ: "JWT" };
|
|
18583
18623
|
const now = Math.floor(Date.now() / 1e3);
|
|
@@ -20119,8 +20159,8 @@ var init_engine = __esm({
|
|
|
20119
20159
|
sweepExpiredCache(this.fragmentCache);
|
|
20120
20160
|
const cached = this.fragmentCache.get(cacheKey);
|
|
20121
20161
|
if (cached) {
|
|
20122
|
-
const [htmlContent,
|
|
20123
|
-
if (Date.now() <
|
|
20162
|
+
const [htmlContent, expiresAt2] = cached;
|
|
20163
|
+
if (Date.now() < expiresAt2) {
|
|
20124
20164
|
let i2 = start2 + 1;
|
|
20125
20165
|
let depth = 0;
|
|
20126
20166
|
while (i2 < tokens.length) {
|
|
@@ -20913,7 +20953,7 @@ function csrfBearerIsValid(req2, secret) {
|
|
|
20913
20953
|
const authHeader = req2.headers.authorization ?? "";
|
|
20914
20954
|
if (!authHeader.startsWith("Bearer ")) return false;
|
|
20915
20955
|
const bearerToken = authHeader.slice(7).trim();
|
|
20916
|
-
return Boolean(bearerToken && validToken(bearerToken, secret));
|
|
20956
|
+
return Boolean(bearerToken && isIdentityPayload(validToken(bearerToken, secret)));
|
|
20917
20957
|
}
|
|
20918
20958
|
function csrfRequestToken(req2) {
|
|
20919
20959
|
const query = req2.query ?? {};
|
|
@@ -22050,6 +22090,22 @@ var init_transport = __esm({
|
|
|
22050
22090
|
}
|
|
22051
22091
|
});
|
|
22052
22092
|
|
|
22093
|
+
// ../core/src/ssoIdentity.ts
|
|
22094
|
+
function liveSessionIdentity(stored) {
|
|
22095
|
+
if (!stored || typeof stored !== "object") return null;
|
|
22096
|
+
const identity = stored.identity;
|
|
22097
|
+
if (!identity || typeof identity !== "object" || !identity.issuer || !identity.subject) return null;
|
|
22098
|
+
const expiresAt2 = stored.expires_at ?? 0;
|
|
22099
|
+
if (typeof expiresAt2 !== "number") return null;
|
|
22100
|
+
if (expiresAt2 > 0 && Math.floor(Date.now() / 1e3) >= expiresAt2) return null;
|
|
22101
|
+
return identity;
|
|
22102
|
+
}
|
|
22103
|
+
var init_ssoIdentity = __esm({
|
|
22104
|
+
"../core/src/ssoIdentity.ts"() {
|
|
22105
|
+
"use strict";
|
|
22106
|
+
}
|
|
22107
|
+
});
|
|
22108
|
+
|
|
22053
22109
|
// ../core/src/authGate.ts
|
|
22054
22110
|
function enforceRouteAuth(req2, res, match, isDevAdmin) {
|
|
22055
22111
|
if (!(match.secure === true && match.noAuth !== true && !isDevAdmin)) {
|
|
@@ -22057,38 +22113,50 @@ function enforceRouteAuth(req2, res, match, isDevAdmin) {
|
|
|
22057
22113
|
}
|
|
22058
22114
|
const authHeader = req2.headers.authorization ?? "";
|
|
22059
22115
|
const headerToken = authHeader.startsWith("Bearer ") ? authHeader.slice(7) : "";
|
|
22060
|
-
let
|
|
22116
|
+
let payload = null;
|
|
22061
22117
|
let tokenSource = "";
|
|
22062
|
-
|
|
22063
|
-
|
|
22064
|
-
|
|
22118
|
+
let resolvedToken = "";
|
|
22119
|
+
if (headerToken) {
|
|
22120
|
+
const candidate = validToken(headerToken);
|
|
22121
|
+
if (isIdentityPayload(candidate)) {
|
|
22122
|
+
payload = candidate;
|
|
22123
|
+
tokenSource = "header";
|
|
22124
|
+
resolvedToken = headerToken;
|
|
22125
|
+
}
|
|
22065
22126
|
}
|
|
22066
|
-
if (!
|
|
22127
|
+
if (!payload) {
|
|
22067
22128
|
const bodyToken = req2.body?.formToken;
|
|
22068
|
-
if (bodyToken
|
|
22069
|
-
|
|
22070
|
-
|
|
22129
|
+
if (bodyToken) {
|
|
22130
|
+
const candidate = validToken(bodyToken);
|
|
22131
|
+
if (isIdentityPayload(candidate)) {
|
|
22132
|
+
payload = candidate;
|
|
22133
|
+
tokenSource = "body";
|
|
22134
|
+
resolvedToken = bodyToken;
|
|
22135
|
+
}
|
|
22071
22136
|
}
|
|
22072
22137
|
}
|
|
22073
|
-
if (!
|
|
22074
|
-
const
|
|
22075
|
-
|
|
22076
|
-
if (identity?.issuer && identity?.subject) {
|
|
22138
|
+
if (!payload) {
|
|
22139
|
+
const identity = liveSessionIdentity(req2.session?.get?.("_tina4_sso"));
|
|
22140
|
+
if (identity) {
|
|
22077
22141
|
req2.user = identity;
|
|
22078
22142
|
return rbacForbidden(match, identity, res);
|
|
22079
22143
|
}
|
|
22080
22144
|
const sessionToken = req2.session?.get?.("token");
|
|
22081
|
-
if (sessionToken
|
|
22082
|
-
|
|
22083
|
-
|
|
22145
|
+
if (sessionToken) {
|
|
22146
|
+
const candidate = validToken(sessionToken);
|
|
22147
|
+
if (isIdentityPayload(candidate)) {
|
|
22148
|
+
payload = candidate;
|
|
22149
|
+
tokenSource = "session";
|
|
22150
|
+
resolvedToken = sessionToken;
|
|
22151
|
+
}
|
|
22084
22152
|
}
|
|
22085
22153
|
}
|
|
22086
|
-
if (!
|
|
22154
|
+
if (!payload) {
|
|
22087
22155
|
res.raw.writeHead(401, { "Content-Type": "application/json" });
|
|
22088
22156
|
res.raw.end(JSON.stringify({ error: "Unauthorized" }));
|
|
22089
22157
|
return true;
|
|
22090
22158
|
}
|
|
22091
|
-
req2.user =
|
|
22159
|
+
req2.user = payload;
|
|
22092
22160
|
if (tokenSource === "body") {
|
|
22093
22161
|
const fresh = refreshToken(resolvedToken);
|
|
22094
22162
|
if (fresh) {
|
|
@@ -22138,6 +22206,7 @@ var init_authGate = __esm({
|
|
|
22138
22206
|
"../core/src/authGate.ts"() {
|
|
22139
22207
|
"use strict";
|
|
22140
22208
|
init_auth();
|
|
22209
|
+
init_ssoIdentity();
|
|
22141
22210
|
}
|
|
22142
22211
|
});
|
|
22143
22212
|
|
|
@@ -23231,8 +23300,8 @@ var init_mongoHandler = __esm({
|
|
|
23231
23300
|
if (!result || result === "__EMPTY__") return null;
|
|
23232
23301
|
try {
|
|
23233
23302
|
const doc = JSON.parse(result);
|
|
23234
|
-
const
|
|
23235
|
-
if (
|
|
23303
|
+
const expiresAt2 = Number(doc?.expires_at ?? 0);
|
|
23304
|
+
if (expiresAt2 > 0 && Date.now() / 1e3 > expiresAt2) {
|
|
23236
23305
|
this.destroy(sessionId);
|
|
23237
23306
|
return null;
|
|
23238
23307
|
}
|
|
@@ -23458,6 +23527,9 @@ parentPort.on("message", (message) => {
|
|
|
23458
23527
|
|
|
23459
23528
|
// ../core/src/sessionHandlers/databaseHandler.ts
|
|
23460
23529
|
import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
|
|
23530
|
+
function sleepSync2(ms) {
|
|
23531
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
23532
|
+
}
|
|
23461
23533
|
function column(row, name) {
|
|
23462
23534
|
if (name in row) return row[name];
|
|
23463
23535
|
return row[name.toUpperCase()];
|
|
@@ -23466,7 +23538,7 @@ function schemeOf2(url) {
|
|
|
23466
23538
|
const match = url.match(/^([a-zA-Z][a-zA-Z0-9+.-]*):/);
|
|
23467
23539
|
return match ? match[1].toLowerCase() : "unknown";
|
|
23468
23540
|
}
|
|
23469
|
-
var CREATE_TABLE, DatabaseSessionHandler;
|
|
23541
|
+
var CREATE_TABLE, CREATE_ATTEMPTS, CREATE_RETRY_DELAY_MS, DatabaseSessionHandler;
|
|
23470
23542
|
var init_databaseHandler = __esm({
|
|
23471
23543
|
"../core/src/sessionHandlers/databaseHandler.ts"() {
|
|
23472
23544
|
"use strict";
|
|
@@ -23546,6 +23618,8 @@ var init_databaseHandler = __esm({
|
|
|
23546
23618
|
END
|
|
23547
23619
|
`
|
|
23548
23620
|
};
|
|
23621
|
+
CREATE_ATTEMPTS = 5;
|
|
23622
|
+
CREATE_RETRY_DELAY_MS = 50;
|
|
23549
23623
|
DatabaseSessionHandler = class {
|
|
23550
23624
|
sqliteHandle = null;
|
|
23551
23625
|
/** Set for SQLite. Null when this handler talks to a networked engine. */
|
|
@@ -23586,6 +23660,7 @@ var init_databaseHandler = __esm({
|
|
|
23586
23660
|
get sqlite() {
|
|
23587
23661
|
if (this.sqliteHandle === null) {
|
|
23588
23662
|
this.sqliteHandle = new DatabaseSync3(this.dbPath);
|
|
23663
|
+
this.sqliteHandle.exec("PRAGMA busy_timeout = 5000");
|
|
23589
23664
|
this.sqliteHandle.exec("PRAGMA journal_mode = WAL");
|
|
23590
23665
|
}
|
|
23591
23666
|
return this.sqliteHandle;
|
|
@@ -23673,15 +23748,55 @@ var init_databaseHandler = __esm({
|
|
|
23673
23748
|
sqlCommandSync(this.target, sql, params);
|
|
23674
23749
|
}
|
|
23675
23750
|
/**
|
|
23676
|
-
* Ensure the session table exists (called once on first use)
|
|
23751
|
+
* Ensure the session table exists (called once on first use) - safely when
|
|
23752
|
+
* several processes race to create it.
|
|
23753
|
+
*
|
|
23754
|
+
* Every per-engine statement above is check-then-act, so two processes can
|
|
23755
|
+
* both find the table absent and both CREATE; the loser errors, and every
|
|
23756
|
+
* engine spells that differently (a pg_type unique violation on PostgreSQL,
|
|
23757
|
+
* Msg 2714 on SQL Server, an RDB$RELATIONS unique violation on Firebird). So
|
|
23758
|
+
* a failure is judged by RE-CHECKING whether the table now exists, never by
|
|
23759
|
+
* parsing the message - the rescue the Firebird note above has always
|
|
23760
|
+
* assumed, and which was not here. MEASURED on the lab before it: six real
|
|
23761
|
+
* processes racing lost one to five of them on PostgreSQL, SQLite and
|
|
23762
|
+
* Firebird.
|
|
23763
|
+
*
|
|
23764
|
+
* The re-check is RETRIED because the loser can fail BEFORE the winner has
|
|
23765
|
+
* committed. On MySQL, CREATE TABLE IF NOT EXISTS takes a shared metadata
|
|
23766
|
+
* lock, checks, then upgrades it to exclusive; two upgrades deadlock, and when
|
|
23767
|
+
* MySQL cannot back the victim off silently it answers 1213 "Deadlock found"
|
|
23768
|
+
* while the winner's table is still invisible (tina4-php CI run 35972320442).
|
|
23769
|
+
* Bounded, so a genuine failure (no CREATE grant) still throws. Same attempts
|
|
23770
|
+
* and delays in all four frameworks.
|
|
23677
23771
|
*/
|
|
23678
23772
|
ensureTable() {
|
|
23679
23773
|
if (this.initialized) return;
|
|
23680
23774
|
const ddl = CREATE_TABLE[this.engine];
|
|
23681
|
-
|
|
23682
|
-
|
|
23775
|
+
for (let attempt = 1; ; attempt++) {
|
|
23776
|
+
try {
|
|
23777
|
+
if (this.target === null) this.sqlite.exec(ddl);
|
|
23778
|
+
else sqlCommandSync(this.target, ddl, []);
|
|
23779
|
+
break;
|
|
23780
|
+
} catch (error2) {
|
|
23781
|
+
if (this.tableExists()) break;
|
|
23782
|
+
if (attempt >= CREATE_ATTEMPTS) throw error2;
|
|
23783
|
+
sleepSync2(CREATE_RETRY_DELAY_MS * attempt);
|
|
23784
|
+
}
|
|
23785
|
+
}
|
|
23683
23786
|
this.initialized = true;
|
|
23684
23787
|
}
|
|
23788
|
+
/**
|
|
23789
|
+
* Whether tina4_session exists, asked the one way every engine answers alike:
|
|
23790
|
+
* a query against it that returns no rows succeeds only if it is there.
|
|
23791
|
+
*/
|
|
23792
|
+
tableExists() {
|
|
23793
|
+
try {
|
|
23794
|
+
this.query("SELECT 1 FROM tina4_session WHERE 1 = 0", []);
|
|
23795
|
+
return true;
|
|
23796
|
+
} catch {
|
|
23797
|
+
return false;
|
|
23798
|
+
}
|
|
23799
|
+
}
|
|
23685
23800
|
read(sessionId) {
|
|
23686
23801
|
this.ensureTable();
|
|
23687
23802
|
const rows = this.query(
|
|
@@ -23691,8 +23806,8 @@ var init_databaseHandler = __esm({
|
|
|
23691
23806
|
const row = rows[0];
|
|
23692
23807
|
if (!row) return null;
|
|
23693
23808
|
const now = Date.now() / 1e3;
|
|
23694
|
-
const
|
|
23695
|
-
if (
|
|
23809
|
+
const expiresAt2 = Number(column(row, "expires_at") ?? 0);
|
|
23810
|
+
if (expiresAt2 > 0 && expiresAt2 < now) {
|
|
23696
23811
|
this.destroy(sessionId);
|
|
23697
23812
|
return null;
|
|
23698
23813
|
}
|
|
@@ -23705,7 +23820,7 @@ var init_databaseHandler = __esm({
|
|
|
23705
23820
|
write(sessionId, data, ttl) {
|
|
23706
23821
|
this.ensureTable();
|
|
23707
23822
|
const json = JSON.stringify(data);
|
|
23708
|
-
const
|
|
23823
|
+
const expiresAt2 = ttl > 0 ? Date.now() / 1e3 + ttl : 0;
|
|
23709
23824
|
const existing = this.query(
|
|
23710
23825
|
"SELECT session_id FROM tina4_session WHERE session_id = ?",
|
|
23711
23826
|
[sessionId]
|
|
@@ -23713,12 +23828,12 @@ var init_databaseHandler = __esm({
|
|
|
23713
23828
|
if (existing.length > 0) {
|
|
23714
23829
|
this.exec(
|
|
23715
23830
|
"UPDATE tina4_session SET data = ?, expires_at = ? WHERE session_id = ?",
|
|
23716
|
-
[json,
|
|
23831
|
+
[json, expiresAt2, sessionId]
|
|
23717
23832
|
);
|
|
23718
23833
|
} else {
|
|
23719
23834
|
this.exec(
|
|
23720
23835
|
"INSERT INTO tina4_session (session_id, data, expires_at) VALUES (?, ?, ?)",
|
|
23721
|
-
[sessionId, json,
|
|
23836
|
+
[sessionId, json, expiresAt2]
|
|
23722
23837
|
);
|
|
23723
23838
|
}
|
|
23724
23839
|
}
|
|
@@ -27713,7 +27828,7 @@ function envTruthy(val) {
|
|
|
27713
27828
|
return ["true", "1", "yes", "on"].includes(val.trim().toLowerCase());
|
|
27714
27829
|
}
|
|
27715
27830
|
function isLoopback(ip) {
|
|
27716
|
-
if (ip == null || ip === "") return
|
|
27831
|
+
if (ip == null || ip === "") return false;
|
|
27717
27832
|
let addr = ip.trim().toLowerCase();
|
|
27718
27833
|
if (addr.startsWith("::ffff:")) addr = addr.slice(7);
|
|
27719
27834
|
return addr === "::1" || addr === "localhost" || addr.startsWith("127.");
|
|
@@ -29304,6 +29419,7 @@ __export(graphql_exports, {
|
|
|
29304
29419
|
graphqlAutoSchemaEnabled: () => graphqlAutoSchemaEnabled,
|
|
29305
29420
|
graphqlEndpoint: () => graphqlEndpoint,
|
|
29306
29421
|
graphqlMaxDepth: () => graphqlMaxDepth,
|
|
29422
|
+
graphqlMaxNodes: () => graphqlMaxNodes,
|
|
29307
29423
|
tokenize: () => tokenize2
|
|
29308
29424
|
});
|
|
29309
29425
|
function tokenize2(source) {
|
|
@@ -29343,7 +29459,12 @@ function graphqlMaxDepth() {
|
|
|
29343
29459
|
const n = parseInt(raw, 10);
|
|
29344
29460
|
return Number.isNaN(n) ? 50 : n;
|
|
29345
29461
|
}
|
|
29346
|
-
|
|
29462
|
+
function graphqlMaxNodes() {
|
|
29463
|
+
const raw = (process.env.TINA4_GRAPHQL_MAX_NODES ?? "1000").trim();
|
|
29464
|
+
const n = parseInt(raw, 10);
|
|
29465
|
+
return Number.isNaN(n) ? 1e3 : n;
|
|
29466
|
+
}
|
|
29467
|
+
var TOKEN_PATTERNS, ParseError, Parser, ComplexityError, GraphQL, GraphQLType;
|
|
29347
29468
|
var init_graphql = __esm({
|
|
29348
29469
|
"../core/src/graphql.ts"() {
|
|
29349
29470
|
"use strict";
|
|
@@ -29382,9 +29503,16 @@ var init_graphql = __esm({
|
|
|
29382
29503
|
Parser = class {
|
|
29383
29504
|
tokens;
|
|
29384
29505
|
pos;
|
|
29506
|
+
// F2: bound the parser's own recursion. A deeply nested query
|
|
29507
|
+
// ("{a{a{a...}}}") otherwise recurses in parseSelectionSet until the call
|
|
29508
|
+
// stack overflows — BEFORE the execution-time depth guard can run. Reusing
|
|
29509
|
+
// TINA4_GRAPHQL_MAX_DEPTH keeps parse and execution on one bound.
|
|
29510
|
+
depth = 0;
|
|
29511
|
+
maxDepth;
|
|
29385
29512
|
constructor(tokens) {
|
|
29386
29513
|
this.tokens = tokens;
|
|
29387
29514
|
this.pos = 0;
|
|
29515
|
+
this.maxDepth = graphqlMaxDepth();
|
|
29388
29516
|
}
|
|
29389
29517
|
peek() {
|
|
29390
29518
|
return this.pos < this.tokens.length ? this.tokens[this.pos] : null;
|
|
@@ -29466,6 +29594,17 @@ var init_graphql = __esm({
|
|
|
29466
29594
|
};
|
|
29467
29595
|
}
|
|
29468
29596
|
parseSelectionSet() {
|
|
29597
|
+
this.depth++;
|
|
29598
|
+
if (this.maxDepth > 0 && this.depth > this.maxDepth) {
|
|
29599
|
+
throw new Error(`Query exceeds maximum depth of ${this.maxDepth}`);
|
|
29600
|
+
}
|
|
29601
|
+
try {
|
|
29602
|
+
return this.parseSelectionSetInner();
|
|
29603
|
+
} finally {
|
|
29604
|
+
this.depth--;
|
|
29605
|
+
}
|
|
29606
|
+
}
|
|
29607
|
+
parseSelectionSetInner() {
|
|
29469
29608
|
this.expect("LBRACE");
|
|
29470
29609
|
const selections = [];
|
|
29471
29610
|
while (!this.match("RBRACE")) {
|
|
@@ -29620,6 +29759,8 @@ var init_graphql = __esm({
|
|
|
29620
29759
|
return t;
|
|
29621
29760
|
}
|
|
29622
29761
|
};
|
|
29762
|
+
ComplexityError = class extends Error {
|
|
29763
|
+
};
|
|
29623
29764
|
GraphQL = class _GraphQL {
|
|
29624
29765
|
types = /* @__PURE__ */ new Map();
|
|
29625
29766
|
queries = /* @__PURE__ */ new Map();
|
|
@@ -29642,6 +29783,12 @@ var init_graphql = __esm({
|
|
|
29642
29783
|
* spread / inline fragment, so circular fragments are caught too.
|
|
29643
29784
|
*/
|
|
29644
29785
|
maxDepth = graphqlMaxDepth();
|
|
29786
|
+
/**
|
|
29787
|
+
* Maximum expanded selection nodes per query (from `TINA4_GRAPHQL_MAX_NODES`,
|
|
29788
|
+
* default 1000; `<= 0` disables). Bounds query WIDTH — fragment bombs and
|
|
29789
|
+
* alias explosions — which the depth guard does not. See {@link graphqlMaxNodes}.
|
|
29790
|
+
*/
|
|
29791
|
+
maxNodes = graphqlMaxNodes();
|
|
29645
29792
|
/**
|
|
29646
29793
|
* Decorator-style resolver registration.
|
|
29647
29794
|
*
|
|
@@ -29789,6 +29936,16 @@ var init_graphql = __esm({
|
|
|
29789
29936
|
return { data: null, errors: [{ message: "No operation found" }] };
|
|
29790
29937
|
}
|
|
29791
29938
|
const op = operations[0];
|
|
29939
|
+
if (this.maxNodes > 0) {
|
|
29940
|
+
try {
|
|
29941
|
+
this.assertComplexity(op.selections, fragments);
|
|
29942
|
+
} catch (e) {
|
|
29943
|
+
if (e instanceof ComplexityError) {
|
|
29944
|
+
return { data: null, errors: [{ message: e.message }] };
|
|
29945
|
+
}
|
|
29946
|
+
throw e;
|
|
29947
|
+
}
|
|
29948
|
+
}
|
|
29792
29949
|
const resolvers = op.operation === "query" ? this.queries : this.mutations;
|
|
29793
29950
|
for (const vdef of op.variables) {
|
|
29794
29951
|
if (!(vdef.name in vars) && vdef.default !== void 0) {
|
|
@@ -29814,6 +29971,36 @@ var init_graphql = __esm({
|
|
|
29814
29971
|
* instead of recursing until the interpreter stack overflows. Top-level
|
|
29815
29972
|
* starts at depth 1; `maxDepth <= 0` disables the guard.
|
|
29816
29973
|
*/
|
|
29974
|
+
/**
|
|
29975
|
+
* Count the expanded selection nodes, throwing {@link ComplexityError} past
|
|
29976
|
+
* `maxNodes` (F2). Fragment spreads are expanded (so a fragment bomb is
|
|
29977
|
+
* counted, not the small unexpanded document) and every field — including
|
|
29978
|
+
* each alias — counts once. The count is capped by `maxNodes` and the walk by
|
|
29979
|
+
* `maxDepth`, so a circular fragment trips the budget instead of looping and
|
|
29980
|
+
* the check itself can never cost more than the budget.
|
|
29981
|
+
*/
|
|
29982
|
+
assertComplexity(selections, fragments) {
|
|
29983
|
+
const depthCap = this.maxDepth > 0 ? this.maxDepth : 1e3;
|
|
29984
|
+
let count = 0;
|
|
29985
|
+
const walk2 = (sels, depth) => {
|
|
29986
|
+
if (depth > depthCap) return;
|
|
29987
|
+
for (const sel of sels) {
|
|
29988
|
+
count++;
|
|
29989
|
+
if (count > this.maxNodes) {
|
|
29990
|
+
throw new ComplexityError(`Query exceeds maximum complexity of ${this.maxNodes} nodes`);
|
|
29991
|
+
}
|
|
29992
|
+
if (sel.kind === "fragment_spread") {
|
|
29993
|
+
const frag = fragments.get(sel.name);
|
|
29994
|
+
if (frag) walk2(frag.selections, depth + 1);
|
|
29995
|
+
} else if (sel.kind === "inline_fragment") {
|
|
29996
|
+
walk2(sel.selections, depth + 1);
|
|
29997
|
+
} else if (sel.kind === "field" && sel.selections) {
|
|
29998
|
+
walk2(sel.selections, depth + 1);
|
|
29999
|
+
}
|
|
30000
|
+
}
|
|
30001
|
+
};
|
|
30002
|
+
walk2(selections, 1);
|
|
30003
|
+
}
|
|
29817
30004
|
async resolveSelectionsInto(selections, resolvers, parent, variables, context, fragments, target, depth) {
|
|
29818
30005
|
const errors = [];
|
|
29819
30006
|
if (this.maxDepth > 0 && depth > this.maxDepth) {
|
|
@@ -31016,7 +31203,8 @@ function wsAuthorized(route, headers, queryString = "", subprotocol = "") {
|
|
|
31016
31203
|
const token = wsToken(headers, queryString, subprotocol);
|
|
31017
31204
|
if (!token) return [null, false];
|
|
31018
31205
|
const payload = validToken(token);
|
|
31019
|
-
return [
|
|
31206
|
+
if (!isIdentityPayload(payload)) return [null, false];
|
|
31207
|
+
return [payload, true];
|
|
31020
31208
|
}
|
|
31021
31209
|
function offeredBearerSubprotocol(subprotocol) {
|
|
31022
31210
|
return subprotocol.split(",").some((p) => p.trim().toLowerCase() === "bearer");
|
|
@@ -32211,12 +32399,36 @@ var init_mongoBackend = __esm({
|
|
|
32211
32399
|
}
|
|
32212
32400
|
}
|
|
32213
32401
|
else if (operation === "size") {
|
|
32214
|
-
|
|
32215
|
-
|
|
32216
|
-
|
|
32217
|
-
|
|
32402
|
+
// ADR-0022 dec 7: size(status) must not answer a different question.
|
|
32403
|
+
// The dead aliases count the .dead_letter store (== deadLetters().length);
|
|
32404
|
+
// reserved/completed count by status; else pending. data = status.
|
|
32405
|
+
const status = data || "pending";
|
|
32406
|
+
let count: number;
|
|
32407
|
+
if (status === "dead" || status === "failed" || status === "dead_letter") {
|
|
32408
|
+
count = await col.countDocuments({ queue: queueName + ".dead_letter" });
|
|
32409
|
+
} else if (status === "reserved" || status === "completed") {
|
|
32410
|
+
count = await col.countDocuments({ queue: queueName, status });
|
|
32411
|
+
} else {
|
|
32412
|
+
count = await col.countDocuments({ queue: queueName, status: "pending" });
|
|
32413
|
+
}
|
|
32218
32414
|
process.stdout.write(String(count));
|
|
32219
32415
|
}
|
|
32416
|
+
else if (operation === "reject") {
|
|
32417
|
+
// Dead-letter NOW, no retry (ADR-0023). Floor attempts at maxRetries
|
|
32418
|
+
// so it reads as exhausted (parity with fail()'s dead-letter branch
|
|
32419
|
+
// and size("dead")). data = JSON { id, reason, maxRetries }.
|
|
32420
|
+
const info = JSON.parse(data);
|
|
32421
|
+
const doc = await col.findOne({ queue: queueName, id: info.id });
|
|
32422
|
+
if (doc) {
|
|
32423
|
+
const attempts = Math.max((doc.attempts || 0) + 1, info.maxRetries);
|
|
32424
|
+
await col.insertOne({
|
|
32425
|
+
...doc, _id: undefined, attempts,
|
|
32426
|
+
status: "dead", queue: queueName + ".dead_letter", error: info.reason,
|
|
32427
|
+
});
|
|
32428
|
+
await col.deleteOne({ _id: doc._id, queue: queueName });
|
|
32429
|
+
}
|
|
32430
|
+
process.stdout.write("__OK__");
|
|
32431
|
+
}
|
|
32220
32432
|
else if (operation === "clear") {
|
|
32221
32433
|
await col.deleteMany({ queue: queueName });
|
|
32222
32434
|
process.stdout.write("__CLEARED__");
|
|
@@ -32452,11 +32664,20 @@ var init_mongoBackend = __esm({
|
|
|
32452
32664
|
return null;
|
|
32453
32665
|
}
|
|
32454
32666
|
}
|
|
32455
|
-
size(queue) {
|
|
32456
|
-
const result = this.execSync("size", queue);
|
|
32667
|
+
size(queue, status2 = "pending") {
|
|
32668
|
+
const result = this.execSync("size", queue, status2);
|
|
32457
32669
|
const num = parseInt(result, 10);
|
|
32458
32670
|
return isNaN(num) ? 0 : num;
|
|
32459
32671
|
}
|
|
32672
|
+
/**
|
|
32673
|
+
* Dead-letter a job immediately — no retry (ADR-0023 reject()). Distinct from
|
|
32674
|
+
* fail(): fail() requeues until maxRetries is spent. attempts is floored at
|
|
32675
|
+
* maxRetries so deadLetters()/size("dead") (which filter/scope to the
|
|
32676
|
+
* dead-letter store) see it.
|
|
32677
|
+
*/
|
|
32678
|
+
reject(queue, id, reason, maxRetries) {
|
|
32679
|
+
this.execSync("reject", queue, JSON.stringify({ id, reason, maxRetries }));
|
|
32680
|
+
}
|
|
32460
32681
|
clear(queue) {
|
|
32461
32682
|
this.execSync("clear", queue);
|
|
32462
32683
|
}
|
|
@@ -32551,7 +32772,9 @@ function createJob(data, queue) {
|
|
|
32551
32772
|
queue._failJob(job.topic, job, reason, queue.getMaxRetries());
|
|
32552
32773
|
},
|
|
32553
32774
|
reject(reason = "") {
|
|
32554
|
-
job.
|
|
32775
|
+
job.status = "dead";
|
|
32776
|
+
job.error = reason;
|
|
32777
|
+
queue._rejectJob(job.topic, job, reason, queue.getMaxRetries());
|
|
32555
32778
|
},
|
|
32556
32779
|
retry(delaySeconds) {
|
|
32557
32780
|
queue._retryJob(job.topic, job, delaySeconds);
|
|
@@ -33160,6 +33383,20 @@ var init_liteBackend = __esm({
|
|
|
33160
33383
|
* dead-letter file -- two spellings of the same intent that previously
|
|
33161
33384
|
* diverged.
|
|
33162
33385
|
*/
|
|
33386
|
+
/**
|
|
33387
|
+
* Reject a job permanently — dead-letter it NOW, no retry (ADR-0023).
|
|
33388
|
+
*
|
|
33389
|
+
* Unlike failJob(), which requeues until maxRetries is spent, rejectJob()
|
|
33390
|
+
* moves a known-poison job straight to the dead-letter store. attempts is
|
|
33391
|
+
* floored at maxRetries so deadLetters() (attempts >= maxRetries) returns it
|
|
33392
|
+
* and size("dead") agrees.
|
|
33393
|
+
*/
|
|
33394
|
+
rejectJob(queue, job, reason, maxRetries) {
|
|
33395
|
+
this.clearReservation(queue, job.id);
|
|
33396
|
+
job.attempts = Math.max((job.attempts || 0) + 1, maxRetries);
|
|
33397
|
+
job.error = reason;
|
|
33398
|
+
this.deadLetter(queue, job, reason);
|
|
33399
|
+
}
|
|
33163
33400
|
retryJob(queue, job, delaySeconds) {
|
|
33164
33401
|
this.clearReservation(queue, job.id);
|
|
33165
33402
|
try {
|
|
@@ -33381,7 +33618,7 @@ var init_queue = __esm({
|
|
|
33381
33618
|
size(status2 = "pending") {
|
|
33382
33619
|
const q = this.topic;
|
|
33383
33620
|
if (this.externalBackend) {
|
|
33384
|
-
return this.externalBackend.size(q);
|
|
33621
|
+
return this.externalBackend.size(q, status2);
|
|
33385
33622
|
}
|
|
33386
33623
|
return this.liteBackend.size(q, status2);
|
|
33387
33624
|
}
|
|
@@ -33649,6 +33886,17 @@ var init_queue = __esm({
|
|
|
33649
33886
|
}
|
|
33650
33887
|
this.liteBackend.failJob(queue, job, error2, maxRetries, this._retryBackoff);
|
|
33651
33888
|
}
|
|
33889
|
+
/**
|
|
33890
|
+
* Dead-letter a job immediately — no retry (ADR-0023 reject()). Distinct from
|
|
33891
|
+
* _failJob, which retries until maxRetries is spent.
|
|
33892
|
+
*/
|
|
33893
|
+
_rejectJob(queue, job, reason, maxRetries) {
|
|
33894
|
+
if (this.externalBackend?.reject) {
|
|
33895
|
+
this.externalBackend.reject(queue, job.id, reason, maxRetries);
|
|
33896
|
+
return;
|
|
33897
|
+
}
|
|
33898
|
+
this.liteBackend.rejectJob(queue, job, reason, maxRetries);
|
|
33899
|
+
}
|
|
33652
33900
|
/**
|
|
33653
33901
|
* Re-queue a job back to the main queue directory with incremented attempts.
|
|
33654
33902
|
*/
|
|
@@ -39360,13 +39608,20 @@ var sso_exports = {};
|
|
|
39360
39608
|
__export(sso_exports, {
|
|
39361
39609
|
SSO: () => Sso,
|
|
39362
39610
|
Sso: () => Sso,
|
|
39363
|
-
SsoError: () => SsoError
|
|
39611
|
+
SsoError: () => SsoError,
|
|
39612
|
+
liveSessionIdentity: () => liveSessionIdentity
|
|
39364
39613
|
});
|
|
39365
39614
|
import { createHash as createHash9, randomBytes as randomBytes7, timingSafeEqual as timingSafeEqual3 } from "node:crypto";
|
|
39615
|
+
function expiresAt(tokens) {
|
|
39616
|
+
const lifetime = Number(tokens.expires_in ?? 0);
|
|
39617
|
+
return lifetime > 0 ? Math.floor(Date.now() / 1e3) + lifetime : 0;
|
|
39618
|
+
}
|
|
39366
39619
|
var SsoError, Sso;
|
|
39367
39620
|
var init_sso = __esm({
|
|
39368
39621
|
"../core/src/sso.ts"() {
|
|
39369
39622
|
"use strict";
|
|
39623
|
+
init_ssoIdentity();
|
|
39624
|
+
init_ssoIdentity();
|
|
39370
39625
|
SsoError = class extends Error {
|
|
39371
39626
|
};
|
|
39372
39627
|
Sso = class _Sso {
|
|
@@ -39572,13 +39827,12 @@ var init_sso = __esm({
|
|
|
39572
39827
|
access_token: tokens.access_token,
|
|
39573
39828
|
refresh_token: tokens.refresh_token,
|
|
39574
39829
|
id_token: tokens.id_token,
|
|
39575
|
-
expires_at:
|
|
39830
|
+
expires_at: expiresAt(tokens)
|
|
39576
39831
|
});
|
|
39577
39832
|
return { identity, return_to: _Sso.safeReturn(pending.return_to) };
|
|
39578
39833
|
}
|
|
39579
39834
|
identity(requestOrSession) {
|
|
39580
|
-
const
|
|
39581
|
-
const identity = stored?.identity ?? null;
|
|
39835
|
+
const identity = liveSessionIdentity(this.session(requestOrSession)?.get(_Sso.SESSION_KEY));
|
|
39582
39836
|
if (identity && requestOrSession?.session) requestOrSession.user = identity;
|
|
39583
39837
|
return identity;
|
|
39584
39838
|
}
|
|
@@ -39605,7 +39859,7 @@ var init_sso = __esm({
|
|
|
39605
39859
|
access_token: tokens.access_token,
|
|
39606
39860
|
refresh_token: tokens.refresh_token ?? stored.refresh_token,
|
|
39607
39861
|
id_token: tokens.id_token ?? stored.id_token,
|
|
39608
|
-
expires_at:
|
|
39862
|
+
expires_at: expiresAt(tokens)
|
|
39609
39863
|
});
|
|
39610
39864
|
return identity;
|
|
39611
39865
|
} catch (error2) {
|
|
@@ -39747,12 +40001,12 @@ function bannerSurfaceLines(port, opts) {
|
|
|
39747
40001
|
];
|
|
39748
40002
|
}
|
|
39749
40003
|
function swaggerAdvertised() {
|
|
39750
|
-
const
|
|
40004
|
+
const TRUTHY3 = ["true", "1", "yes", "on"];
|
|
39751
40005
|
const raw = (process.env.TINA4_SWAGGER_ENABLED ?? "").trim().toLowerCase();
|
|
39752
40006
|
if (raw === "") {
|
|
39753
|
-
return
|
|
40007
|
+
return TRUTHY3.includes((process.env.TINA4_DEBUG ?? "").trim().toLowerCase());
|
|
39754
40008
|
}
|
|
39755
|
-
return
|
|
40009
|
+
return TRUTHY3.includes(raw);
|
|
39756
40010
|
}
|
|
39757
40011
|
async function autoMigrateOnStartup(migrationDir = "migrations", base = process.cwd()) {
|
|
39758
40012
|
const dir = resolve20(base, migrationDir);
|
|
@@ -40671,8 +40925,9 @@ function wireLocaleGlobal(frondEngine, base) {
|
|
|
40671
40925
|
async function startServer(config) {
|
|
40672
40926
|
loadEnv(".env.local");
|
|
40673
40927
|
loadEnv();
|
|
40674
|
-
const { ensureDevSecret: ensureDevSecret2 } = await Promise.resolve().then(() => (init_auth(), auth_exports));
|
|
40928
|
+
const { ensureDevSecret: ensureDevSecret2, requireBootSecret: requireBootSecret2 } = await Promise.resolve().then(() => (init_auth(), auth_exports));
|
|
40675
40929
|
ensureDevSecret2();
|
|
40930
|
+
requireBootSecret2();
|
|
40676
40931
|
_checkLegacyEnvVars();
|
|
40677
40932
|
const resolved = resolvePortAndHost(config);
|
|
40678
40933
|
const host = resolved.host;
|
|
@@ -42337,6 +42592,178 @@ var init_service = __esm({
|
|
|
42337
42592
|
}
|
|
42338
42593
|
});
|
|
42339
42594
|
|
|
42595
|
+
// ../core/src/ssrf.ts
|
|
42596
|
+
import { isIP } from "node:net";
|
|
42597
|
+
import { lookup } from "node:dns/promises";
|
|
42598
|
+
function v4ToBytes(ip) {
|
|
42599
|
+
const parts = ip.split(".");
|
|
42600
|
+
if (parts.length !== 4) return null;
|
|
42601
|
+
const bytes = new Uint8Array(4);
|
|
42602
|
+
for (let i = 0; i < 4; i++) {
|
|
42603
|
+
const n = Number(parts[i]);
|
|
42604
|
+
if (!Number.isInteger(n) || n < 0 || n > 255 || !/^\d+$/.test(parts[i])) return null;
|
|
42605
|
+
bytes[i] = n;
|
|
42606
|
+
}
|
|
42607
|
+
return bytes;
|
|
42608
|
+
}
|
|
42609
|
+
function v6ToBytes(input) {
|
|
42610
|
+
let ip = input.split("%")[0];
|
|
42611
|
+
const lastColon = ip.lastIndexOf(":");
|
|
42612
|
+
const tail = ip.slice(lastColon + 1);
|
|
42613
|
+
if (tail.includes(".")) {
|
|
42614
|
+
const v4 = v4ToBytes(tail);
|
|
42615
|
+
if (!v4) return null;
|
|
42616
|
+
const hextetA = (v4[0] << 8 | v4[1]).toString(16);
|
|
42617
|
+
const hextetB = (v4[2] << 8 | v4[3]).toString(16);
|
|
42618
|
+
ip = ip.slice(0, lastColon + 1) + hextetA + ":" + hextetB;
|
|
42619
|
+
}
|
|
42620
|
+
const halves = ip.split("::");
|
|
42621
|
+
if (halves.length > 2) return null;
|
|
42622
|
+
const head = halves[0] ? halves[0].split(":") : [];
|
|
42623
|
+
const back = halves.length === 2 ? halves[1] ? halves[1].split(":") : [] : [];
|
|
42624
|
+
let groups;
|
|
42625
|
+
if (halves.length === 1) {
|
|
42626
|
+
if (head.length !== 8) return null;
|
|
42627
|
+
groups = head;
|
|
42628
|
+
} else {
|
|
42629
|
+
const missing = 8 - (head.length + back.length);
|
|
42630
|
+
if (missing < 0) return null;
|
|
42631
|
+
groups = [...head, ...Array(missing).fill("0"), ...back];
|
|
42632
|
+
}
|
|
42633
|
+
if (groups.length !== 8) return null;
|
|
42634
|
+
const bytes = new Uint8Array(16);
|
|
42635
|
+
for (let i = 0; i < 8; i++) {
|
|
42636
|
+
if (!/^[0-9a-fA-F]{1,4}$/.test(groups[i])) return null;
|
|
42637
|
+
const g = parseInt(groups[i], 16);
|
|
42638
|
+
bytes[i * 2] = g >> 8 & 255;
|
|
42639
|
+
bytes[i * 2 + 1] = g & 255;
|
|
42640
|
+
}
|
|
42641
|
+
return bytes;
|
|
42642
|
+
}
|
|
42643
|
+
function ipToBytes(ip) {
|
|
42644
|
+
const cleaned = ip.split("%")[0];
|
|
42645
|
+
const family = isIP(cleaned);
|
|
42646
|
+
if (family === 4) return v4ToBytes(cleaned);
|
|
42647
|
+
if (family === 6) {
|
|
42648
|
+
const bytes = v6ToBytes(cleaned);
|
|
42649
|
+
if (!bytes) return null;
|
|
42650
|
+
const isMapped = bytes.subarray(0, 10).every((b) => b === 0) && bytes[10] === 255 && bytes[11] === 255;
|
|
42651
|
+
return isMapped ? bytes.subarray(12) : bytes;
|
|
42652
|
+
}
|
|
42653
|
+
return null;
|
|
42654
|
+
}
|
|
42655
|
+
function parseCidr(cidr) {
|
|
42656
|
+
const [netStr, bitsStr] = cidr.split("/");
|
|
42657
|
+
const net4 = ipToBytes(netStr);
|
|
42658
|
+
if (!net4) return null;
|
|
42659
|
+
return { net: net4, bits: Number(bitsStr) };
|
|
42660
|
+
}
|
|
42661
|
+
function inCidr(packed, cidr) {
|
|
42662
|
+
if (packed.length !== cidr.net.length) return false;
|
|
42663
|
+
const whole = Math.floor(cidr.bits / 8);
|
|
42664
|
+
const remainder = cidr.bits % 8;
|
|
42665
|
+
for (let i = 0; i < whole; i++) {
|
|
42666
|
+
if (packed[i] !== cidr.net[i]) return false;
|
|
42667
|
+
}
|
|
42668
|
+
if (remainder === 0) return true;
|
|
42669
|
+
const mask = 255 << 8 - remainder & 255;
|
|
42670
|
+
return (packed[whole] & mask) === (cidr.net[whole] & mask);
|
|
42671
|
+
}
|
|
42672
|
+
function allowPrivateRequests() {
|
|
42673
|
+
return TRUTHY2.has((process.env[ALLOW_PRIVATE_ENV] ?? "").trim().toLowerCase());
|
|
42674
|
+
}
|
|
42675
|
+
function isBlockedAddress(ip) {
|
|
42676
|
+
const packed = ipToBytes(ip);
|
|
42677
|
+
if (!packed) return true;
|
|
42678
|
+
return BLOCKED.some((cidr) => inCidr(packed, cidr));
|
|
42679
|
+
}
|
|
42680
|
+
function matchesAllowList(host, port, resolved, allowHosts) {
|
|
42681
|
+
const hostLower = host.toLowerCase();
|
|
42682
|
+
for (const raw of allowHosts ?? []) {
|
|
42683
|
+
const entry = String(raw).trim().toLowerCase();
|
|
42684
|
+
if (!entry) continue;
|
|
42685
|
+
if (entry === hostLower || entry === `${hostLower}:${port}`) return true;
|
|
42686
|
+
if (entry.includes("/")) {
|
|
42687
|
+
const cidr = parseCidr(entry);
|
|
42688
|
+
if (cidr && resolved.some((ip) => {
|
|
42689
|
+
const packed = ipToBytes(ip);
|
|
42690
|
+
return packed !== null && inCidr(packed, cidr);
|
|
42691
|
+
})) {
|
|
42692
|
+
return true;
|
|
42693
|
+
}
|
|
42694
|
+
}
|
|
42695
|
+
}
|
|
42696
|
+
return false;
|
|
42697
|
+
}
|
|
42698
|
+
function logBlock(host, ip) {
|
|
42699
|
+
void Promise.resolve().then(() => (init_logger(), logger_exports)).then((mod) => mod.Log?.warning?.(`SSRF guard blocked outbound request to ${host} (${ip})`)).catch(() => void 0);
|
|
42700
|
+
}
|
|
42701
|
+
async function guardUrl(url, allowHosts) {
|
|
42702
|
+
let parsed;
|
|
42703
|
+
try {
|
|
42704
|
+
parsed = new URL(url);
|
|
42705
|
+
} catch {
|
|
42706
|
+
throw new SsrfError(`Blocked request: '${url}' is not a valid URL`);
|
|
42707
|
+
}
|
|
42708
|
+
const scheme = parsed.protocol.replace(/:$/, "").toLowerCase();
|
|
42709
|
+
if (scheme !== "http" && scheme !== "https") {
|
|
42710
|
+
throw new SsrfError(`Blocked request: URL scheme '${scheme || "(none)"}' is not http or https`);
|
|
42711
|
+
}
|
|
42712
|
+
const host = parsed.hostname.replace(/^\[|\]$/g, "");
|
|
42713
|
+
if (!host) {
|
|
42714
|
+
throw new SsrfError("Blocked request: URL has no host");
|
|
42715
|
+
}
|
|
42716
|
+
const port = parsed.port ? Number(parsed.port) : scheme === "https" ? 443 : 80;
|
|
42717
|
+
if (allowPrivateRequests()) return;
|
|
42718
|
+
let resolved;
|
|
42719
|
+
try {
|
|
42720
|
+
const results = await lookup(host, { all: true });
|
|
42721
|
+
resolved = results.map((r) => r.address);
|
|
42722
|
+
} catch {
|
|
42723
|
+
throw new SsrfError(`Blocked request to ${host}: cannot resolve host`);
|
|
42724
|
+
}
|
|
42725
|
+
if (resolved.length === 0) {
|
|
42726
|
+
throw new SsrfError(`Blocked request to ${host}: cannot resolve host`);
|
|
42727
|
+
}
|
|
42728
|
+
if (matchesAllowList(host, port, resolved, allowHosts)) return;
|
|
42729
|
+
for (const ip of resolved) {
|
|
42730
|
+
if (isBlockedAddress(ip)) {
|
|
42731
|
+
logBlock(host, ip);
|
|
42732
|
+
throw new SsrfError(
|
|
42733
|
+
`Blocked request to private/internal address ${ip} (host ${host}): set ${ALLOW_PRIVATE_ENV}=true to allow, or pass an allow-list.`
|
|
42734
|
+
);
|
|
42735
|
+
}
|
|
42736
|
+
}
|
|
42737
|
+
}
|
|
42738
|
+
var ALLOW_PRIVATE_ENV, TRUTHY2, SsrfError, BLOCKED_CIDRS, BLOCKED;
|
|
42739
|
+
var init_ssrf = __esm({
|
|
42740
|
+
"../core/src/ssrf.ts"() {
|
|
42741
|
+
"use strict";
|
|
42742
|
+
ALLOW_PRIVATE_ENV = "TINA4_ALLOW_PRIVATE_REQUESTS";
|
|
42743
|
+
TRUTHY2 = /* @__PURE__ */ new Set(["1", "true", "yes", "on"]);
|
|
42744
|
+
SsrfError = class extends Error {
|
|
42745
|
+
constructor(message) {
|
|
42746
|
+
super(message);
|
|
42747
|
+
this.name = "SsrfError";
|
|
42748
|
+
}
|
|
42749
|
+
};
|
|
42750
|
+
BLOCKED_CIDRS = [
|
|
42751
|
+
"0.0.0.0/8",
|
|
42752
|
+
"10.0.0.0/8",
|
|
42753
|
+
"127.0.0.0/8",
|
|
42754
|
+
"169.254.0.0/16",
|
|
42755
|
+
"172.16.0.0/12",
|
|
42756
|
+
"192.168.0.0/16",
|
|
42757
|
+
"100.64.0.0/10",
|
|
42758
|
+
"::1/128",
|
|
42759
|
+
"::/128",
|
|
42760
|
+
"fc00::/7",
|
|
42761
|
+
"fe80::/10"
|
|
42762
|
+
];
|
|
42763
|
+
BLOCKED = BLOCKED_CIDRS.map(parseCidr).filter((c) => c !== null);
|
|
42764
|
+
}
|
|
42765
|
+
});
|
|
42766
|
+
|
|
42340
42767
|
// ../core/src/api.ts
|
|
42341
42768
|
import http from "node:http";
|
|
42342
42769
|
import https from "node:https";
|
|
@@ -42493,6 +42920,7 @@ var init_api = __esm({
|
|
|
42493
42920
|
"../core/src/api.ts"() {
|
|
42494
42921
|
"use strict";
|
|
42495
42922
|
init_version();
|
|
42923
|
+
init_ssrf();
|
|
42496
42924
|
ApiStreamError = class extends Error {
|
|
42497
42925
|
constructor(message, status2 = null) {
|
|
42498
42926
|
super(message);
|
|
@@ -42547,6 +42975,7 @@ var init_api = __esm({
|
|
|
42547
42975
|
transportFn;
|
|
42548
42976
|
cookiesEnabled;
|
|
42549
42977
|
cookies;
|
|
42978
|
+
allowHosts;
|
|
42550
42979
|
/**
|
|
42551
42980
|
* Construct an Api client.
|
|
42552
42981
|
*
|
|
@@ -42586,6 +43015,7 @@ var init_api = __esm({
|
|
|
42586
43015
|
this.transportFn = void 0;
|
|
42587
43016
|
this.cookiesEnabled = false;
|
|
42588
43017
|
this.cookies = {};
|
|
43018
|
+
this.allowHosts = [];
|
|
42589
43019
|
if (typeof authHeaderOrOptions === "object" && authHeaderOrOptions !== null) {
|
|
42590
43020
|
const opts = authHeaderOrOptions;
|
|
42591
43021
|
this.authHeader = opts.authHeader ?? "";
|
|
@@ -42595,6 +43025,7 @@ var init_api = __esm({
|
|
|
42595
43025
|
this.retryBackoff = opts.retryBackoff ?? 0.5;
|
|
42596
43026
|
this.transportFn = opts.transport;
|
|
42597
43027
|
this.cookiesEnabled = opts.cookies ?? false;
|
|
43028
|
+
this.allowHosts = opts.allowHosts ?? [];
|
|
42598
43029
|
if (opts.bearerToken != null) {
|
|
42599
43030
|
this.setBearerToken(opts.bearerToken);
|
|
42600
43031
|
} else if (opts.username != null && opts.password != null) {
|
|
@@ -42908,7 +43339,12 @@ var init_api = __esm({
|
|
|
42908
43339
|
* body-phase timeout is applied by the caller (streamBytes) via
|
|
42909
43340
|
* `res.destroy()`.
|
|
42910
43341
|
*/
|
|
42911
|
-
openStreamRequest(method, url, headers, data, connectSec) {
|
|
43342
|
+
async openStreamRequest(method, url, headers, data, connectSec) {
|
|
43343
|
+
try {
|
|
43344
|
+
await guardUrl(url, this.allowHosts);
|
|
43345
|
+
} catch (err) {
|
|
43346
|
+
throw new ApiStreamError(err instanceof Error ? err.message : String(err), null);
|
|
43347
|
+
}
|
|
42912
43348
|
return new Promise((resolve29, reject) => {
|
|
42913
43349
|
let parsed;
|
|
42914
43350
|
try {
|
|
@@ -42967,10 +43403,11 @@ var init_api = __esm({
|
|
|
42967
43403
|
*/
|
|
42968
43404
|
buildRequest(method, contentType, body, extraHeaders, targetUrl) {
|
|
42969
43405
|
const headers = { "User-Agent": `Tina4/${TINA4_VERSION}`, ...this.headers };
|
|
42970
|
-
|
|
43406
|
+
const sameOriginAsBase = targetUrl === void 0 || sameOrigin(targetUrl, this.baseUrl);
|
|
43407
|
+
if (this.authHeader && sameOriginAsBase) {
|
|
42971
43408
|
headers["Authorization"] = this.authHeader;
|
|
42972
43409
|
}
|
|
42973
|
-
if (this.cookiesEnabled) {
|
|
43410
|
+
if (this.cookiesEnabled && sameOriginAsBase) {
|
|
42974
43411
|
const cookieHeader = this.cookieHeader();
|
|
42975
43412
|
if (cookieHeader) {
|
|
42976
43413
|
headers["Cookie"] = cookieHeader;
|
|
@@ -43071,7 +43508,12 @@ var init_api = __esm({
|
|
|
43071
43508
|
* bearer token / session cookie never leaks to a host you didn't
|
|
43072
43509
|
* authenticate to.
|
|
43073
43510
|
*/
|
|
43074
|
-
performRequest(method, url, headers, data, redirectsLeft) {
|
|
43511
|
+
async performRequest(method, url, headers, data, redirectsLeft) {
|
|
43512
|
+
try {
|
|
43513
|
+
await guardUrl(url, this.allowHosts);
|
|
43514
|
+
} catch (err) {
|
|
43515
|
+
return { kind: "error", error: err instanceof Error ? err.message : String(err) };
|
|
43516
|
+
}
|
|
43075
43517
|
return new Promise((resolve29) => {
|
|
43076
43518
|
let parsed;
|
|
43077
43519
|
try {
|
|
@@ -45829,6 +46271,7 @@ var CURVE, RECORD_SIZE, MAX_PAYLOAD, PushError, Push;
|
|
|
45829
46271
|
var init_push = __esm({
|
|
45830
46272
|
"../core/src/push.ts"() {
|
|
45831
46273
|
"use strict";
|
|
46274
|
+
init_ssrf();
|
|
45832
46275
|
CURVE = "prime256v1";
|
|
45833
46276
|
RECORD_SIZE = 4096;
|
|
45834
46277
|
MAX_PAYLOAD = RECORD_SIZE - 17;
|
|
@@ -45903,10 +46346,19 @@ var init_push = __esm({
|
|
|
45903
46346
|
return { rawPublic, rawPrivate };
|
|
45904
46347
|
}
|
|
45905
46348
|
async deliver(endpoint, config, keys, body) {
|
|
46349
|
+
try {
|
|
46350
|
+
await guardUrl(endpoint.toString(), this.options.allowHosts);
|
|
46351
|
+
} catch (error2) {
|
|
46352
|
+
if (error2 instanceof SsrfError) throw new PushError(error2.message);
|
|
46353
|
+
throw error2;
|
|
46354
|
+
}
|
|
45906
46355
|
let response;
|
|
45907
46356
|
try {
|
|
45908
46357
|
response = await fetch(endpoint, {
|
|
45909
46358
|
method: "POST",
|
|
46359
|
+
// A real push service answers the POST directly; a redirect from a push
|
|
46360
|
+
// endpoint is not followed to a private address (ADR-0084).
|
|
46361
|
+
redirect: "manual",
|
|
45910
46362
|
headers: {
|
|
45911
46363
|
Authorization: `vapid t=${vapidToken(endpoint.toString(), config.subject, keys.rawPrivate, keys.rawPublic)}, k=${config.publicKey}`,
|
|
45912
46364
|
"Content-Encoding": "aes128gcm",
|
|
@@ -47993,6 +48445,7 @@ var init_testClient = __esm({
|
|
|
47993
48445
|
reqHeaders["content-length"] = String(Buffer.byteLength(rawBody));
|
|
47994
48446
|
}
|
|
47995
48447
|
const socket = new Socket();
|
|
48448
|
+
Object.defineProperty(socket, "remoteAddress", { value: "127.0.0.1" });
|
|
47996
48449
|
const rawReq = new IncomingMessage2(socket);
|
|
47997
48450
|
rawReq.method = method.toUpperCase();
|
|
47998
48451
|
rawReq.url = path10;
|
|
@@ -48365,6 +48818,7 @@ var init_validator = __esm({
|
|
|
48365
48818
|
var src_exports3 = {};
|
|
48366
48819
|
__export(src_exports3, {
|
|
48367
48820
|
AI_TOOLS: () => AI_TOOLS,
|
|
48821
|
+
ALLOW_PRIVATE_ENV: () => ALLOW_PRIVATE_ENV,
|
|
48368
48822
|
APPLICATION_FORM: () => APPLICATION_FORM,
|
|
48369
48823
|
APPLICATION_JSON: () => APPLICATION_JSON,
|
|
48370
48824
|
APPLICATION_OCTET: () => APPLICATION_OCTET,
|
|
@@ -48472,6 +48926,7 @@ __export(src_exports3, {
|
|
|
48472
48926
|
Session: () => Session,
|
|
48473
48927
|
Sso: () => Sso,
|
|
48474
48928
|
SsoError: () => SsoError,
|
|
48929
|
+
SsrfError: () => SsrfError,
|
|
48475
48930
|
TAKEOVER_KILLED: () => TAKEOVER_KILLED,
|
|
48476
48931
|
TAKEOVER_NOTHING: () => TAKEOVER_NOTHING,
|
|
48477
48932
|
TAKEOVER_REFUSALS: () => TAKEOVER_REFUSALS,
|
|
@@ -48505,6 +48960,7 @@ __export(src_exports3, {
|
|
|
48505
48960
|
addHtmlHelpers: () => addHtmlHelpers,
|
|
48506
48961
|
algorithmAvailable: () => algorithmAvailable,
|
|
48507
48962
|
allEnv: () => allEnv,
|
|
48963
|
+
allowPrivateRequests: () => allowPrivateRequests,
|
|
48508
48964
|
any: () => any,
|
|
48509
48965
|
attachCsrfFromEnv: () => attachCsrfFromEnv,
|
|
48510
48966
|
authMiddleware: () => authMiddleware,
|
|
@@ -48570,6 +49026,7 @@ __export(src_exports3, {
|
|
|
48570
49026
|
graphqlAutoSchemaEnabled: () => graphqlAutoSchemaEnabled,
|
|
48571
49027
|
graphqlEndpoint: () => graphqlEndpoint,
|
|
48572
49028
|
graphqlMaxDepth: () => graphqlMaxDepth,
|
|
49029
|
+
guardUrl: () => guardUrl,
|
|
48573
49030
|
handle: () => handle,
|
|
48574
49031
|
handleFeedbackTurn: () => handleFeedbackTurn,
|
|
48575
49032
|
handleFeedbackWidgetJs: () => handleFeedbackWidgetJs,
|
|
@@ -48583,6 +49040,7 @@ __export(src_exports3, {
|
|
|
48583
49040
|
installAll: () => installAll,
|
|
48584
49041
|
installSelected: () => installSelected,
|
|
48585
49042
|
isBannerSuppressed: () => isBannerSuppressed,
|
|
49043
|
+
isBlockedAddress: () => isBlockedAddress,
|
|
48586
49044
|
isDebugMode: () => isDebugMode,
|
|
48587
49045
|
isDev: () => isDev,
|
|
48588
49046
|
isInstalled: () => isInstalled,
|
|
@@ -48702,6 +49160,7 @@ var init_src3 = __esm({
|
|
|
48702
49160
|
init_service();
|
|
48703
49161
|
init_cache();
|
|
48704
49162
|
init_api();
|
|
49163
|
+
init_ssrf();
|
|
48705
49164
|
init_context();
|
|
48706
49165
|
init_events();
|
|
48707
49166
|
init_devAdmin();
|