tina4-nodejs 3.13.138 → 3.13.140
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 +683 -329
- package/packages/cli/src/commands/generate.ts +7 -13
- package/packages/core/dist/index.js +688 -329
- 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 +675 -320
- package/packages/orm/src/adapters/firebird.ts +14 -45
- package/packages/orm/src/adapters/mssql.ts +21 -58
- package/packages/orm/src/adapters/mysql.ts +10 -49
- package/packages/orm/src/adapters/odbc.ts +11 -42
- package/packages/orm/src/adapters/postgres.ts +20 -60
- package/packages/orm/src/adapters/sqlCrud.ts +178 -0
- package/packages/orm/src/adapters/sqlite.ts +46 -66
- package/packages/orm/src/index.ts +1 -1
- 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/types/orm/src/adapters/sqlCrud.d.ts +90 -0
- package/types/orm/src/adapters/sqlite.d.ts +17 -0
- package/types/orm/src/index.d.ts +1 -1
|
@@ -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"],
|
|
@@ -3540,14 +3574,72 @@ var init_cachedDatabase = __esm({
|
|
|
3540
3574
|
}
|
|
3541
3575
|
});
|
|
3542
3576
|
|
|
3577
|
+
// ../orm/src/adapters/sqlCrud.ts
|
|
3578
|
+
function buildInsertRow(dialect, table2, data, suffix = "", options = {}) {
|
|
3579
|
+
const keys = Object.keys(data);
|
|
3580
|
+
const sql = buildInsert(dialect, table2, keys, suffix, options.startAt ?? 1);
|
|
3581
|
+
return { sql, values: Object.values(data) };
|
|
3582
|
+
}
|
|
3583
|
+
function buildInsertRows(dialect, table2, rows) {
|
|
3584
|
+
if (rows.length === 0) return null;
|
|
3585
|
+
const keys = Object.keys(rows[0]);
|
|
3586
|
+
const sql = buildInsert(dialect, table2, keys);
|
|
3587
|
+
const paramsList = rows.map((row) => keys.map((k) => row[k]));
|
|
3588
|
+
return { sql, paramsList };
|
|
3589
|
+
}
|
|
3590
|
+
function buildUpdate(dialect, table2, data, filter, params, options = {}) {
|
|
3591
|
+
const startAt = options.startAt ?? 1;
|
|
3592
|
+
const convertFragment = options.convertFragment ?? identityFragment;
|
|
3593
|
+
const dataKeys = Object.keys(data);
|
|
3594
|
+
const setClauses = buildSetClause(dialect, dataKeys, startAt);
|
|
3595
|
+
const whereStart = startAt + dataKeys.length;
|
|
3596
|
+
if (typeof filter === "string") {
|
|
3597
|
+
const where = filter ? ` WHERE ${convertFragment(filter, whereStart)}` : "";
|
|
3598
|
+
return {
|
|
3599
|
+
sql: `UPDATE ${dialect.quote(table2)} SET ${setClauses}${where}`,
|
|
3600
|
+
values: [...Object.values(data), ...params ?? []]
|
|
3601
|
+
};
|
|
3602
|
+
}
|
|
3603
|
+
const whereClauses = buildWhereClause(dialect, Object.keys(filter), whereStart);
|
|
3604
|
+
return {
|
|
3605
|
+
sql: `UPDATE ${dialect.quote(table2)} SET ${setClauses} WHERE ${whereClauses}`,
|
|
3606
|
+
values: [...Object.values(data), ...Object.values(filter)]
|
|
3607
|
+
};
|
|
3608
|
+
}
|
|
3609
|
+
function buildDelete(dialect, table2, filter, params, options = {}) {
|
|
3610
|
+
const startAt = options.startAt ?? 1;
|
|
3611
|
+
const convertFragment = options.convertFragment ?? identityFragment;
|
|
3612
|
+
if (typeof filter === "string") {
|
|
3613
|
+
const where = filter ? ` WHERE ${convertFragment(filter, startAt)}` : "";
|
|
3614
|
+
return {
|
|
3615
|
+
sql: `DELETE FROM ${dialect.quote(table2)}${where}`,
|
|
3616
|
+
values: params ?? []
|
|
3617
|
+
};
|
|
3618
|
+
}
|
|
3619
|
+
const whereClauses = buildWhereClause(dialect, Object.keys(filter), startAt);
|
|
3620
|
+
return {
|
|
3621
|
+
sql: `DELETE FROM ${dialect.quote(table2)} WHERE ${whereClauses}`,
|
|
3622
|
+
values: Object.values(filter)
|
|
3623
|
+
};
|
|
3624
|
+
}
|
|
3625
|
+
var identityFragment;
|
|
3626
|
+
var init_sqlCrud = __esm({
|
|
3627
|
+
"../orm/src/adapters/sqlCrud.ts"() {
|
|
3628
|
+
"use strict";
|
|
3629
|
+
init_sqlDialect();
|
|
3630
|
+
identityFragment = (fragment) => fragment;
|
|
3631
|
+
}
|
|
3632
|
+
});
|
|
3633
|
+
|
|
3543
3634
|
// ../orm/src/adapters/sqlite.ts
|
|
3544
3635
|
var sqlite_exports = {};
|
|
3545
3636
|
__export(sqlite_exports, {
|
|
3546
|
-
SQLiteAdapter: () => SQLiteAdapter
|
|
3637
|
+
SQLiteAdapter: () => SQLiteAdapter,
|
|
3638
|
+
resolveSqlitePath: () => resolveSqlitePath
|
|
3547
3639
|
});
|
|
3548
3640
|
import { DatabaseSync } from "node:sqlite";
|
|
3549
3641
|
import { mkdirSync as mkdirSync2 } from "node:fs";
|
|
3550
|
-
import { dirname as dirname3, isAbsolute as isAbsolute2, join as join4, resolve as resolve2 } from "node:path";
|
|
3642
|
+
import { dirname as dirname3, isAbsolute as isAbsolute2, join as join4, resolve as resolve2, sep } from "node:path";
|
|
3551
3643
|
function isIdentifier(str) {
|
|
3552
3644
|
return /^[A-Za-z_][A-Za-z0-9_]*$/.test(str);
|
|
3553
3645
|
}
|
|
@@ -3578,17 +3670,20 @@ function sqliteVersionAtLeast(major, minor, patch2) {
|
|
|
3578
3670
|
}
|
|
3579
3671
|
function resolveSqlitePath(dbPath) {
|
|
3580
3672
|
if (dbPath === ":memory:") return dbPath;
|
|
3581
|
-
|
|
3582
|
-
if (
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3673
|
+
const isWindowsAbs = /^[A-Za-z]:[\\/]/.test(dbPath);
|
|
3674
|
+
if (isAbsolute2(dbPath) || isWindowsAbs) {
|
|
3675
|
+
return dbPath;
|
|
3676
|
+
}
|
|
3677
|
+
const cwd = resolve2(process.cwd());
|
|
3678
|
+
const path10 = join4(cwd, dbPath);
|
|
3679
|
+
const parent = dirname3(path10);
|
|
3680
|
+
const absParent = resolve2(parent);
|
|
3681
|
+
if (absParent !== cwd && !absParent.startsWith(cwd + sep)) {
|
|
3682
|
+
throw new Error(
|
|
3683
|
+
`SQLite path "${dbPath}" resolves outside the project directory: "${absParent}" is not within "${cwd}". Tina4 refuses to create directories outside the project (ADR-0086). Use an absolute path for a database that lives outside the project.`
|
|
3684
|
+
);
|
|
3591
3685
|
}
|
|
3686
|
+
mkdirSync2(parent, { recursive: true, mode: 509 });
|
|
3592
3687
|
return path10;
|
|
3593
3688
|
}
|
|
3594
3689
|
function fieldTypeToSQLite(def) {
|
|
@@ -3624,6 +3719,7 @@ var init_sqlite = __esm({
|
|
|
3624
3719
|
"../orm/src/adapters/sqlite.ts"() {
|
|
3625
3720
|
"use strict";
|
|
3626
3721
|
init_sqlDialect();
|
|
3722
|
+
init_sqlCrud();
|
|
3627
3723
|
init_sqlTranslator();
|
|
3628
3724
|
_sqliteVersionInfo = null;
|
|
3629
3725
|
SQLiteAdapter = class {
|
|
@@ -3744,16 +3840,12 @@ var init_sqlite = __esm({
|
|
|
3744
3840
|
}
|
|
3745
3841
|
insert(table2, data) {
|
|
3746
3842
|
if (Array.isArray(data)) {
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
const
|
|
3750
|
-
const paramsList = data.map((row) => keys2.map((k) => row[k]));
|
|
3751
|
-
const result = this.executeMany(sql2, paramsList);
|
|
3843
|
+
const batch = buildInsertRows(ANSI_DIALECT, table2, data);
|
|
3844
|
+
if (!batch) return { success: true, affectedRows: 0 };
|
|
3845
|
+
const result = this.executeMany(batch.sql, batch.paramsList);
|
|
3752
3846
|
return { success: true, affectedRows: result.affectedRows, lastId: result.lastId };
|
|
3753
3847
|
}
|
|
3754
|
-
const
|
|
3755
|
-
const sql = buildInsert(ANSI_DIALECT, table2, keys);
|
|
3756
|
-
const values = Object.values(data);
|
|
3848
|
+
const { sql, values } = buildInsertRow(ANSI_DIALECT, table2, data);
|
|
3757
3849
|
try {
|
|
3758
3850
|
const result = this.db.prepare(sql).run(...toSqlParams(values));
|
|
3759
3851
|
this._lastInsertId = result.lastInsertRowid;
|
|
@@ -3763,21 +3855,7 @@ var init_sqlite = __esm({
|
|
|
3763
3855
|
}
|
|
3764
3856
|
}
|
|
3765
3857
|
update(table2, data, filter, params) {
|
|
3766
|
-
const
|
|
3767
|
-
if (typeof filter === "string") {
|
|
3768
|
-
const where = filter ? ` WHERE ${filter}` : "";
|
|
3769
|
-
const sql2 = `UPDATE ${ANSI_DIALECT.quote(table2)} SET ${setClauses}${where}`;
|
|
3770
|
-
const values2 = [...Object.values(data), ...params ?? []];
|
|
3771
|
-
try {
|
|
3772
|
-
const result = this.db.prepare(sql2).run(...toSqlParams(values2));
|
|
3773
|
-
return { success: true, affectedRows: Number(result.changes) };
|
|
3774
|
-
} catch (e) {
|
|
3775
|
-
return { success: false, affectedRows: 0, error: e.message };
|
|
3776
|
-
}
|
|
3777
|
-
}
|
|
3778
|
-
const whereClauses = buildWhereClause(ANSI_DIALECT, Object.keys(filter));
|
|
3779
|
-
const sql = `UPDATE ${ANSI_DIALECT.quote(table2)} SET ${setClauses} WHERE ${whereClauses}`;
|
|
3780
|
-
const values = [...Object.values(data), ...Object.values(filter)];
|
|
3858
|
+
const { sql, values } = buildUpdate(ANSI_DIALECT, table2, data, filter, params);
|
|
3781
3859
|
try {
|
|
3782
3860
|
const result = this.db.prepare(sql).run(...toSqlParams(values));
|
|
3783
3861
|
return { success: true, affectedRows: Number(result.changes) };
|
|
@@ -3794,18 +3872,7 @@ var init_sqlite = __esm({
|
|
|
3794
3872
|
}
|
|
3795
3873
|
return { success: true, affectedRows: totalAffected };
|
|
3796
3874
|
}
|
|
3797
|
-
|
|
3798
|
-
const sql2 = filter ? `DELETE FROM "${table2}" WHERE ${filter}` : `DELETE FROM "${table2}"`;
|
|
3799
|
-
try {
|
|
3800
|
-
const result = this.db.prepare(sql2).run(...toSqlParams(params ?? []));
|
|
3801
|
-
return { success: true, affectedRows: Number(result.changes) };
|
|
3802
|
-
} catch (e) {
|
|
3803
|
-
return { success: false, affectedRows: 0, error: e.message };
|
|
3804
|
-
}
|
|
3805
|
-
}
|
|
3806
|
-
const whereClauses = buildWhereClause(ANSI_DIALECT, Object.keys(filter));
|
|
3807
|
-
const sql = `DELETE FROM ${ANSI_DIALECT.quote(table2)} WHERE ${whereClauses}`;
|
|
3808
|
-
const values = Object.values(filter);
|
|
3875
|
+
const { sql, values } = buildDelete(ANSI_DIALECT, table2, filter, params);
|
|
3809
3876
|
try {
|
|
3810
3877
|
const result = this.db.prepare(sql).run(...toSqlParams(values));
|
|
3811
3878
|
return { success: true, affectedRows: Number(result.changes) };
|
|
@@ -4097,6 +4164,7 @@ var init_postgres = __esm({
|
|
|
4097
4164
|
"../orm/src/adapters/postgres.ts"() {
|
|
4098
4165
|
"use strict";
|
|
4099
4166
|
init_sqlDialect();
|
|
4167
|
+
init_sqlCrud();
|
|
4100
4168
|
init_sqlTranslator();
|
|
4101
4169
|
init_connectTimeout();
|
|
4102
4170
|
pg = null;
|
|
@@ -4298,21 +4366,17 @@ var init_postgres = __esm({
|
|
|
4298
4366
|
async insertAsync(table2, data) {
|
|
4299
4367
|
this.ensureConnected();
|
|
4300
4368
|
if (Array.isArray(data)) {
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
const sql2 = buildInsert(ANSI_DIALECT, table2, keys2);
|
|
4304
|
-
const paramsList = data.map((row) => keys2.map((k) => row[k]));
|
|
4369
|
+
const batch = buildInsertRows(ANSI_DIALECT, table2, data);
|
|
4370
|
+
if (!batch) return { success: true, affectedRows: 0 };
|
|
4305
4371
|
try {
|
|
4306
|
-
const result = await this.executeManyAsync(
|
|
4372
|
+
const result = await this.executeManyAsync(batch.sql, batch.paramsList);
|
|
4307
4373
|
if (result.lastId !== void 0) this._lastInsertId = result.lastId;
|
|
4308
4374
|
return { success: true, affectedRows: result.totalAffected, lastId: result.lastId };
|
|
4309
4375
|
} catch (e) {
|
|
4310
4376
|
return { success: false, affectedRows: 0, error: e.message };
|
|
4311
4377
|
}
|
|
4312
4378
|
}
|
|
4313
|
-
const
|
|
4314
|
-
const sql = buildInsert(POSTGRES_DIALECT, table2, keys, " RETURNING *");
|
|
4315
|
-
const values = Object.values(data);
|
|
4379
|
+
const { sql, values } = buildInsertRow(POSTGRES_DIALECT, table2, data, " RETURNING *");
|
|
4316
4380
|
try {
|
|
4317
4381
|
const result = await this.client.query(sql, values);
|
|
4318
4382
|
const insertedRow = result.rows[0];
|
|
@@ -4332,26 +4396,10 @@ var init_postgres = __esm({
|
|
|
4332
4396
|
}
|
|
4333
4397
|
async updateAsync(table2, data, filter, params) {
|
|
4334
4398
|
this.ensureConnected();
|
|
4335
|
-
const
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
if (typeof filter === "string") {
|
|
4340
|
-
const where = filter ? ` WHERE ${this.convertPlaceholders(filter, paramIndex)}` : "";
|
|
4341
|
-
const sql2 = `UPDATE ${POSTGRES_DIALECT.quote(table2)} SET ${setClauses}${where}`;
|
|
4342
|
-
const values2 = [...Object.values(data), ...params ?? []];
|
|
4343
|
-
try {
|
|
4344
|
-
const result = await this.client.query(sql2, values2);
|
|
4345
|
-
return { success: true, affectedRows: result.rowCount ?? 0 };
|
|
4346
|
-
} catch (e) {
|
|
4347
|
-
return { success: false, affectedRows: 0, error: e.message };
|
|
4348
|
-
}
|
|
4349
|
-
}
|
|
4350
|
-
const filterKeys = Object.keys(filter);
|
|
4351
|
-
const whereClauses = buildWhereClause(POSTGRES_DIALECT, filterKeys, paramIndex);
|
|
4352
|
-
paramIndex += filterKeys.length;
|
|
4353
|
-
const sql = `UPDATE ${POSTGRES_DIALECT.quote(table2)} SET ${setClauses} WHERE ${whereClauses}`;
|
|
4354
|
-
const values = [...Object.values(data), ...Object.values(filter)];
|
|
4399
|
+
const { sql, values } = buildUpdate(POSTGRES_DIALECT, table2, data, filter, params, {
|
|
4400
|
+
startAt: 1,
|
|
4401
|
+
convertFragment: (fragment, startAt) => this.convertPlaceholders(fragment, startAt)
|
|
4402
|
+
});
|
|
4355
4403
|
try {
|
|
4356
4404
|
const result = await this.client.query(sql, values);
|
|
4357
4405
|
return { success: true, affectedRows: result.rowCount ?? 0 };
|
|
@@ -4364,21 +4412,10 @@ var init_postgres = __esm({
|
|
|
4364
4412
|
}
|
|
4365
4413
|
async deleteAsync(table2, filter, params) {
|
|
4366
4414
|
this.ensureConnected();
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
return { success: true, affectedRows: result.rowCount ?? 0 };
|
|
4372
|
-
} catch (e) {
|
|
4373
|
-
return { success: false, affectedRows: 0, error: e.message };
|
|
4374
|
-
}
|
|
4375
|
-
}
|
|
4376
|
-
const filterKeys = Object.keys(filter);
|
|
4377
|
-
let paramIndex = 1;
|
|
4378
|
-
const whereClauses = buildWhereClause(POSTGRES_DIALECT, filterKeys, paramIndex);
|
|
4379
|
-
paramIndex += filterKeys.length;
|
|
4380
|
-
const sql = `DELETE FROM ${POSTGRES_DIALECT.quote(table2)} WHERE ${whereClauses}`;
|
|
4381
|
-
const values = Object.values(filter);
|
|
4415
|
+
const { sql, values } = buildDelete(POSTGRES_DIALECT, table2, filter, params, {
|
|
4416
|
+
startAt: 1,
|
|
4417
|
+
convertFragment: (fragment) => this.bindable(fragment, params)
|
|
4418
|
+
});
|
|
4382
4419
|
try {
|
|
4383
4420
|
const result = await this.client.query(sql, values);
|
|
4384
4421
|
return { success: true, affectedRows: result.rowCount ?? 0 };
|
|
@@ -4552,6 +4589,7 @@ var init_mysql = __esm({
|
|
|
4552
4589
|
"../orm/src/adapters/mysql.ts"() {
|
|
4553
4590
|
"use strict";
|
|
4554
4591
|
init_sqlDialect();
|
|
4592
|
+
init_sqlCrud();
|
|
4555
4593
|
init_sqlTranslator();
|
|
4556
4594
|
init_connectTimeout();
|
|
4557
4595
|
mysql2 = null;
|
|
@@ -4733,21 +4771,17 @@ var init_mysql = __esm({
|
|
|
4733
4771
|
async insertAsync(table2, data) {
|
|
4734
4772
|
this.ensureConnected();
|
|
4735
4773
|
if (Array.isArray(data)) {
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
const sql2 = buildInsert(MYSQL_DIALECT, table2, keys2);
|
|
4739
|
-
const paramsList = data.map((row) => keys2.map((k) => row[k]));
|
|
4774
|
+
const batch = buildInsertRows(MYSQL_DIALECT, table2, data);
|
|
4775
|
+
if (!batch) return { success: true, affectedRows: 0 };
|
|
4740
4776
|
try {
|
|
4741
|
-
const result = await this.executeManyAsync(
|
|
4777
|
+
const result = await this.executeManyAsync(batch.sql, batch.paramsList);
|
|
4742
4778
|
if (result.lastId !== void 0) this._lastInsertId = result.lastId;
|
|
4743
4779
|
return { success: true, affectedRows: result.totalAffected, lastId: result.lastId };
|
|
4744
4780
|
} catch (e) {
|
|
4745
4781
|
return { success: false, affectedRows: 0, error: e.message };
|
|
4746
4782
|
}
|
|
4747
4783
|
}
|
|
4748
|
-
const
|
|
4749
|
-
const sql = buildInsert(MYSQL_DIALECT, table2, keys);
|
|
4750
|
-
const values = Object.values(data);
|
|
4784
|
+
const { sql, values } = buildInsertRow(MYSQL_DIALECT, table2, data);
|
|
4751
4785
|
try {
|
|
4752
4786
|
const result = await this.queryPromise(sql, values);
|
|
4753
4787
|
this._lastInsertId = result.insertId ?? null;
|
|
@@ -4765,21 +4799,7 @@ var init_mysql = __esm({
|
|
|
4765
4799
|
}
|
|
4766
4800
|
async updateAsync(table2, data, filter, params) {
|
|
4767
4801
|
this.ensureConnected();
|
|
4768
|
-
const
|
|
4769
|
-
if (typeof filter === "string") {
|
|
4770
|
-
const where = filter ? ` WHERE ${filter}` : "";
|
|
4771
|
-
const sql2 = `UPDATE ${MYSQL_DIALECT.quote(table2)} SET ${setClauses}${where}`;
|
|
4772
|
-
const values2 = [...Object.values(data), ...params ?? []];
|
|
4773
|
-
try {
|
|
4774
|
-
const result = await this.queryPromise(sql2, values2);
|
|
4775
|
-
return { success: true, affectedRows: result.affectedRows ?? 0 };
|
|
4776
|
-
} catch (e) {
|
|
4777
|
-
return { success: false, affectedRows: 0, error: e.message };
|
|
4778
|
-
}
|
|
4779
|
-
}
|
|
4780
|
-
const whereClauses = buildWhereClause(MYSQL_DIALECT, Object.keys(filter));
|
|
4781
|
-
const sql = `UPDATE ${MYSQL_DIALECT.quote(table2)} SET ${setClauses} WHERE ${whereClauses}`;
|
|
4782
|
-
const values = [...Object.values(data), ...Object.values(filter)];
|
|
4802
|
+
const { sql, values } = buildUpdate(MYSQL_DIALECT, table2, data, filter, params);
|
|
4783
4803
|
try {
|
|
4784
4804
|
const result = await this.queryPromise(sql, values);
|
|
4785
4805
|
return { success: true, affectedRows: result.affectedRows ?? 0 };
|
|
@@ -4792,18 +4812,7 @@ var init_mysql = __esm({
|
|
|
4792
4812
|
}
|
|
4793
4813
|
async deleteAsync(table2, filter, params) {
|
|
4794
4814
|
this.ensureConnected();
|
|
4795
|
-
|
|
4796
|
-
const sql2 = filter ? `DELETE FROM \`${table2}\` WHERE ${filter}` : `DELETE FROM \`${table2}\``;
|
|
4797
|
-
try {
|
|
4798
|
-
const result = await this.queryPromise(sql2, params ?? []);
|
|
4799
|
-
return { success: true, affectedRows: result.affectedRows ?? 0 };
|
|
4800
|
-
} catch (e) {
|
|
4801
|
-
return { success: false, affectedRows: 0, error: e.message };
|
|
4802
|
-
}
|
|
4803
|
-
}
|
|
4804
|
-
const whereClauses = buildWhereClause(MYSQL_DIALECT, Object.keys(filter));
|
|
4805
|
-
const sql = `DELETE FROM ${MYSQL_DIALECT.quote(table2)} WHERE ${whereClauses}`;
|
|
4806
|
-
const values = Object.values(filter);
|
|
4815
|
+
const { sql, values } = buildDelete(MYSQL_DIALECT, table2, filter, params);
|
|
4807
4816
|
try {
|
|
4808
4817
|
const result = await this.queryPromise(sql, values);
|
|
4809
4818
|
return { success: true, affectedRows: result.affectedRows ?? 0 };
|
|
@@ -4955,6 +4964,7 @@ var init_mssql = __esm({
|
|
|
4955
4964
|
"../orm/src/adapters/mssql.ts"() {
|
|
4956
4965
|
"use strict";
|
|
4957
4966
|
init_sqlDialect();
|
|
4967
|
+
init_sqlCrud();
|
|
4958
4968
|
init_sqlTranslator();
|
|
4959
4969
|
init_connectTimeout();
|
|
4960
4970
|
tedious = null;
|
|
@@ -5211,21 +5221,17 @@ var init_mssql = __esm({
|
|
|
5211
5221
|
async insertAsync(table2, data) {
|
|
5212
5222
|
this.ensureConnected();
|
|
5213
5223
|
if (Array.isArray(data)) {
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
const sql2 = buildInsert({ quote: MSSQL_DIALECT.quote, marker: ANSI_DIALECT.marker }, table2, keys2);
|
|
5217
|
-
const paramsList = data.map((row) => keys2.map((k) => row[k]));
|
|
5224
|
+
const batch = buildInsertRows({ quote: MSSQL_DIALECT.quote, marker: ANSI_DIALECT.marker }, table2, data);
|
|
5225
|
+
if (!batch) return { success: true, affectedRows: 0 };
|
|
5218
5226
|
try {
|
|
5219
|
-
const result = await this.executeManyAsync(
|
|
5227
|
+
const result = await this.executeManyAsync(batch.sql, batch.paramsList);
|
|
5220
5228
|
if (result.lastId !== void 0) this._lastInsertId = result.lastId;
|
|
5221
5229
|
return { success: true, affectedRows: result.totalAffected, lastId: result.lastId };
|
|
5222
5230
|
} catch (e) {
|
|
5223
5231
|
return { success: false, affectedRows: 0, error: e.message };
|
|
5224
5232
|
}
|
|
5225
5233
|
}
|
|
5226
|
-
const
|
|
5227
|
-
const sql = buildInsert(MSSQL_DIALECT, table2, keys, "; SELECT SCOPE_IDENTITY() AS id", 0);
|
|
5228
|
-
const values = Object.values(data);
|
|
5234
|
+
const { sql, values } = buildInsertRow(MSSQL_DIALECT, table2, data, "; SELECT SCOPE_IDENTITY() AS id", { startAt: 0 });
|
|
5229
5235
|
try {
|
|
5230
5236
|
const result = await this.execSqlPromise(sql, values);
|
|
5231
5237
|
const id = result.rows[0]?.id ?? null;
|
|
@@ -5248,26 +5254,10 @@ var init_mssql = __esm({
|
|
|
5248
5254
|
}
|
|
5249
5255
|
async updateAsync(table2, data, filter, params) {
|
|
5250
5256
|
this.ensureConnected();
|
|
5251
|
-
const
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
if (typeof filter === "string") {
|
|
5256
|
-
const where = filter ? ` WHERE ${this.convertPlaceholders(filter, paramIndex)}` : "";
|
|
5257
|
-
const sql2 = `UPDATE ${MSSQL_DIALECT.quote(table2)} SET ${setClauses}${where}`;
|
|
5258
|
-
const values2 = [...Object.values(data), ...params ?? []];
|
|
5259
|
-
try {
|
|
5260
|
-
const result = await this.execSqlPromise(sql2, values2);
|
|
5261
|
-
return { success: true, affectedRows: result.rowCount };
|
|
5262
|
-
} catch (e) {
|
|
5263
|
-
return { success: false, affectedRows: 0, error: e.message };
|
|
5264
|
-
}
|
|
5265
|
-
}
|
|
5266
|
-
const filterKeys = Object.keys(filter);
|
|
5267
|
-
const whereClauses = buildWhereClause(MSSQL_DIALECT, filterKeys, paramIndex);
|
|
5268
|
-
paramIndex += filterKeys.length;
|
|
5269
|
-
const sql = `UPDATE ${MSSQL_DIALECT.quote(table2)} SET ${setClauses} WHERE ${whereClauses}`;
|
|
5270
|
-
const values = [...Object.values(data), ...Object.values(filter)];
|
|
5257
|
+
const { sql, values } = buildUpdate(MSSQL_DIALECT, table2, data, filter, params, {
|
|
5258
|
+
startAt: 0,
|
|
5259
|
+
convertFragment: (fragment, startAt) => this.convertPlaceholders(fragment, startAt)
|
|
5260
|
+
});
|
|
5271
5261
|
try {
|
|
5272
5262
|
const result = await this.execSqlPromise(sql, values);
|
|
5273
5263
|
return { success: true, affectedRows: result.rowCount };
|
|
@@ -5280,21 +5270,10 @@ var init_mssql = __esm({
|
|
|
5280
5270
|
}
|
|
5281
5271
|
async deleteAsync(table2, filter, params) {
|
|
5282
5272
|
this.ensureConnected();
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
return { success: true, affectedRows: result.rowCount };
|
|
5288
|
-
} catch (e) {
|
|
5289
|
-
return { success: false, affectedRows: 0, error: e.message };
|
|
5290
|
-
}
|
|
5291
|
-
}
|
|
5292
|
-
const filterKeys = Object.keys(filter);
|
|
5293
|
-
let paramIndex = 0;
|
|
5294
|
-
const whereClauses = buildWhereClause(MSSQL_DIALECT, filterKeys, paramIndex);
|
|
5295
|
-
paramIndex += filterKeys.length;
|
|
5296
|
-
const sql = `DELETE FROM ${MSSQL_DIALECT.quote(table2)} WHERE ${whereClauses}`;
|
|
5297
|
-
const values = Object.values(filter);
|
|
5273
|
+
const { sql, values } = buildDelete(MSSQL_DIALECT, table2, filter, params, {
|
|
5274
|
+
startAt: 0,
|
|
5275
|
+
convertFragment: (fragment) => this.bindable(fragment, params)
|
|
5276
|
+
});
|
|
5298
5277
|
try {
|
|
5299
5278
|
const result = await this.execSqlPromise(sql, values);
|
|
5300
5279
|
return { success: true, affectedRows: result.rowCount };
|
|
@@ -5547,6 +5526,7 @@ var init_firebird = __esm({
|
|
|
5547
5526
|
"../orm/src/adapters/firebird.ts"() {
|
|
5548
5527
|
"use strict";
|
|
5549
5528
|
init_sqlDialect();
|
|
5529
|
+
init_sqlCrud();
|
|
5550
5530
|
init_sqlTranslator();
|
|
5551
5531
|
init_connectTimeout();
|
|
5552
5532
|
firebird = null;
|
|
@@ -5917,17 +5897,13 @@ var init_firebird = __esm({
|
|
|
5917
5897
|
async insertAsync(table2, data) {
|
|
5918
5898
|
this.ensureConnected();
|
|
5919
5899
|
if (Array.isArray(data)) {
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
const
|
|
5923
|
-
const paramsList = data.map((row) => keys2.map((k) => row[k]));
|
|
5924
|
-
const result = await this.executeManyAsync(sql2, paramsList);
|
|
5900
|
+
const batch = buildInsertRows(FB_DIALECT, table2, data);
|
|
5901
|
+
if (!batch) return { success: true, affectedRows: 0 };
|
|
5902
|
+
const result = await this.executeManyAsync(batch.sql, batch.paramsList);
|
|
5925
5903
|
const lastId2 = await this.readGeneratorId(table2) ?? result.lastId;
|
|
5926
5904
|
return { success: true, affectedRows: result.totalAffected, lastId: lastId2 ?? void 0 };
|
|
5927
5905
|
}
|
|
5928
|
-
const
|
|
5929
|
-
const sql = buildInsert(FB_DIALECT, table2, keys);
|
|
5930
|
-
const values = Object.values(data);
|
|
5906
|
+
const { sql, values } = buildInsertRow(FB_DIALECT, table2, data);
|
|
5931
5907
|
await this.executePromise(sql, values);
|
|
5932
5908
|
const lastId = await this.readGeneratorId(table2);
|
|
5933
5909
|
return { success: true, affectedRows: 1, lastId: lastId ?? void 0 };
|
|
@@ -5937,18 +5913,7 @@ var init_firebird = __esm({
|
|
|
5937
5913
|
}
|
|
5938
5914
|
async updateAsync(table2, data, filter, params) {
|
|
5939
5915
|
this.ensureConnected();
|
|
5940
|
-
const
|
|
5941
|
-
if (typeof filter === "string") {
|
|
5942
|
-
const where = filter ? ` WHERE ${filter}` : "";
|
|
5943
|
-
const affected2 = await this.executeReturningCount(
|
|
5944
|
-
`UPDATE ${fbQuote(table2)} SET ${setClauses}${where}`,
|
|
5945
|
-
[...Object.values(data), ...params ?? []]
|
|
5946
|
-
);
|
|
5947
|
-
return { success: true, affectedRows: affected2 };
|
|
5948
|
-
}
|
|
5949
|
-
const whereClauses = buildWhereClause(FB_DIALECT, Object.keys(filter));
|
|
5950
|
-
const sql = `UPDATE ${FB_DIALECT.quote(table2)} SET ${setClauses} WHERE ${whereClauses}`;
|
|
5951
|
-
const values = [...Object.values(data), ...Object.values(filter)];
|
|
5916
|
+
const { sql, values } = buildUpdate(FB_DIALECT, table2, data, filter, params);
|
|
5952
5917
|
const affected = await this.executeReturningCount(sql, values);
|
|
5953
5918
|
return { success: true, affectedRows: affected };
|
|
5954
5919
|
}
|
|
@@ -5957,14 +5922,7 @@ var init_firebird = __esm({
|
|
|
5957
5922
|
}
|
|
5958
5923
|
async deleteAsync(table2, filter, params) {
|
|
5959
5924
|
this.ensureConnected();
|
|
5960
|
-
|
|
5961
|
-
const where = filter ? ` WHERE ${filter}` : "";
|
|
5962
|
-
const affected2 = await this.executeReturningCount(`DELETE FROM ${fbQuote(table2)}${where}`, params ?? []);
|
|
5963
|
-
return { success: true, affectedRows: affected2 };
|
|
5964
|
-
}
|
|
5965
|
-
const whereClauses = buildWhereClause(FB_DIALECT, Object.keys(filter));
|
|
5966
|
-
const sql = `DELETE FROM ${FB_DIALECT.quote(table2)} WHERE ${whereClauses}`;
|
|
5967
|
-
const values = Object.values(filter);
|
|
5925
|
+
const { sql, values } = buildDelete(FB_DIALECT, table2, filter, params);
|
|
5968
5926
|
const affected = await this.executeReturningCount(sql, values);
|
|
5969
5927
|
return { success: true, affectedRows: affected };
|
|
5970
5928
|
}
|
|
@@ -6796,6 +6754,7 @@ var init_odbc = __esm({
|
|
|
6796
6754
|
"../orm/src/adapters/odbc.ts"() {
|
|
6797
6755
|
"use strict";
|
|
6798
6756
|
init_sqlDialect();
|
|
6757
|
+
init_sqlCrud();
|
|
6799
6758
|
init_sqlTranslator();
|
|
6800
6759
|
init_connectTimeout();
|
|
6801
6760
|
odbcModule = null;
|
|
@@ -6995,16 +6954,12 @@ var init_odbc = __esm({
|
|
|
6995
6954
|
async insertAsync(table2, data) {
|
|
6996
6955
|
this.ensureConnected();
|
|
6997
6956
|
if (Array.isArray(data)) {
|
|
6998
|
-
|
|
6999
|
-
|
|
7000
|
-
const
|
|
7001
|
-
const paramsList = data.map((row) => keys2.map((k) => row[k]));
|
|
7002
|
-
const { totalAffected } = await this.executeManyAsync(sql2, paramsList);
|
|
6957
|
+
const batch = buildInsertRows(ANSI_DIALECT, table2, data);
|
|
6958
|
+
if (!batch) return { success: true, affectedRows: 0 };
|
|
6959
|
+
const { totalAffected } = await this.executeManyAsync(batch.sql, batch.paramsList);
|
|
7003
6960
|
return { success: true, affectedRows: totalAffected };
|
|
7004
6961
|
}
|
|
7005
|
-
const
|
|
7006
|
-
const sql = buildInsert(ANSI_DIALECT, table2, keys);
|
|
7007
|
-
const values = Object.values(data);
|
|
6962
|
+
const { sql, values } = buildInsertRow(ANSI_DIALECT, table2, data);
|
|
7008
6963
|
try {
|
|
7009
6964
|
const result = await this.connection.query(sql, values);
|
|
7010
6965
|
return { success: true, affectedRows: this.affectedCount(result), lastId: this._lastInsertId ?? void 0 };
|
|
@@ -7015,17 +6970,7 @@ var init_odbc = __esm({
|
|
|
7015
6970
|
/** Update rows in a table matching filter. */
|
|
7016
6971
|
async updateAsync(table2, data, filter, params) {
|
|
7017
6972
|
this.ensureConnected();
|
|
7018
|
-
const
|
|
7019
|
-
let whereSql;
|
|
7020
|
-
let values;
|
|
7021
|
-
if (typeof filter === "string") {
|
|
7022
|
-
whereSql = filter;
|
|
7023
|
-
values = [...Object.values(data), ...params ?? []];
|
|
7024
|
-
} else {
|
|
7025
|
-
whereSql = buildWhereClause(ANSI_DIALECT, Object.keys(filter));
|
|
7026
|
-
values = [...Object.values(data), ...Object.values(filter)];
|
|
7027
|
-
}
|
|
7028
|
-
const sql = `UPDATE ${ANSI_DIALECT.quote(table2)} SET ${setClauses} WHERE ${whereSql}`;
|
|
6973
|
+
const { sql, values } = buildUpdate(ANSI_DIALECT, table2, data, filter, params);
|
|
7029
6974
|
try {
|
|
7030
6975
|
const result = await this.connection.query(sql, values);
|
|
7031
6976
|
return { success: true, affectedRows: this.affectedCount(result) };
|
|
@@ -7044,18 +6989,7 @@ var init_odbc = __esm({
|
|
|
7044
6989
|
}
|
|
7045
6990
|
return { success: true, affectedRows: totalAffected };
|
|
7046
6991
|
}
|
|
7047
|
-
|
|
7048
|
-
const sql2 = filter ? `DELETE FROM "${table2}" WHERE ${filter}` : `DELETE FROM "${table2}"`;
|
|
7049
|
-
try {
|
|
7050
|
-
const result = await this.connection.query(sql2, params ?? []);
|
|
7051
|
-
return { success: true, affectedRows: this.affectedCount(result) };
|
|
7052
|
-
} catch (e) {
|
|
7053
|
-
return { success: false, affectedRows: 0, error: e.message };
|
|
7054
|
-
}
|
|
7055
|
-
}
|
|
7056
|
-
const whereClauses = buildWhereClause(ANSI_DIALECT, Object.keys(filter));
|
|
7057
|
-
const sql = `DELETE FROM ${ANSI_DIALECT.quote(table2)} WHERE ${whereClauses}`;
|
|
7058
|
-
const values = Object.values(filter);
|
|
6992
|
+
const { sql, values } = buildDelete(ANSI_DIALECT, table2, filter, params);
|
|
7059
6993
|
try {
|
|
7060
6994
|
const result = await this.connection.query(sql, values);
|
|
7061
6995
|
return { success: true, affectedRows: this.affectedCount(result) };
|
|
@@ -10775,11 +10709,11 @@ async function eagerLoadBelongsToRelation(instances, model, relatedClass, relati
|
|
|
10775
10709
|
const relatedPkColumn = relatedClass.getDbColumn(relatedPk);
|
|
10776
10710
|
const related = await eagerQueryRelated(relatedClass, relatedPkColumn, values);
|
|
10777
10711
|
if (nested.length > 0 && related.length > 0) await relatedClass._eagerLoad(related, nested);
|
|
10778
|
-
const
|
|
10779
|
-
for (const record of related)
|
|
10712
|
+
const lookup2 = {};
|
|
10713
|
+
for (const record of related) lookup2[_joinKey(record[relatedPk])] = record;
|
|
10780
10714
|
for (const instance of instances) {
|
|
10781
10715
|
const value = instance[fkProp];
|
|
10782
|
-
relationshipCache(instance)[relationName] = value !== void 0 && value !== null ?
|
|
10716
|
+
relationshipCache(instance)[relationName] = value !== void 0 && value !== null ? lookup2[_joinKey(value)] ?? null : null;
|
|
10783
10717
|
}
|
|
10784
10718
|
}
|
|
10785
10719
|
async function eagerLoadRelation(instances, model, relationName, nested) {
|
|
@@ -14466,7 +14400,7 @@ var init_attachment = __esm({
|
|
|
14466
14400
|
// ../orm/src/realtime/storage.ts
|
|
14467
14401
|
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
14468
14402
|
import { mkdirSync as mkdirSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync2, unlinkSync as unlinkSync2, statSync as statSync4 } from "node:fs";
|
|
14469
|
-
import { resolve as resolve5, sep } from "node:path";
|
|
14403
|
+
import { resolve as resolve5, sep as sep2 } from "node:path";
|
|
14470
14404
|
import { createRequire as createRequire6 } from "node:module";
|
|
14471
14405
|
function loadAwsSdk(packageName) {
|
|
14472
14406
|
try {
|
|
@@ -14519,7 +14453,7 @@ var init_storage = __esm({
|
|
|
14519
14453
|
// Resolve inside the root and reject any traversal attempt.
|
|
14520
14454
|
pathFor(key) {
|
|
14521
14455
|
const target = resolve5(this.directory, key);
|
|
14522
|
-
if (target !== this.directory && !target.startsWith(this.directory +
|
|
14456
|
+
if (target !== this.directory && !target.startsWith(this.directory + sep2)) {
|
|
14523
14457
|
throw new Error(`unsafe storage key: ${JSON.stringify(key)}`);
|
|
14524
14458
|
}
|
|
14525
14459
|
return target;
|
|
@@ -15036,6 +14970,7 @@ __export(src_exports, {
|
|
|
15036
14970
|
resolveDbPool: () => resolveDbPool,
|
|
15037
14971
|
resolveFirebirdCharset: () => resolveFirebirdCharset,
|
|
15038
14972
|
resolveGraphConnectTimeout: () => resolveGraphConnectTimeout,
|
|
14973
|
+
resolveSqlitePath: () => resolveSqlitePath,
|
|
15039
14974
|
rollback: () => rollback,
|
|
15040
14975
|
seedModels: () => seedModels,
|
|
15041
14976
|
seedOrm: () => seedOrm,
|
|
@@ -15421,9 +15356,9 @@ var init_cache = __esm({
|
|
|
15421
15356
|
return entry.value;
|
|
15422
15357
|
}
|
|
15423
15358
|
async set(key, value, ttl) {
|
|
15424
|
-
const
|
|
15359
|
+
const expiresAt2 = ttl > 0 ? Date.now() + ttl * 1e3 : 0;
|
|
15425
15360
|
this.store.delete(key);
|
|
15426
|
-
this.store.set(key, { value, expiresAt });
|
|
15361
|
+
this.store.set(key, { value, expiresAt: expiresAt2 });
|
|
15427
15362
|
while (this.store.size > this.maxEntries) {
|
|
15428
15363
|
const firstKey = this.store.keys().next().value;
|
|
15429
15364
|
if (firstKey !== void 0) this.store.delete(firstKey);
|
|
@@ -15856,8 +15791,8 @@ ${s}\r
|
|
|
15856
15791
|
const oldest = files.shift();
|
|
15857
15792
|
if (oldest) fs.unlinkSync(path.join(this.dir, oldest.name));
|
|
15858
15793
|
}
|
|
15859
|
-
const
|
|
15860
|
-
const entry = { key, value, expiresAt };
|
|
15794
|
+
const expiresAt2 = ttl > 0 ? Date.now() / 1e3 + ttl : 0;
|
|
15795
|
+
const entry = { key, value, expiresAt: expiresAt2 };
|
|
15861
15796
|
fs.writeFileSync(this.keyPath(key), JSON.stringify(entry));
|
|
15862
15797
|
} catch {
|
|
15863
15798
|
}
|
|
@@ -17510,7 +17445,7 @@ __export(engine_exports, {
|
|
|
17510
17445
|
});
|
|
17511
17446
|
import { createHash as createHash3, createHmac as createHmac3, randomBytes as randomBytes4 } from "node:crypto";
|
|
17512
17447
|
import { readFileSync as readFileSync6, existsSync as existsSync7, statSync as statSync6, realpathSync } from "node:fs";
|
|
17513
|
-
import { join as join10, resolve as resolve6, isAbsolute as isAbsolute4, sep as
|
|
17448
|
+
import { join as join10, resolve as resolve6, isAbsolute as isAbsolute4, sep as sep3 } from "node:path";
|
|
17514
17449
|
function jsonText(value) {
|
|
17515
17450
|
try {
|
|
17516
17451
|
const text = JSON.stringify(value);
|
|
@@ -17604,8 +17539,8 @@ function capCache(cache, maxEntries) {
|
|
|
17604
17539
|
}
|
|
17605
17540
|
function sweepExpiredCache(cache) {
|
|
17606
17541
|
const now = Date.now();
|
|
17607
|
-
for (const [key, [,
|
|
17608
|
-
if (
|
|
17542
|
+
for (const [key, [, expiresAt2]] of cache) {
|
|
17543
|
+
if (expiresAt2 <= now) cache.delete(key);
|
|
17609
17544
|
}
|
|
17610
17545
|
}
|
|
17611
17546
|
function tokenize(source) {
|
|
@@ -17851,15 +17786,15 @@ function findOutsideQuotes(expr, needle) {
|
|
|
17851
17786
|
}
|
|
17852
17787
|
return -1;
|
|
17853
17788
|
}
|
|
17854
|
-
function splitOutsideQuotes(expr,
|
|
17855
|
-
if (!expr.includes(
|
|
17789
|
+
function splitOutsideQuotes(expr, sep9) {
|
|
17790
|
+
if (!expr.includes(sep9)) return [expr];
|
|
17856
17791
|
const parts = [];
|
|
17857
17792
|
let currentStart = 0;
|
|
17858
17793
|
let inQuote = null;
|
|
17859
17794
|
let depth = 0;
|
|
17860
17795
|
let bracketDepth = 0;
|
|
17861
17796
|
let i = 0;
|
|
17862
|
-
const sepLen =
|
|
17797
|
+
const sepLen = sep9.length;
|
|
17863
17798
|
const lastStart = expr.length - sepLen;
|
|
17864
17799
|
while (i <= lastStart) {
|
|
17865
17800
|
const ch = expr[i];
|
|
@@ -17880,7 +17815,7 @@ function splitOutsideQuotes(expr, sep8) {
|
|
|
17880
17815
|
else if (ch === ")") depth--;
|
|
17881
17816
|
else if (ch === "[") bracketDepth++;
|
|
17882
17817
|
else if (ch === "]") bracketDepth--;
|
|
17883
|
-
if (depth === 0 && bracketDepth === 0 && expr.startsWith(
|
|
17818
|
+
if (depth === 0 && bracketDepth === 0 && expr.startsWith(sep9, i)) {
|
|
17884
17819
|
parts.push(expr.slice(currentStart, i));
|
|
17885
17820
|
i += sepLen;
|
|
17886
17821
|
currentStart = i;
|
|
@@ -18577,6 +18512,12 @@ function setFormTokenSessionId(sessionId) {
|
|
|
18577
18512
|
}
|
|
18578
18513
|
function _buildFormTokenJwt(descriptor = "") {
|
|
18579
18514
|
const secret = process.env.TINA4_SECRET ?? "";
|
|
18515
|
+
const secretBytes = Buffer.byteLength(secret, "utf8");
|
|
18516
|
+
if (secretBytes < 32) {
|
|
18517
|
+
throw new Error(
|
|
18518
|
+
`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.`
|
|
18519
|
+
);
|
|
18520
|
+
}
|
|
18580
18521
|
const ttlMinutes = parseInt(process.env.TINA4_TOKEN_LIMIT || "60", 10);
|
|
18581
18522
|
const header = { alg: "HS256", typ: "JWT" };
|
|
18582
18523
|
const now = Math.floor(Date.now() / 1e3);
|
|
@@ -18827,8 +18768,8 @@ var init_engine = __esm({
|
|
|
18827
18768
|
},
|
|
18828
18769
|
first: (v) => Array.isArray(v) ? v[0] ?? null : null,
|
|
18829
18770
|
last: (v) => Array.isArray(v) ? v[v.length - 1] ?? null : null,
|
|
18830
|
-
join: (v,
|
|
18831
|
-
split: (v,
|
|
18771
|
+
join: (v, sep9) => Array.isArray(v) ? v.map(String).join(sep9 !== void 0 ? String(sep9) : ", ") : String(v),
|
|
18772
|
+
split: (v, sep9) => String(v).split(sep9 !== void 0 ? String(sep9) : " "),
|
|
18832
18773
|
replace: (v, from, to) => {
|
|
18833
18774
|
const s = String(v);
|
|
18834
18775
|
if (from !== void 0 && typeof from === "object" && from !== null && !Array.isArray(from)) {
|
|
@@ -19232,7 +19173,7 @@ var init_engine = __esm({
|
|
|
19232
19173
|
}
|
|
19233
19174
|
const root = realpathSync(this.templateDir);
|
|
19234
19175
|
const real = realpathSync(filePath);
|
|
19235
|
-
if (real !== root && !real.startsWith(root +
|
|
19176
|
+
if (real !== root && !real.startsWith(root + sep3)) {
|
|
19236
19177
|
throw new Error(`Template path escapes the templates directory: ${name}`);
|
|
19237
19178
|
}
|
|
19238
19179
|
return readFileSync6(real, "utf-8");
|
|
@@ -20118,8 +20059,8 @@ var init_engine = __esm({
|
|
|
20118
20059
|
sweepExpiredCache(this.fragmentCache);
|
|
20119
20060
|
const cached = this.fragmentCache.get(cacheKey);
|
|
20120
20061
|
if (cached) {
|
|
20121
|
-
const [htmlContent,
|
|
20122
|
-
if (Date.now() <
|
|
20062
|
+
const [htmlContent, expiresAt2] = cached;
|
|
20063
|
+
if (Date.now() < expiresAt2) {
|
|
20123
20064
|
let i2 = start2 + 1;
|
|
20124
20065
|
let depth = 0;
|
|
20125
20066
|
while (i2 < tokens.length) {
|
|
@@ -20912,7 +20853,7 @@ function csrfBearerIsValid(req2, secret) {
|
|
|
20912
20853
|
const authHeader = req2.headers.authorization ?? "";
|
|
20913
20854
|
if (!authHeader.startsWith("Bearer ")) return false;
|
|
20914
20855
|
const bearerToken = authHeader.slice(7).trim();
|
|
20915
|
-
return Boolean(bearerToken && validToken(bearerToken, secret));
|
|
20856
|
+
return Boolean(bearerToken && isIdentityPayload(validToken(bearerToken, secret)));
|
|
20916
20857
|
}
|
|
20917
20858
|
function csrfRequestToken(req2) {
|
|
20918
20859
|
const query = req2.query ?? {};
|
|
@@ -22049,6 +21990,22 @@ var init_transport = __esm({
|
|
|
22049
21990
|
}
|
|
22050
21991
|
});
|
|
22051
21992
|
|
|
21993
|
+
// src/ssoIdentity.ts
|
|
21994
|
+
function liveSessionIdentity(stored) {
|
|
21995
|
+
if (!stored || typeof stored !== "object") return null;
|
|
21996
|
+
const identity = stored.identity;
|
|
21997
|
+
if (!identity || typeof identity !== "object" || !identity.issuer || !identity.subject) return null;
|
|
21998
|
+
const expiresAt2 = stored.expires_at ?? 0;
|
|
21999
|
+
if (typeof expiresAt2 !== "number") return null;
|
|
22000
|
+
if (expiresAt2 > 0 && Math.floor(Date.now() / 1e3) >= expiresAt2) return null;
|
|
22001
|
+
return identity;
|
|
22002
|
+
}
|
|
22003
|
+
var init_ssoIdentity = __esm({
|
|
22004
|
+
"src/ssoIdentity.ts"() {
|
|
22005
|
+
"use strict";
|
|
22006
|
+
}
|
|
22007
|
+
});
|
|
22008
|
+
|
|
22052
22009
|
// src/authGate.ts
|
|
22053
22010
|
function enforceRouteAuth(req2, res, match, isDevAdmin) {
|
|
22054
22011
|
if (!(match.secure === true && match.noAuth !== true && !isDevAdmin)) {
|
|
@@ -22056,38 +22013,50 @@ function enforceRouteAuth(req2, res, match, isDevAdmin) {
|
|
|
22056
22013
|
}
|
|
22057
22014
|
const authHeader = req2.headers.authorization ?? "";
|
|
22058
22015
|
const headerToken = authHeader.startsWith("Bearer ") ? authHeader.slice(7) : "";
|
|
22059
|
-
let
|
|
22016
|
+
let payload = null;
|
|
22060
22017
|
let tokenSource = "";
|
|
22061
|
-
|
|
22062
|
-
|
|
22063
|
-
|
|
22018
|
+
let resolvedToken = "";
|
|
22019
|
+
if (headerToken) {
|
|
22020
|
+
const candidate = validToken(headerToken);
|
|
22021
|
+
if (isIdentityPayload(candidate)) {
|
|
22022
|
+
payload = candidate;
|
|
22023
|
+
tokenSource = "header";
|
|
22024
|
+
resolvedToken = headerToken;
|
|
22025
|
+
}
|
|
22064
22026
|
}
|
|
22065
|
-
if (!
|
|
22027
|
+
if (!payload) {
|
|
22066
22028
|
const bodyToken = req2.body?.formToken;
|
|
22067
|
-
if (bodyToken
|
|
22068
|
-
|
|
22069
|
-
|
|
22029
|
+
if (bodyToken) {
|
|
22030
|
+
const candidate = validToken(bodyToken);
|
|
22031
|
+
if (isIdentityPayload(candidate)) {
|
|
22032
|
+
payload = candidate;
|
|
22033
|
+
tokenSource = "body";
|
|
22034
|
+
resolvedToken = bodyToken;
|
|
22035
|
+
}
|
|
22070
22036
|
}
|
|
22071
22037
|
}
|
|
22072
|
-
if (!
|
|
22073
|
-
const
|
|
22074
|
-
|
|
22075
|
-
if (identity?.issuer && identity?.subject) {
|
|
22038
|
+
if (!payload) {
|
|
22039
|
+
const identity = liveSessionIdentity(req2.session?.get?.("_tina4_sso"));
|
|
22040
|
+
if (identity) {
|
|
22076
22041
|
req2.user = identity;
|
|
22077
22042
|
return rbacForbidden(match, identity, res);
|
|
22078
22043
|
}
|
|
22079
22044
|
const sessionToken = req2.session?.get?.("token");
|
|
22080
|
-
if (sessionToken
|
|
22081
|
-
|
|
22082
|
-
|
|
22045
|
+
if (sessionToken) {
|
|
22046
|
+
const candidate = validToken(sessionToken);
|
|
22047
|
+
if (isIdentityPayload(candidate)) {
|
|
22048
|
+
payload = candidate;
|
|
22049
|
+
tokenSource = "session";
|
|
22050
|
+
resolvedToken = sessionToken;
|
|
22051
|
+
}
|
|
22083
22052
|
}
|
|
22084
22053
|
}
|
|
22085
|
-
if (!
|
|
22054
|
+
if (!payload) {
|
|
22086
22055
|
res.raw.writeHead(401, { "Content-Type": "application/json" });
|
|
22087
22056
|
res.raw.end(JSON.stringify({ error: "Unauthorized" }));
|
|
22088
22057
|
return true;
|
|
22089
22058
|
}
|
|
22090
|
-
req2.user =
|
|
22059
|
+
req2.user = payload;
|
|
22091
22060
|
if (tokenSource === "body") {
|
|
22092
22061
|
const fresh = refreshToken(resolvedToken);
|
|
22093
22062
|
if (fresh) {
|
|
@@ -22137,6 +22106,7 @@ var init_authGate = __esm({
|
|
|
22137
22106
|
"src/authGate.ts"() {
|
|
22138
22107
|
"use strict";
|
|
22139
22108
|
init_auth();
|
|
22109
|
+
init_ssoIdentity();
|
|
22140
22110
|
}
|
|
22141
22111
|
});
|
|
22142
22112
|
|
|
@@ -23210,8 +23180,8 @@ var init_mongoHandler = __esm({
|
|
|
23210
23180
|
if (!result || result === "__EMPTY__") return null;
|
|
23211
23181
|
try {
|
|
23212
23182
|
const doc = JSON.parse(result);
|
|
23213
|
-
const
|
|
23214
|
-
if (
|
|
23183
|
+
const expiresAt2 = Number(doc?.expires_at ?? 0);
|
|
23184
|
+
if (expiresAt2 > 0 && Date.now() / 1e3 > expiresAt2) {
|
|
23215
23185
|
this.destroy(sessionId);
|
|
23216
23186
|
return null;
|
|
23217
23187
|
}
|
|
@@ -23437,6 +23407,9 @@ parentPort.on("message", (message) => {
|
|
|
23437
23407
|
|
|
23438
23408
|
// src/sessionHandlers/databaseHandler.ts
|
|
23439
23409
|
import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
|
|
23410
|
+
function sleepSync2(ms) {
|
|
23411
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
23412
|
+
}
|
|
23440
23413
|
function column(row, name) {
|
|
23441
23414
|
if (name in row) return row[name];
|
|
23442
23415
|
return row[name.toUpperCase()];
|
|
@@ -23445,7 +23418,7 @@ function schemeOf2(url) {
|
|
|
23445
23418
|
const match = url.match(/^([a-zA-Z][a-zA-Z0-9+.-]*):/);
|
|
23446
23419
|
return match ? match[1].toLowerCase() : "unknown";
|
|
23447
23420
|
}
|
|
23448
|
-
var CREATE_TABLE, DatabaseSessionHandler;
|
|
23421
|
+
var CREATE_TABLE, CREATE_ATTEMPTS, CREATE_RETRY_DELAY_MS, DatabaseSessionHandler;
|
|
23449
23422
|
var init_databaseHandler = __esm({
|
|
23450
23423
|
"src/sessionHandlers/databaseHandler.ts"() {
|
|
23451
23424
|
"use strict";
|
|
@@ -23525,6 +23498,8 @@ var init_databaseHandler = __esm({
|
|
|
23525
23498
|
END
|
|
23526
23499
|
`
|
|
23527
23500
|
};
|
|
23501
|
+
CREATE_ATTEMPTS = 5;
|
|
23502
|
+
CREATE_RETRY_DELAY_MS = 50;
|
|
23528
23503
|
DatabaseSessionHandler = class {
|
|
23529
23504
|
sqliteHandle = null;
|
|
23530
23505
|
/** Set for SQLite. Null when this handler talks to a networked engine. */
|
|
@@ -23565,6 +23540,7 @@ var init_databaseHandler = __esm({
|
|
|
23565
23540
|
get sqlite() {
|
|
23566
23541
|
if (this.sqliteHandle === null) {
|
|
23567
23542
|
this.sqliteHandle = new DatabaseSync3(this.dbPath);
|
|
23543
|
+
this.sqliteHandle.exec("PRAGMA busy_timeout = 5000");
|
|
23568
23544
|
this.sqliteHandle.exec("PRAGMA journal_mode = WAL");
|
|
23569
23545
|
}
|
|
23570
23546
|
return this.sqliteHandle;
|
|
@@ -23652,15 +23628,55 @@ var init_databaseHandler = __esm({
|
|
|
23652
23628
|
sqlCommandSync(this.target, sql, params);
|
|
23653
23629
|
}
|
|
23654
23630
|
/**
|
|
23655
|
-
* Ensure the session table exists (called once on first use)
|
|
23631
|
+
* Ensure the session table exists (called once on first use) - safely when
|
|
23632
|
+
* several processes race to create it.
|
|
23633
|
+
*
|
|
23634
|
+
* Every per-engine statement above is check-then-act, so two processes can
|
|
23635
|
+
* both find the table absent and both CREATE; the loser errors, and every
|
|
23636
|
+
* engine spells that differently (a pg_type unique violation on PostgreSQL,
|
|
23637
|
+
* Msg 2714 on SQL Server, an RDB$RELATIONS unique violation on Firebird). So
|
|
23638
|
+
* a failure is judged by RE-CHECKING whether the table now exists, never by
|
|
23639
|
+
* parsing the message - the rescue the Firebird note above has always
|
|
23640
|
+
* assumed, and which was not here. MEASURED on the lab before it: six real
|
|
23641
|
+
* processes racing lost one to five of them on PostgreSQL, SQLite and
|
|
23642
|
+
* Firebird.
|
|
23643
|
+
*
|
|
23644
|
+
* The re-check is RETRIED because the loser can fail BEFORE the winner has
|
|
23645
|
+
* committed. On MySQL, CREATE TABLE IF NOT EXISTS takes a shared metadata
|
|
23646
|
+
* lock, checks, then upgrades it to exclusive; two upgrades deadlock, and when
|
|
23647
|
+
* MySQL cannot back the victim off silently it answers 1213 "Deadlock found"
|
|
23648
|
+
* while the winner's table is still invisible (tina4-php CI run 35972320442).
|
|
23649
|
+
* Bounded, so a genuine failure (no CREATE grant) still throws. Same attempts
|
|
23650
|
+
* and delays in all four frameworks.
|
|
23656
23651
|
*/
|
|
23657
23652
|
ensureTable() {
|
|
23658
23653
|
if (this.initialized) return;
|
|
23659
23654
|
const ddl = CREATE_TABLE[this.engine];
|
|
23660
|
-
|
|
23661
|
-
|
|
23655
|
+
for (let attempt = 1; ; attempt++) {
|
|
23656
|
+
try {
|
|
23657
|
+
if (this.target === null) this.sqlite.exec(ddl);
|
|
23658
|
+
else sqlCommandSync(this.target, ddl, []);
|
|
23659
|
+
break;
|
|
23660
|
+
} catch (error2) {
|
|
23661
|
+
if (this.tableExists()) break;
|
|
23662
|
+
if (attempt >= CREATE_ATTEMPTS) throw error2;
|
|
23663
|
+
sleepSync2(CREATE_RETRY_DELAY_MS * attempt);
|
|
23664
|
+
}
|
|
23665
|
+
}
|
|
23662
23666
|
this.initialized = true;
|
|
23663
23667
|
}
|
|
23668
|
+
/**
|
|
23669
|
+
* Whether tina4_session exists, asked the one way every engine answers alike:
|
|
23670
|
+
* a query against it that returns no rows succeeds only if it is there.
|
|
23671
|
+
*/
|
|
23672
|
+
tableExists() {
|
|
23673
|
+
try {
|
|
23674
|
+
this.query("SELECT 1 FROM tina4_session WHERE 1 = 0", []);
|
|
23675
|
+
return true;
|
|
23676
|
+
} catch {
|
|
23677
|
+
return false;
|
|
23678
|
+
}
|
|
23679
|
+
}
|
|
23664
23680
|
read(sessionId) {
|
|
23665
23681
|
this.ensureTable();
|
|
23666
23682
|
const rows = this.query(
|
|
@@ -23670,8 +23686,8 @@ var init_databaseHandler = __esm({
|
|
|
23670
23686
|
const row = rows[0];
|
|
23671
23687
|
if (!row) return null;
|
|
23672
23688
|
const now = Date.now() / 1e3;
|
|
23673
|
-
const
|
|
23674
|
-
if (
|
|
23689
|
+
const expiresAt2 = Number(column(row, "expires_at") ?? 0);
|
|
23690
|
+
if (expiresAt2 > 0 && expiresAt2 < now) {
|
|
23675
23691
|
this.destroy(sessionId);
|
|
23676
23692
|
return null;
|
|
23677
23693
|
}
|
|
@@ -23684,7 +23700,7 @@ var init_databaseHandler = __esm({
|
|
|
23684
23700
|
write(sessionId, data, ttl) {
|
|
23685
23701
|
this.ensureTable();
|
|
23686
23702
|
const json = JSON.stringify(data);
|
|
23687
|
-
const
|
|
23703
|
+
const expiresAt2 = ttl > 0 ? Date.now() / 1e3 + ttl : 0;
|
|
23688
23704
|
const existing = this.query(
|
|
23689
23705
|
"SELECT session_id FROM tina4_session WHERE session_id = ?",
|
|
23690
23706
|
[sessionId]
|
|
@@ -23692,12 +23708,12 @@ var init_databaseHandler = __esm({
|
|
|
23692
23708
|
if (existing.length > 0) {
|
|
23693
23709
|
this.exec(
|
|
23694
23710
|
"UPDATE tina4_session SET data = ?, expires_at = ? WHERE session_id = ?",
|
|
23695
|
-
[json,
|
|
23711
|
+
[json, expiresAt2, sessionId]
|
|
23696
23712
|
);
|
|
23697
23713
|
} else {
|
|
23698
23714
|
this.exec(
|
|
23699
23715
|
"INSERT INTO tina4_session (session_id, data, expires_at) VALUES (?, ?, ?)",
|
|
23700
|
-
[sessionId, json,
|
|
23716
|
+
[sessionId, json, expiresAt2]
|
|
23701
23717
|
);
|
|
23702
23718
|
}
|
|
23703
23719
|
}
|
|
@@ -24527,7 +24543,7 @@ var init_dispatchPipeline = __esm({
|
|
|
24527
24543
|
|
|
24528
24544
|
// src/static.ts
|
|
24529
24545
|
import { readFileSync as readFileSync9, realpathSync as realpathSync3, statSync as statSync8 } from "node:fs";
|
|
24530
|
-
import { join as join15, extname as extname3, sep as
|
|
24546
|
+
import { join as join15, extname as extname3, sep as sep4 } from "node:path";
|
|
24531
24547
|
import { gzipSync as gzipSync2 } from "node:zlib";
|
|
24532
24548
|
function isCompressible(contentType) {
|
|
24533
24549
|
return COMPRESSIBLE_PREFIXES2.some((prefix) => contentType.includes(prefix));
|
|
@@ -24558,8 +24574,8 @@ function tryServeStatic(staticDir, req2, res) {
|
|
|
24558
24574
|
}
|
|
24559
24575
|
const stat = statSync8(realPath);
|
|
24560
24576
|
if (!stat.isFile()) continue;
|
|
24561
|
-
if (realPath !== realDir && !realPath.startsWith(realDir +
|
|
24562
|
-
if (hasHiddenSegment(realPath.slice(realDir.length + 1),
|
|
24577
|
+
if (realPath !== realDir && !realPath.startsWith(realDir + sep4)) continue;
|
|
24578
|
+
if (hasHiddenSegment(realPath.slice(realDir.length + 1), sep4)) continue;
|
|
24563
24579
|
const ext = extname3(realPath);
|
|
24564
24580
|
const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
|
|
24565
24581
|
const etag = `W/"${stat.size}-${Math.floor(stat.mtimeMs / 1e3)}"`;
|
|
@@ -25542,7 +25558,7 @@ __export(generate_exports, {
|
|
|
25542
25558
|
toTableName: () => toTableName
|
|
25543
25559
|
});
|
|
25544
25560
|
import { existsSync as existsSync15, mkdirSync as mkdirSync12, writeFileSync as writeFileSync9 } from "node:fs";
|
|
25545
|
-
import { join as join20, relative as relative2, resolve as resolve10, sep as
|
|
25561
|
+
import { join as join20, relative as relative2, resolve as resolve10, sep as sep5 } from "node:path";
|
|
25546
25562
|
function ensureDir(dir) {
|
|
25547
25563
|
if (__resolution.dryRun) return;
|
|
25548
25564
|
if (!existsSync15(dir)) {
|
|
@@ -25668,7 +25684,7 @@ function toRelPath(absPath) {
|
|
|
25668
25684
|
const cwd = process.cwd();
|
|
25669
25685
|
const rel = relative2(cwd, absPath);
|
|
25670
25686
|
if (!rel) return absPath;
|
|
25671
|
-
return
|
|
25687
|
+
return sep5 === "/" ? rel : rel.split(sep5).join("/");
|
|
25672
25688
|
}
|
|
25673
25689
|
function captureEditHints(absPath, content) {
|
|
25674
25690
|
if (!/\.(ts|tsx|js|mjs|cjs|jsx|sql|twig|html\.twig)$/.test(absPath)) return;
|
|
@@ -25740,12 +25756,6 @@ function printResolution() {
|
|
|
25740
25756
|
lines.push("");
|
|
25741
25757
|
process.stderr.write(lines.join("\n"));
|
|
25742
25758
|
}
|
|
25743
|
-
function toPlural(name) {
|
|
25744
|
-
const lower = name.toLowerCase();
|
|
25745
|
-
if (lower.endsWith("s")) return lower;
|
|
25746
|
-
if (lower.endsWith("y") && !/[aeiou]y$/i.test(lower)) return lower.slice(0, -1) + "ies";
|
|
25747
|
-
return lower + "s";
|
|
25748
|
-
}
|
|
25749
25759
|
function toCamel(name) {
|
|
25750
25760
|
return name.charAt(0).toLowerCase() + name.slice(1);
|
|
25751
25761
|
}
|
|
@@ -26178,7 +26188,7 @@ ${aiFill(`delete_${singular}`, {
|
|
|
26178
26188
|
}
|
|
26179
26189
|
function generateCrud(name, flags) {
|
|
26180
26190
|
const table2 = resolveTable(name, flags, { announce: false });
|
|
26181
|
-
const routeName =
|
|
26191
|
+
const routeName = pluralizeReserved(toSnake(name));
|
|
26182
26192
|
const isPublic = Boolean(flags.public);
|
|
26183
26193
|
if (!__resolution.jsonMode) console.log(`
|
|
26184
26194
|
Generating CRUD for ${name}...
|
|
@@ -26432,7 +26442,7 @@ void test${titleName};
|
|
|
26432
26442
|
function generateForm(name, flags) {
|
|
26433
26443
|
const fields = fieldsOrDefault(flags.fields || "");
|
|
26434
26444
|
const table2 = resolveTable(name, flags, { announce: false });
|
|
26435
|
-
const routeName =
|
|
26445
|
+
const routeName = pluralizeReserved(toSnake(name));
|
|
26436
26446
|
const inputTypes = {
|
|
26437
26447
|
string: "text",
|
|
26438
26448
|
str: "text",
|
|
@@ -26498,7 +26508,7 @@ function generateForm(name, flags) {
|
|
|
26498
26508
|
function generateView(name, flags) {
|
|
26499
26509
|
const fields = fieldsOrDefault(flags.fields || "");
|
|
26500
26510
|
const table2 = resolveTable(name, flags, { announce: false });
|
|
26501
|
-
const routeName =
|
|
26511
|
+
const routeName = pluralizeReserved(toSnake(name));
|
|
26502
26512
|
const cols = fields.map(([f]) => f);
|
|
26503
26513
|
const dir = resolve10("src/templates/pages");
|
|
26504
26514
|
ensureDir(dir);
|
|
@@ -27510,7 +27520,7 @@ var init_generate = __esm({
|
|
|
27510
27520
|
crud: ({ name, table: table2 }) => [
|
|
27511
27521
|
`Apply the migration: npx tina4nodejs migrate`,
|
|
27512
27522
|
`Serve and try: npx tina4nodejs serve -> visit /swagger`,
|
|
27513
|
-
`Run the gate test: npx tsx tests/${
|
|
27523
|
+
`Run the gate test: npx tsx tests/${pluralizeReserved(toSnake(name))}.test.ts`,
|
|
27514
27524
|
`Change fields: edit src/models/${name}.ts then re-run generate crud`
|
|
27515
27525
|
],
|
|
27516
27526
|
migration: () => [
|
|
@@ -27528,10 +27538,10 @@ var init_generate = __esm({
|
|
|
27528
27538
|
],
|
|
27529
27539
|
form: ({ name, table: table2 }) => [
|
|
27530
27540
|
`Render from a route: res.render("forms/${table2}.twig", { item })`,
|
|
27531
|
-
`Add the POST route: npx tina4nodejs generate route ${
|
|
27541
|
+
`Add the POST route: npx tina4nodejs generate route ${pluralizeReserved(toSnake(name))} --model ${name}`
|
|
27532
27542
|
],
|
|
27533
|
-
view: ({ table: table2 }) => [
|
|
27534
|
-
`Wire routes to render list -> ${
|
|
27543
|
+
view: ({ name, table: table2 }) => [
|
|
27544
|
+
`Wire routes to render list -> ${pluralizeReserved(toSnake(name))}.twig, detail -> ${table2}.twig`,
|
|
27535
27545
|
`Customize the templates in src/templates/pages/`
|
|
27536
27546
|
],
|
|
27537
27547
|
auth: () => [
|
|
@@ -27692,7 +27702,7 @@ function envTruthy(val) {
|
|
|
27692
27702
|
return ["true", "1", "yes", "on"].includes(val.trim().toLowerCase());
|
|
27693
27703
|
}
|
|
27694
27704
|
function isLoopback(ip) {
|
|
27695
|
-
if (ip == null || ip === "") return
|
|
27705
|
+
if (ip == null || ip === "") return false;
|
|
27696
27706
|
let addr = ip.trim().toLowerCase();
|
|
27697
27707
|
if (addr.startsWith("::ffff:")) addr = addr.slice(7);
|
|
27698
27708
|
return addr === "::1" || addr === "localhost" || addr.startsWith("127.");
|
|
@@ -29283,6 +29293,7 @@ __export(graphql_exports, {
|
|
|
29283
29293
|
graphqlAutoSchemaEnabled: () => graphqlAutoSchemaEnabled,
|
|
29284
29294
|
graphqlEndpoint: () => graphqlEndpoint,
|
|
29285
29295
|
graphqlMaxDepth: () => graphqlMaxDepth,
|
|
29296
|
+
graphqlMaxNodes: () => graphqlMaxNodes,
|
|
29286
29297
|
tokenize: () => tokenize2
|
|
29287
29298
|
});
|
|
29288
29299
|
function tokenize2(source) {
|
|
@@ -29322,7 +29333,12 @@ function graphqlMaxDepth() {
|
|
|
29322
29333
|
const n = parseInt(raw, 10);
|
|
29323
29334
|
return Number.isNaN(n) ? 50 : n;
|
|
29324
29335
|
}
|
|
29325
|
-
|
|
29336
|
+
function graphqlMaxNodes() {
|
|
29337
|
+
const raw = (process.env.TINA4_GRAPHQL_MAX_NODES ?? "1000").trim();
|
|
29338
|
+
const n = parseInt(raw, 10);
|
|
29339
|
+
return Number.isNaN(n) ? 1e3 : n;
|
|
29340
|
+
}
|
|
29341
|
+
var TOKEN_PATTERNS, ParseError, Parser, ComplexityError, GraphQL, GraphQLType;
|
|
29326
29342
|
var init_graphql = __esm({
|
|
29327
29343
|
"src/graphql.ts"() {
|
|
29328
29344
|
"use strict";
|
|
@@ -29361,9 +29377,16 @@ var init_graphql = __esm({
|
|
|
29361
29377
|
Parser = class {
|
|
29362
29378
|
tokens;
|
|
29363
29379
|
pos;
|
|
29380
|
+
// F2: bound the parser's own recursion. A deeply nested query
|
|
29381
|
+
// ("{a{a{a...}}}") otherwise recurses in parseSelectionSet until the call
|
|
29382
|
+
// stack overflows — BEFORE the execution-time depth guard can run. Reusing
|
|
29383
|
+
// TINA4_GRAPHQL_MAX_DEPTH keeps parse and execution on one bound.
|
|
29384
|
+
depth = 0;
|
|
29385
|
+
maxDepth;
|
|
29364
29386
|
constructor(tokens) {
|
|
29365
29387
|
this.tokens = tokens;
|
|
29366
29388
|
this.pos = 0;
|
|
29389
|
+
this.maxDepth = graphqlMaxDepth();
|
|
29367
29390
|
}
|
|
29368
29391
|
peek() {
|
|
29369
29392
|
return this.pos < this.tokens.length ? this.tokens[this.pos] : null;
|
|
@@ -29445,6 +29468,17 @@ var init_graphql = __esm({
|
|
|
29445
29468
|
};
|
|
29446
29469
|
}
|
|
29447
29470
|
parseSelectionSet() {
|
|
29471
|
+
this.depth++;
|
|
29472
|
+
if (this.maxDepth > 0 && this.depth > this.maxDepth) {
|
|
29473
|
+
throw new Error(`Query exceeds maximum depth of ${this.maxDepth}`);
|
|
29474
|
+
}
|
|
29475
|
+
try {
|
|
29476
|
+
return this.parseSelectionSetInner();
|
|
29477
|
+
} finally {
|
|
29478
|
+
this.depth--;
|
|
29479
|
+
}
|
|
29480
|
+
}
|
|
29481
|
+
parseSelectionSetInner() {
|
|
29448
29482
|
this.expect("LBRACE");
|
|
29449
29483
|
const selections = [];
|
|
29450
29484
|
while (!this.match("RBRACE")) {
|
|
@@ -29599,6 +29633,8 @@ var init_graphql = __esm({
|
|
|
29599
29633
|
return t;
|
|
29600
29634
|
}
|
|
29601
29635
|
};
|
|
29636
|
+
ComplexityError = class extends Error {
|
|
29637
|
+
};
|
|
29602
29638
|
GraphQL = class _GraphQL {
|
|
29603
29639
|
types = /* @__PURE__ */ new Map();
|
|
29604
29640
|
queries = /* @__PURE__ */ new Map();
|
|
@@ -29621,6 +29657,12 @@ var init_graphql = __esm({
|
|
|
29621
29657
|
* spread / inline fragment, so circular fragments are caught too.
|
|
29622
29658
|
*/
|
|
29623
29659
|
maxDepth = graphqlMaxDepth();
|
|
29660
|
+
/**
|
|
29661
|
+
* Maximum expanded selection nodes per query (from `TINA4_GRAPHQL_MAX_NODES`,
|
|
29662
|
+
* default 1000; `<= 0` disables). Bounds query WIDTH — fragment bombs and
|
|
29663
|
+
* alias explosions — which the depth guard does not. See {@link graphqlMaxNodes}.
|
|
29664
|
+
*/
|
|
29665
|
+
maxNodes = graphqlMaxNodes();
|
|
29624
29666
|
/**
|
|
29625
29667
|
* Decorator-style resolver registration.
|
|
29626
29668
|
*
|
|
@@ -29768,6 +29810,16 @@ var init_graphql = __esm({
|
|
|
29768
29810
|
return { data: null, errors: [{ message: "No operation found" }] };
|
|
29769
29811
|
}
|
|
29770
29812
|
const op = operations[0];
|
|
29813
|
+
if (this.maxNodes > 0) {
|
|
29814
|
+
try {
|
|
29815
|
+
this.assertComplexity(op.selections, fragments);
|
|
29816
|
+
} catch (e) {
|
|
29817
|
+
if (e instanceof ComplexityError) {
|
|
29818
|
+
return { data: null, errors: [{ message: e.message }] };
|
|
29819
|
+
}
|
|
29820
|
+
throw e;
|
|
29821
|
+
}
|
|
29822
|
+
}
|
|
29771
29823
|
const resolvers = op.operation === "query" ? this.queries : this.mutations;
|
|
29772
29824
|
for (const vdef of op.variables) {
|
|
29773
29825
|
if (!(vdef.name in vars) && vdef.default !== void 0) {
|
|
@@ -29793,6 +29845,36 @@ var init_graphql = __esm({
|
|
|
29793
29845
|
* instead of recursing until the interpreter stack overflows. Top-level
|
|
29794
29846
|
* starts at depth 1; `maxDepth <= 0` disables the guard.
|
|
29795
29847
|
*/
|
|
29848
|
+
/**
|
|
29849
|
+
* Count the expanded selection nodes, throwing {@link ComplexityError} past
|
|
29850
|
+
* `maxNodes` (F2). Fragment spreads are expanded (so a fragment bomb is
|
|
29851
|
+
* counted, not the small unexpanded document) and every field — including
|
|
29852
|
+
* each alias — counts once. The count is capped by `maxNodes` and the walk by
|
|
29853
|
+
* `maxDepth`, so a circular fragment trips the budget instead of looping and
|
|
29854
|
+
* the check itself can never cost more than the budget.
|
|
29855
|
+
*/
|
|
29856
|
+
assertComplexity(selections, fragments) {
|
|
29857
|
+
const depthCap = this.maxDepth > 0 ? this.maxDepth : 1e3;
|
|
29858
|
+
let count = 0;
|
|
29859
|
+
const walk2 = (sels, depth) => {
|
|
29860
|
+
if (depth > depthCap) return;
|
|
29861
|
+
for (const sel of sels) {
|
|
29862
|
+
count++;
|
|
29863
|
+
if (count > this.maxNodes) {
|
|
29864
|
+
throw new ComplexityError(`Query exceeds maximum complexity of ${this.maxNodes} nodes`);
|
|
29865
|
+
}
|
|
29866
|
+
if (sel.kind === "fragment_spread") {
|
|
29867
|
+
const frag = fragments.get(sel.name);
|
|
29868
|
+
if (frag) walk2(frag.selections, depth + 1);
|
|
29869
|
+
} else if (sel.kind === "inline_fragment") {
|
|
29870
|
+
walk2(sel.selections, depth + 1);
|
|
29871
|
+
} else if (sel.kind === "field" && sel.selections) {
|
|
29872
|
+
walk2(sel.selections, depth + 1);
|
|
29873
|
+
}
|
|
29874
|
+
}
|
|
29875
|
+
};
|
|
29876
|
+
walk2(selections, 1);
|
|
29877
|
+
}
|
|
29796
29878
|
async resolveSelectionsInto(selections, resolvers, parent, variables, context, fragments, target, depth) {
|
|
29797
29879
|
const errors = [];
|
|
29798
29880
|
if (this.maxDepth > 0 && depth > this.maxDepth) {
|
|
@@ -30995,7 +31077,8 @@ function wsAuthorized(route, headers, queryString = "", subprotocol = "") {
|
|
|
30995
31077
|
const token = wsToken(headers, queryString, subprotocol);
|
|
30996
31078
|
if (!token) return [null, false];
|
|
30997
31079
|
const payload = validToken(token);
|
|
30998
|
-
return [
|
|
31080
|
+
if (!isIdentityPayload(payload)) return [null, false];
|
|
31081
|
+
return [payload, true];
|
|
30999
31082
|
}
|
|
31000
31083
|
function offeredBearerSubprotocol(subprotocol) {
|
|
31001
31084
|
return subprotocol.split(",").some((p) => p.trim().toLowerCase() === "bearer");
|
|
@@ -32190,12 +32273,36 @@ var init_mongoBackend = __esm({
|
|
|
32190
32273
|
}
|
|
32191
32274
|
}
|
|
32192
32275
|
else if (operation === "size") {
|
|
32193
|
-
|
|
32194
|
-
|
|
32195
|
-
|
|
32196
|
-
|
|
32276
|
+
// ADR-0022 dec 7: size(status) must not answer a different question.
|
|
32277
|
+
// The dead aliases count the .dead_letter store (== deadLetters().length);
|
|
32278
|
+
// reserved/completed count by status; else pending. data = status.
|
|
32279
|
+
const status = data || "pending";
|
|
32280
|
+
let count: number;
|
|
32281
|
+
if (status === "dead" || status === "failed" || status === "dead_letter") {
|
|
32282
|
+
count = await col.countDocuments({ queue: queueName + ".dead_letter" });
|
|
32283
|
+
} else if (status === "reserved" || status === "completed") {
|
|
32284
|
+
count = await col.countDocuments({ queue: queueName, status });
|
|
32285
|
+
} else {
|
|
32286
|
+
count = await col.countDocuments({ queue: queueName, status: "pending" });
|
|
32287
|
+
}
|
|
32197
32288
|
process.stdout.write(String(count));
|
|
32198
32289
|
}
|
|
32290
|
+
else if (operation === "reject") {
|
|
32291
|
+
// Dead-letter NOW, no retry (ADR-0023). Floor attempts at maxRetries
|
|
32292
|
+
// so it reads as exhausted (parity with fail()'s dead-letter branch
|
|
32293
|
+
// and size("dead")). data = JSON { id, reason, maxRetries }.
|
|
32294
|
+
const info = JSON.parse(data);
|
|
32295
|
+
const doc = await col.findOne({ queue: queueName, id: info.id });
|
|
32296
|
+
if (doc) {
|
|
32297
|
+
const attempts = Math.max((doc.attempts || 0) + 1, info.maxRetries);
|
|
32298
|
+
await col.insertOne({
|
|
32299
|
+
...doc, _id: undefined, attempts,
|
|
32300
|
+
status: "dead", queue: queueName + ".dead_letter", error: info.reason,
|
|
32301
|
+
});
|
|
32302
|
+
await col.deleteOne({ _id: doc._id, queue: queueName });
|
|
32303
|
+
}
|
|
32304
|
+
process.stdout.write("__OK__");
|
|
32305
|
+
}
|
|
32199
32306
|
else if (operation === "clear") {
|
|
32200
32307
|
await col.deleteMany({ queue: queueName });
|
|
32201
32308
|
process.stdout.write("__CLEARED__");
|
|
@@ -32431,11 +32538,20 @@ var init_mongoBackend = __esm({
|
|
|
32431
32538
|
return null;
|
|
32432
32539
|
}
|
|
32433
32540
|
}
|
|
32434
|
-
size(queue) {
|
|
32435
|
-
const result = this.execSync("size", queue);
|
|
32541
|
+
size(queue, status2 = "pending") {
|
|
32542
|
+
const result = this.execSync("size", queue, status2);
|
|
32436
32543
|
const num = parseInt(result, 10);
|
|
32437
32544
|
return isNaN(num) ? 0 : num;
|
|
32438
32545
|
}
|
|
32546
|
+
/**
|
|
32547
|
+
* Dead-letter a job immediately — no retry (ADR-0023 reject()). Distinct from
|
|
32548
|
+
* fail(): fail() requeues until maxRetries is spent. attempts is floored at
|
|
32549
|
+
* maxRetries so deadLetters()/size("dead") (which filter/scope to the
|
|
32550
|
+
* dead-letter store) see it.
|
|
32551
|
+
*/
|
|
32552
|
+
reject(queue, id, reason, maxRetries) {
|
|
32553
|
+
this.execSync("reject", queue, JSON.stringify({ id, reason, maxRetries }));
|
|
32554
|
+
}
|
|
32439
32555
|
clear(queue) {
|
|
32440
32556
|
this.execSync("clear", queue);
|
|
32441
32557
|
}
|
|
@@ -32530,7 +32646,9 @@ function createJob(data, queue) {
|
|
|
32530
32646
|
queue._failJob(job.topic, job, reason, queue.getMaxRetries());
|
|
32531
32647
|
},
|
|
32532
32648
|
reject(reason = "") {
|
|
32533
|
-
job.
|
|
32649
|
+
job.status = "dead";
|
|
32650
|
+
job.error = reason;
|
|
32651
|
+
queue._rejectJob(job.topic, job, reason, queue.getMaxRetries());
|
|
32534
32652
|
},
|
|
32535
32653
|
retry(delaySeconds) {
|
|
32536
32654
|
queue._retryJob(job.topic, job, delaySeconds);
|
|
@@ -33139,6 +33257,20 @@ var init_liteBackend = __esm({
|
|
|
33139
33257
|
* dead-letter file -- two spellings of the same intent that previously
|
|
33140
33258
|
* diverged.
|
|
33141
33259
|
*/
|
|
33260
|
+
/**
|
|
33261
|
+
* Reject a job permanently — dead-letter it NOW, no retry (ADR-0023).
|
|
33262
|
+
*
|
|
33263
|
+
* Unlike failJob(), which requeues until maxRetries is spent, rejectJob()
|
|
33264
|
+
* moves a known-poison job straight to the dead-letter store. attempts is
|
|
33265
|
+
* floored at maxRetries so deadLetters() (attempts >= maxRetries) returns it
|
|
33266
|
+
* and size("dead") agrees.
|
|
33267
|
+
*/
|
|
33268
|
+
rejectJob(queue, job, reason, maxRetries) {
|
|
33269
|
+
this.clearReservation(queue, job.id);
|
|
33270
|
+
job.attempts = Math.max((job.attempts || 0) + 1, maxRetries);
|
|
33271
|
+
job.error = reason;
|
|
33272
|
+
this.deadLetter(queue, job, reason);
|
|
33273
|
+
}
|
|
33142
33274
|
retryJob(queue, job, delaySeconds) {
|
|
33143
33275
|
this.clearReservation(queue, job.id);
|
|
33144
33276
|
try {
|
|
@@ -33360,7 +33492,7 @@ var init_queue = __esm({
|
|
|
33360
33492
|
size(status2 = "pending") {
|
|
33361
33493
|
const q = this.topic;
|
|
33362
33494
|
if (this.externalBackend) {
|
|
33363
|
-
return this.externalBackend.size(q);
|
|
33495
|
+
return this.externalBackend.size(q, status2);
|
|
33364
33496
|
}
|
|
33365
33497
|
return this.liteBackend.size(q, status2);
|
|
33366
33498
|
}
|
|
@@ -33628,6 +33760,17 @@ var init_queue = __esm({
|
|
|
33628
33760
|
}
|
|
33629
33761
|
this.liteBackend.failJob(queue, job, error2, maxRetries, this._retryBackoff);
|
|
33630
33762
|
}
|
|
33763
|
+
/**
|
|
33764
|
+
* Dead-letter a job immediately — no retry (ADR-0023 reject()). Distinct from
|
|
33765
|
+
* _failJob, which retries until maxRetries is spent.
|
|
33766
|
+
*/
|
|
33767
|
+
_rejectJob(queue, job, reason, maxRetries) {
|
|
33768
|
+
if (this.externalBackend?.reject) {
|
|
33769
|
+
this.externalBackend.reject(queue, job.id, reason, maxRetries);
|
|
33770
|
+
return;
|
|
33771
|
+
}
|
|
33772
|
+
this.liteBackend.rejectJob(queue, job, reason, maxRetries);
|
|
33773
|
+
}
|
|
33631
33774
|
/**
|
|
33632
33775
|
* Re-queue a job back to the main queue directory with incremented attempts.
|
|
33633
33776
|
*/
|
|
@@ -35752,7 +35895,7 @@ ${end}
|
|
|
35752
35895
|
// src/devAdmin.ts
|
|
35753
35896
|
import { cpus as osCpus } from "node:os";
|
|
35754
35897
|
import { readFileSync as readFileSync20, writeFileSync as writeFileSync15, existsSync as existsSync22, readdirSync as readdirSync15, mkdirSync as mkdirSync18, copyFileSync, statSync as statSync17, openSync as openSync4, closeSync as closeSync4, fstatSync as fstatSync2, fchmodSync as fchmodSync2, ftruncateSync, constants as constants3, realpathSync as realpathSync6, lstatSync } from "node:fs";
|
|
35755
|
-
import { join as join27, dirname as dirname12, resolve as resolve16, relative as relative9, sep as
|
|
35898
|
+
import { join as join27, dirname as dirname12, resolve as resolve16, relative as relative9, sep as sep8, basename as basename5 } from "node:path";
|
|
35756
35899
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
35757
35900
|
import { timingSafeEqual as timingSafeEqual2 } from "node:crypto";
|
|
35758
35901
|
function escapeHtml(value) {
|
|
@@ -36143,7 +36286,7 @@ function safeJoin(projectRoot3, rel, extraRoots = []) {
|
|
|
36143
36286
|
const resolved = realPathOf(resolve16(root, rel));
|
|
36144
36287
|
const roots = [root, ...extraRoots.filter(Boolean).map((r) => realPathOf(resolve16(r)))];
|
|
36145
36288
|
for (const r of roots) {
|
|
36146
|
-
if (resolved === r || resolved.startsWith(r.endsWith(
|
|
36289
|
+
if (resolved === r || resolved.startsWith(r.endsWith(sep8) ? r : r + sep8)) return resolved;
|
|
36147
36290
|
}
|
|
36148
36291
|
} catch {
|
|
36149
36292
|
}
|
|
@@ -39339,13 +39482,20 @@ var sso_exports = {};
|
|
|
39339
39482
|
__export(sso_exports, {
|
|
39340
39483
|
SSO: () => Sso,
|
|
39341
39484
|
Sso: () => Sso,
|
|
39342
|
-
SsoError: () => SsoError
|
|
39485
|
+
SsoError: () => SsoError,
|
|
39486
|
+
liveSessionIdentity: () => liveSessionIdentity
|
|
39343
39487
|
});
|
|
39344
39488
|
import { createHash as createHash9, randomBytes as randomBytes7, timingSafeEqual as timingSafeEqual3 } from "node:crypto";
|
|
39489
|
+
function expiresAt(tokens) {
|
|
39490
|
+
const lifetime = Number(tokens.expires_in ?? 0);
|
|
39491
|
+
return lifetime > 0 ? Math.floor(Date.now() / 1e3) + lifetime : 0;
|
|
39492
|
+
}
|
|
39345
39493
|
var SsoError, Sso;
|
|
39346
39494
|
var init_sso = __esm({
|
|
39347
39495
|
"src/sso.ts"() {
|
|
39348
39496
|
"use strict";
|
|
39497
|
+
init_ssoIdentity();
|
|
39498
|
+
init_ssoIdentity();
|
|
39349
39499
|
SsoError = class extends Error {
|
|
39350
39500
|
};
|
|
39351
39501
|
Sso = class _Sso {
|
|
@@ -39551,13 +39701,12 @@ var init_sso = __esm({
|
|
|
39551
39701
|
access_token: tokens.access_token,
|
|
39552
39702
|
refresh_token: tokens.refresh_token,
|
|
39553
39703
|
id_token: tokens.id_token,
|
|
39554
|
-
expires_at:
|
|
39704
|
+
expires_at: expiresAt(tokens)
|
|
39555
39705
|
});
|
|
39556
39706
|
return { identity, return_to: _Sso.safeReturn(pending.return_to) };
|
|
39557
39707
|
}
|
|
39558
39708
|
identity(requestOrSession) {
|
|
39559
|
-
const
|
|
39560
|
-
const identity = stored?.identity ?? null;
|
|
39709
|
+
const identity = liveSessionIdentity(this.session(requestOrSession)?.get(_Sso.SESSION_KEY));
|
|
39561
39710
|
if (identity && requestOrSession?.session) requestOrSession.user = identity;
|
|
39562
39711
|
return identity;
|
|
39563
39712
|
}
|
|
@@ -39584,7 +39733,7 @@ var init_sso = __esm({
|
|
|
39584
39733
|
access_token: tokens.access_token,
|
|
39585
39734
|
refresh_token: tokens.refresh_token ?? stored.refresh_token,
|
|
39586
39735
|
id_token: tokens.id_token ?? stored.id_token,
|
|
39587
|
-
expires_at:
|
|
39736
|
+
expires_at: expiresAt(tokens)
|
|
39588
39737
|
});
|
|
39589
39738
|
return identity;
|
|
39590
39739
|
} catch (error2) {
|
|
@@ -39676,8 +39825,8 @@ function writeMcpDiscovery(projectRoot3, port) {
|
|
|
39676
39825
|
const lines = contents.split(/\r?\n/);
|
|
39677
39826
|
const already = lines.some((l) => l.trim() === GITIGNORE_LINE || l.trim() === ".tina4");
|
|
39678
39827
|
if (!already) {
|
|
39679
|
-
const
|
|
39680
|
-
fs9.writeFileSync(gitignorePath, `${contents}${
|
|
39828
|
+
const sep9 = contents.endsWith("\n") || contents === "" ? "" : "\n";
|
|
39829
|
+
fs9.writeFileSync(gitignorePath, `${contents}${sep9}${GITIGNORE_LINE}
|
|
39681
39830
|
`, "utf-8");
|
|
39682
39831
|
}
|
|
39683
39832
|
}
|
|
@@ -39726,12 +39875,12 @@ function bannerSurfaceLines(port, opts) {
|
|
|
39726
39875
|
];
|
|
39727
39876
|
}
|
|
39728
39877
|
function swaggerAdvertised() {
|
|
39729
|
-
const
|
|
39878
|
+
const TRUTHY3 = ["true", "1", "yes", "on"];
|
|
39730
39879
|
const raw = (process.env.TINA4_SWAGGER_ENABLED ?? "").trim().toLowerCase();
|
|
39731
39880
|
if (raw === "") {
|
|
39732
|
-
return
|
|
39881
|
+
return TRUTHY3.includes((process.env.TINA4_DEBUG ?? "").trim().toLowerCase());
|
|
39733
39882
|
}
|
|
39734
|
-
return
|
|
39883
|
+
return TRUTHY3.includes(raw);
|
|
39735
39884
|
}
|
|
39736
39885
|
async function autoMigrateOnStartup(migrationDir = "migrations", base = process.cwd()) {
|
|
39737
39886
|
const dir = resolve19(base, migrationDir);
|
|
@@ -40650,8 +40799,9 @@ function wireLocaleGlobal(frondEngine, base) {
|
|
|
40650
40799
|
async function startServer(config) {
|
|
40651
40800
|
loadEnv(".env.local");
|
|
40652
40801
|
loadEnv();
|
|
40653
|
-
const { ensureDevSecret: ensureDevSecret2 } = await Promise.resolve().then(() => (init_auth(), auth_exports));
|
|
40802
|
+
const { ensureDevSecret: ensureDevSecret2, requireBootSecret: requireBootSecret2 } = await Promise.resolve().then(() => (init_auth(), auth_exports));
|
|
40654
40803
|
ensureDevSecret2();
|
|
40804
|
+
requireBootSecret2();
|
|
40655
40805
|
_checkLegacyEnvVars();
|
|
40656
40806
|
const resolved = resolvePortAndHost(config);
|
|
40657
40807
|
const host = resolved.host;
|
|
@@ -42316,6 +42466,178 @@ var init_service = __esm({
|
|
|
42316
42466
|
}
|
|
42317
42467
|
});
|
|
42318
42468
|
|
|
42469
|
+
// src/ssrf.ts
|
|
42470
|
+
import { isIP } from "node:net";
|
|
42471
|
+
import { lookup } from "node:dns/promises";
|
|
42472
|
+
function v4ToBytes(ip) {
|
|
42473
|
+
const parts = ip.split(".");
|
|
42474
|
+
if (parts.length !== 4) return null;
|
|
42475
|
+
const bytes = new Uint8Array(4);
|
|
42476
|
+
for (let i = 0; i < 4; i++) {
|
|
42477
|
+
const n = Number(parts[i]);
|
|
42478
|
+
if (!Number.isInteger(n) || n < 0 || n > 255 || !/^\d+$/.test(parts[i])) return null;
|
|
42479
|
+
bytes[i] = n;
|
|
42480
|
+
}
|
|
42481
|
+
return bytes;
|
|
42482
|
+
}
|
|
42483
|
+
function v6ToBytes(input) {
|
|
42484
|
+
let ip = input.split("%")[0];
|
|
42485
|
+
const lastColon = ip.lastIndexOf(":");
|
|
42486
|
+
const tail = ip.slice(lastColon + 1);
|
|
42487
|
+
if (tail.includes(".")) {
|
|
42488
|
+
const v4 = v4ToBytes(tail);
|
|
42489
|
+
if (!v4) return null;
|
|
42490
|
+
const hextetA = (v4[0] << 8 | v4[1]).toString(16);
|
|
42491
|
+
const hextetB = (v4[2] << 8 | v4[3]).toString(16);
|
|
42492
|
+
ip = ip.slice(0, lastColon + 1) + hextetA + ":" + hextetB;
|
|
42493
|
+
}
|
|
42494
|
+
const halves = ip.split("::");
|
|
42495
|
+
if (halves.length > 2) return null;
|
|
42496
|
+
const head = halves[0] ? halves[0].split(":") : [];
|
|
42497
|
+
const back = halves.length === 2 ? halves[1] ? halves[1].split(":") : [] : [];
|
|
42498
|
+
let groups;
|
|
42499
|
+
if (halves.length === 1) {
|
|
42500
|
+
if (head.length !== 8) return null;
|
|
42501
|
+
groups = head;
|
|
42502
|
+
} else {
|
|
42503
|
+
const missing = 8 - (head.length + back.length);
|
|
42504
|
+
if (missing < 0) return null;
|
|
42505
|
+
groups = [...head, ...Array(missing).fill("0"), ...back];
|
|
42506
|
+
}
|
|
42507
|
+
if (groups.length !== 8) return null;
|
|
42508
|
+
const bytes = new Uint8Array(16);
|
|
42509
|
+
for (let i = 0; i < 8; i++) {
|
|
42510
|
+
if (!/^[0-9a-fA-F]{1,4}$/.test(groups[i])) return null;
|
|
42511
|
+
const g = parseInt(groups[i], 16);
|
|
42512
|
+
bytes[i * 2] = g >> 8 & 255;
|
|
42513
|
+
bytes[i * 2 + 1] = g & 255;
|
|
42514
|
+
}
|
|
42515
|
+
return bytes;
|
|
42516
|
+
}
|
|
42517
|
+
function ipToBytes(ip) {
|
|
42518
|
+
const cleaned = ip.split("%")[0];
|
|
42519
|
+
const family = isIP(cleaned);
|
|
42520
|
+
if (family === 4) return v4ToBytes(cleaned);
|
|
42521
|
+
if (family === 6) {
|
|
42522
|
+
const bytes = v6ToBytes(cleaned);
|
|
42523
|
+
if (!bytes) return null;
|
|
42524
|
+
const isMapped = bytes.subarray(0, 10).every((b) => b === 0) && bytes[10] === 255 && bytes[11] === 255;
|
|
42525
|
+
return isMapped ? bytes.subarray(12) : bytes;
|
|
42526
|
+
}
|
|
42527
|
+
return null;
|
|
42528
|
+
}
|
|
42529
|
+
function parseCidr(cidr) {
|
|
42530
|
+
const [netStr, bitsStr] = cidr.split("/");
|
|
42531
|
+
const net4 = ipToBytes(netStr);
|
|
42532
|
+
if (!net4) return null;
|
|
42533
|
+
return { net: net4, bits: Number(bitsStr) };
|
|
42534
|
+
}
|
|
42535
|
+
function inCidr(packed, cidr) {
|
|
42536
|
+
if (packed.length !== cidr.net.length) return false;
|
|
42537
|
+
const whole = Math.floor(cidr.bits / 8);
|
|
42538
|
+
const remainder = cidr.bits % 8;
|
|
42539
|
+
for (let i = 0; i < whole; i++) {
|
|
42540
|
+
if (packed[i] !== cidr.net[i]) return false;
|
|
42541
|
+
}
|
|
42542
|
+
if (remainder === 0) return true;
|
|
42543
|
+
const mask = 255 << 8 - remainder & 255;
|
|
42544
|
+
return (packed[whole] & mask) === (cidr.net[whole] & mask);
|
|
42545
|
+
}
|
|
42546
|
+
function allowPrivateRequests() {
|
|
42547
|
+
return TRUTHY2.has((process.env[ALLOW_PRIVATE_ENV] ?? "").trim().toLowerCase());
|
|
42548
|
+
}
|
|
42549
|
+
function isBlockedAddress(ip) {
|
|
42550
|
+
const packed = ipToBytes(ip);
|
|
42551
|
+
if (!packed) return true;
|
|
42552
|
+
return BLOCKED.some((cidr) => inCidr(packed, cidr));
|
|
42553
|
+
}
|
|
42554
|
+
function matchesAllowList(host, port, resolved, allowHosts) {
|
|
42555
|
+
const hostLower = host.toLowerCase();
|
|
42556
|
+
for (const raw of allowHosts ?? []) {
|
|
42557
|
+
const entry = String(raw).trim().toLowerCase();
|
|
42558
|
+
if (!entry) continue;
|
|
42559
|
+
if (entry === hostLower || entry === `${hostLower}:${port}`) return true;
|
|
42560
|
+
if (entry.includes("/")) {
|
|
42561
|
+
const cidr = parseCidr(entry);
|
|
42562
|
+
if (cidr && resolved.some((ip) => {
|
|
42563
|
+
const packed = ipToBytes(ip);
|
|
42564
|
+
return packed !== null && inCidr(packed, cidr);
|
|
42565
|
+
})) {
|
|
42566
|
+
return true;
|
|
42567
|
+
}
|
|
42568
|
+
}
|
|
42569
|
+
}
|
|
42570
|
+
return false;
|
|
42571
|
+
}
|
|
42572
|
+
function logBlock(host, ip) {
|
|
42573
|
+
void Promise.resolve().then(() => (init_logger(), logger_exports)).then((mod) => mod.Log?.warning?.(`SSRF guard blocked outbound request to ${host} (${ip})`)).catch(() => void 0);
|
|
42574
|
+
}
|
|
42575
|
+
async function guardUrl(url, allowHosts) {
|
|
42576
|
+
let parsed;
|
|
42577
|
+
try {
|
|
42578
|
+
parsed = new URL(url);
|
|
42579
|
+
} catch {
|
|
42580
|
+
throw new SsrfError(`Blocked request: '${url}' is not a valid URL`);
|
|
42581
|
+
}
|
|
42582
|
+
const scheme = parsed.protocol.replace(/:$/, "").toLowerCase();
|
|
42583
|
+
if (scheme !== "http" && scheme !== "https") {
|
|
42584
|
+
throw new SsrfError(`Blocked request: URL scheme '${scheme || "(none)"}' is not http or https`);
|
|
42585
|
+
}
|
|
42586
|
+
const host = parsed.hostname.replace(/^\[|\]$/g, "");
|
|
42587
|
+
if (!host) {
|
|
42588
|
+
throw new SsrfError("Blocked request: URL has no host");
|
|
42589
|
+
}
|
|
42590
|
+
const port = parsed.port ? Number(parsed.port) : scheme === "https" ? 443 : 80;
|
|
42591
|
+
if (allowPrivateRequests()) return;
|
|
42592
|
+
let resolved;
|
|
42593
|
+
try {
|
|
42594
|
+
const results = await lookup(host, { all: true });
|
|
42595
|
+
resolved = results.map((r) => r.address);
|
|
42596
|
+
} catch {
|
|
42597
|
+
throw new SsrfError(`Blocked request to ${host}: cannot resolve host`);
|
|
42598
|
+
}
|
|
42599
|
+
if (resolved.length === 0) {
|
|
42600
|
+
throw new SsrfError(`Blocked request to ${host}: cannot resolve host`);
|
|
42601
|
+
}
|
|
42602
|
+
if (matchesAllowList(host, port, resolved, allowHosts)) return;
|
|
42603
|
+
for (const ip of resolved) {
|
|
42604
|
+
if (isBlockedAddress(ip)) {
|
|
42605
|
+
logBlock(host, ip);
|
|
42606
|
+
throw new SsrfError(
|
|
42607
|
+
`Blocked request to private/internal address ${ip} (host ${host}): set ${ALLOW_PRIVATE_ENV}=true to allow, or pass an allow-list.`
|
|
42608
|
+
);
|
|
42609
|
+
}
|
|
42610
|
+
}
|
|
42611
|
+
}
|
|
42612
|
+
var ALLOW_PRIVATE_ENV, TRUTHY2, SsrfError, BLOCKED_CIDRS, BLOCKED;
|
|
42613
|
+
var init_ssrf = __esm({
|
|
42614
|
+
"src/ssrf.ts"() {
|
|
42615
|
+
"use strict";
|
|
42616
|
+
ALLOW_PRIVATE_ENV = "TINA4_ALLOW_PRIVATE_REQUESTS";
|
|
42617
|
+
TRUTHY2 = /* @__PURE__ */ new Set(["1", "true", "yes", "on"]);
|
|
42618
|
+
SsrfError = class extends Error {
|
|
42619
|
+
constructor(message) {
|
|
42620
|
+
super(message);
|
|
42621
|
+
this.name = "SsrfError";
|
|
42622
|
+
}
|
|
42623
|
+
};
|
|
42624
|
+
BLOCKED_CIDRS = [
|
|
42625
|
+
"0.0.0.0/8",
|
|
42626
|
+
"10.0.0.0/8",
|
|
42627
|
+
"127.0.0.0/8",
|
|
42628
|
+
"169.254.0.0/16",
|
|
42629
|
+
"172.16.0.0/12",
|
|
42630
|
+
"192.168.0.0/16",
|
|
42631
|
+
"100.64.0.0/10",
|
|
42632
|
+
"::1/128",
|
|
42633
|
+
"::/128",
|
|
42634
|
+
"fc00::/7",
|
|
42635
|
+
"fe80::/10"
|
|
42636
|
+
];
|
|
42637
|
+
BLOCKED = BLOCKED_CIDRS.map(parseCidr).filter((c) => c !== null);
|
|
42638
|
+
}
|
|
42639
|
+
});
|
|
42640
|
+
|
|
42319
42641
|
// src/api.ts
|
|
42320
42642
|
import http from "node:http";
|
|
42321
42643
|
import https from "node:https";
|
|
@@ -42472,6 +42794,7 @@ var init_api = __esm({
|
|
|
42472
42794
|
"src/api.ts"() {
|
|
42473
42795
|
"use strict";
|
|
42474
42796
|
init_version();
|
|
42797
|
+
init_ssrf();
|
|
42475
42798
|
ApiStreamError = class extends Error {
|
|
42476
42799
|
constructor(message, status2 = null) {
|
|
42477
42800
|
super(message);
|
|
@@ -42526,6 +42849,7 @@ var init_api = __esm({
|
|
|
42526
42849
|
transportFn;
|
|
42527
42850
|
cookiesEnabled;
|
|
42528
42851
|
cookies;
|
|
42852
|
+
allowHosts;
|
|
42529
42853
|
/**
|
|
42530
42854
|
* Construct an Api client.
|
|
42531
42855
|
*
|
|
@@ -42565,6 +42889,7 @@ var init_api = __esm({
|
|
|
42565
42889
|
this.transportFn = void 0;
|
|
42566
42890
|
this.cookiesEnabled = false;
|
|
42567
42891
|
this.cookies = {};
|
|
42892
|
+
this.allowHosts = [];
|
|
42568
42893
|
if (typeof authHeaderOrOptions === "object" && authHeaderOrOptions !== null) {
|
|
42569
42894
|
const opts = authHeaderOrOptions;
|
|
42570
42895
|
this.authHeader = opts.authHeader ?? "";
|
|
@@ -42574,6 +42899,7 @@ var init_api = __esm({
|
|
|
42574
42899
|
this.retryBackoff = opts.retryBackoff ?? 0.5;
|
|
42575
42900
|
this.transportFn = opts.transport;
|
|
42576
42901
|
this.cookiesEnabled = opts.cookies ?? false;
|
|
42902
|
+
this.allowHosts = opts.allowHosts ?? [];
|
|
42577
42903
|
if (opts.bearerToken != null) {
|
|
42578
42904
|
this.setBearerToken(opts.bearerToken);
|
|
42579
42905
|
} else if (opts.username != null && opts.password != null) {
|
|
@@ -42887,7 +43213,12 @@ var init_api = __esm({
|
|
|
42887
43213
|
* body-phase timeout is applied by the caller (streamBytes) via
|
|
42888
43214
|
* `res.destroy()`.
|
|
42889
43215
|
*/
|
|
42890
|
-
openStreamRequest(method, url, headers, data, connectSec) {
|
|
43216
|
+
async openStreamRequest(method, url, headers, data, connectSec) {
|
|
43217
|
+
try {
|
|
43218
|
+
await guardUrl(url, this.allowHosts);
|
|
43219
|
+
} catch (err) {
|
|
43220
|
+
throw new ApiStreamError(err instanceof Error ? err.message : String(err), null);
|
|
43221
|
+
}
|
|
42891
43222
|
return new Promise((resolve21, reject) => {
|
|
42892
43223
|
let parsed;
|
|
42893
43224
|
try {
|
|
@@ -42946,10 +43277,11 @@ var init_api = __esm({
|
|
|
42946
43277
|
*/
|
|
42947
43278
|
buildRequest(method, contentType, body, extraHeaders, targetUrl) {
|
|
42948
43279
|
const headers = { "User-Agent": `Tina4/${TINA4_VERSION}`, ...this.headers };
|
|
42949
|
-
|
|
43280
|
+
const sameOriginAsBase = targetUrl === void 0 || sameOrigin(targetUrl, this.baseUrl);
|
|
43281
|
+
if (this.authHeader && sameOriginAsBase) {
|
|
42950
43282
|
headers["Authorization"] = this.authHeader;
|
|
42951
43283
|
}
|
|
42952
|
-
if (this.cookiesEnabled) {
|
|
43284
|
+
if (this.cookiesEnabled && sameOriginAsBase) {
|
|
42953
43285
|
const cookieHeader = this.cookieHeader();
|
|
42954
43286
|
if (cookieHeader) {
|
|
42955
43287
|
headers["Cookie"] = cookieHeader;
|
|
@@ -43050,7 +43382,12 @@ var init_api = __esm({
|
|
|
43050
43382
|
* bearer token / session cookie never leaks to a host you didn't
|
|
43051
43383
|
* authenticate to.
|
|
43052
43384
|
*/
|
|
43053
|
-
performRequest(method, url, headers, data, redirectsLeft) {
|
|
43385
|
+
async performRequest(method, url, headers, data, redirectsLeft) {
|
|
43386
|
+
try {
|
|
43387
|
+
await guardUrl(url, this.allowHosts);
|
|
43388
|
+
} catch (err) {
|
|
43389
|
+
return { kind: "error", error: err instanceof Error ? err.message : String(err) };
|
|
43390
|
+
}
|
|
43054
43391
|
return new Promise((resolve21) => {
|
|
43055
43392
|
let parsed;
|
|
43056
43393
|
try {
|
|
@@ -45790,6 +46127,7 @@ var CURVE, RECORD_SIZE, MAX_PAYLOAD, PushError, Push;
|
|
|
45790
46127
|
var init_push = __esm({
|
|
45791
46128
|
"src/push.ts"() {
|
|
45792
46129
|
"use strict";
|
|
46130
|
+
init_ssrf();
|
|
45793
46131
|
CURVE = "prime256v1";
|
|
45794
46132
|
RECORD_SIZE = 4096;
|
|
45795
46133
|
MAX_PAYLOAD = RECORD_SIZE - 17;
|
|
@@ -45864,10 +46202,19 @@ var init_push = __esm({
|
|
|
45864
46202
|
return { rawPublic, rawPrivate };
|
|
45865
46203
|
}
|
|
45866
46204
|
async deliver(endpoint, config, keys, body) {
|
|
46205
|
+
try {
|
|
46206
|
+
await guardUrl(endpoint.toString(), this.options.allowHosts);
|
|
46207
|
+
} catch (error2) {
|
|
46208
|
+
if (error2 instanceof SsrfError) throw new PushError(error2.message);
|
|
46209
|
+
throw error2;
|
|
46210
|
+
}
|
|
45867
46211
|
let response;
|
|
45868
46212
|
try {
|
|
45869
46213
|
response = await fetch(endpoint, {
|
|
45870
46214
|
method: "POST",
|
|
46215
|
+
// A real push service answers the POST directly; a redirect from a push
|
|
46216
|
+
// endpoint is not followed to a private address (ADR-0084).
|
|
46217
|
+
redirect: "manual",
|
|
45871
46218
|
headers: {
|
|
45872
46219
|
Authorization: `vapid t=${vapidToken(endpoint.toString(), config.subject, keys.rawPrivate, keys.rawPublic)}, k=${config.publicKey}`,
|
|
45873
46220
|
"Content-Encoding": "aes128gcm",
|
|
@@ -47954,6 +48301,7 @@ var init_testClient = __esm({
|
|
|
47954
48301
|
reqHeaders["content-length"] = String(Buffer.byteLength(rawBody));
|
|
47955
48302
|
}
|
|
47956
48303
|
const socket = new Socket();
|
|
48304
|
+
Object.defineProperty(socket, "remoteAddress", { value: "127.0.0.1" });
|
|
47957
48305
|
const rawReq = new IncomingMessage2(socket);
|
|
47958
48306
|
rawReq.method = method.toUpperCase();
|
|
47959
48307
|
rawReq.url = path10;
|
|
@@ -48326,6 +48674,7 @@ var init_validator = __esm({
|
|
|
48326
48674
|
var index_exports = {};
|
|
48327
48675
|
__export(index_exports, {
|
|
48328
48676
|
AI_TOOLS: () => AI_TOOLS,
|
|
48677
|
+
ALLOW_PRIVATE_ENV: () => ALLOW_PRIVATE_ENV,
|
|
48329
48678
|
APPLICATION_FORM: () => APPLICATION_FORM,
|
|
48330
48679
|
APPLICATION_JSON: () => APPLICATION_JSON,
|
|
48331
48680
|
APPLICATION_OCTET: () => APPLICATION_OCTET,
|
|
@@ -48433,6 +48782,7 @@ __export(index_exports, {
|
|
|
48433
48782
|
Session: () => Session,
|
|
48434
48783
|
Sso: () => Sso,
|
|
48435
48784
|
SsoError: () => SsoError,
|
|
48785
|
+
SsrfError: () => SsrfError,
|
|
48436
48786
|
TAKEOVER_KILLED: () => TAKEOVER_KILLED,
|
|
48437
48787
|
TAKEOVER_NOTHING: () => TAKEOVER_NOTHING,
|
|
48438
48788
|
TAKEOVER_REFUSALS: () => TAKEOVER_REFUSALS,
|
|
@@ -48466,6 +48816,7 @@ __export(index_exports, {
|
|
|
48466
48816
|
addHtmlHelpers: () => addHtmlHelpers,
|
|
48467
48817
|
algorithmAvailable: () => algorithmAvailable,
|
|
48468
48818
|
allEnv: () => allEnv,
|
|
48819
|
+
allowPrivateRequests: () => allowPrivateRequests,
|
|
48469
48820
|
any: () => any,
|
|
48470
48821
|
attachCsrfFromEnv: () => attachCsrfFromEnv,
|
|
48471
48822
|
authMiddleware: () => authMiddleware,
|
|
@@ -48531,6 +48882,7 @@ __export(index_exports, {
|
|
|
48531
48882
|
graphqlAutoSchemaEnabled: () => graphqlAutoSchemaEnabled,
|
|
48532
48883
|
graphqlEndpoint: () => graphqlEndpoint,
|
|
48533
48884
|
graphqlMaxDepth: () => graphqlMaxDepth,
|
|
48885
|
+
guardUrl: () => guardUrl,
|
|
48534
48886
|
handle: () => handle,
|
|
48535
48887
|
handleFeedbackTurn: () => handleFeedbackTurn,
|
|
48536
48888
|
handleFeedbackWidgetJs: () => handleFeedbackWidgetJs,
|
|
@@ -48544,6 +48896,7 @@ __export(index_exports, {
|
|
|
48544
48896
|
installAll: () => installAll,
|
|
48545
48897
|
installSelected: () => installSelected,
|
|
48546
48898
|
isBannerSuppressed: () => isBannerSuppressed,
|
|
48899
|
+
isBlockedAddress: () => isBlockedAddress,
|
|
48547
48900
|
isDebugMode: () => isDebugMode,
|
|
48548
48901
|
isDev: () => isDev,
|
|
48549
48902
|
isInstalled: () => isInstalled,
|
|
@@ -48662,6 +49015,7 @@ var init_index = __esm({
|
|
|
48662
49015
|
init_service();
|
|
48663
49016
|
init_cache();
|
|
48664
49017
|
init_api();
|
|
49018
|
+
init_ssrf();
|
|
48665
49019
|
init_context();
|
|
48666
49020
|
init_events();
|
|
48667
49021
|
init_devAdmin();
|
|
@@ -48700,6 +49054,7 @@ var init_index = __esm({
|
|
|
48700
49054
|
init_index();
|
|
48701
49055
|
export {
|
|
48702
49056
|
AI_TOOLS,
|
|
49057
|
+
ALLOW_PRIVATE_ENV,
|
|
48703
49058
|
APPLICATION_FORM,
|
|
48704
49059
|
APPLICATION_JSON,
|
|
48705
49060
|
APPLICATION_OCTET,
|
|
@@ -48807,6 +49162,7 @@ export {
|
|
|
48807
49162
|
Session,
|
|
48808
49163
|
Sso,
|
|
48809
49164
|
SsoError,
|
|
49165
|
+
SsrfError,
|
|
48810
49166
|
TAKEOVER_KILLED,
|
|
48811
49167
|
TAKEOVER_NOTHING,
|
|
48812
49168
|
TAKEOVER_REFUSALS,
|
|
@@ -48840,6 +49196,7 @@ export {
|
|
|
48840
49196
|
addHtmlHelpers,
|
|
48841
49197
|
algorithmAvailable,
|
|
48842
49198
|
allEnv,
|
|
49199
|
+
allowPrivateRequests,
|
|
48843
49200
|
any,
|
|
48844
49201
|
attachCsrfFromEnv,
|
|
48845
49202
|
authMiddleware,
|
|
@@ -48905,6 +49262,7 @@ export {
|
|
|
48905
49262
|
graphqlAutoSchemaEnabled,
|
|
48906
49263
|
graphqlEndpoint,
|
|
48907
49264
|
graphqlMaxDepth,
|
|
49265
|
+
guardUrl,
|
|
48908
49266
|
handle,
|
|
48909
49267
|
handleFeedbackTurn,
|
|
48910
49268
|
handleFeedbackWidgetJs,
|
|
@@ -48918,6 +49276,7 @@ export {
|
|
|
48918
49276
|
installAll,
|
|
48919
49277
|
installSelected,
|
|
48920
49278
|
isBannerSuppressed,
|
|
49279
|
+
isBlockedAddress,
|
|
48921
49280
|
isDebugMode,
|
|
48922
49281
|
isDev,
|
|
48923
49282
|
isInstalled,
|