@galadrim/galadmin 0.1.236 → 0.2.7
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/.github/workflows/main.yml +34 -3
- package/README.md +65 -0
- package/dist/api.d.ts +16 -0
- package/dist/api.js +240 -189
- package/dist/api.js.map +1 -1
- package/dist/index.js +25 -3
- package/dist/index.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/config.d.ts +6 -1
- package/dist/utils/dbManager.d.ts +5 -0
- package/dist/utils/dbManager.js +146 -11
- package/dist/utils/dbManager.js.map +1 -1
- package/dist/utils/imageDimensions.d.ts +5 -0
- package/dist/utils/imageDimensions.js +98 -0
- package/dist/utils/imageDimensions.js.map +1 -0
- package/jest.config.js +43 -0
- package/jest.integration.config.js +16 -0
- package/package.json +30 -10
- package/src/api.ts +284 -217
- package/src/front/src/App.test.js +28 -0
- package/src/front/src/components/OutsideClickHandler.test.js +53 -0
- package/src/front/src/components/RedirectToFirstView.test.js +38 -0
- package/src/front/src/utils/checkRequiredColumns.test.js +46 -0
- package/src/front/src/utils/evalExpression.test.js +29 -0
- package/src/index.ts +26 -3
- package/src/types/config.ts +13 -1
- package/src/utils/dbManager.ts +243 -102
- package/src/utils/imageDimensions.ts +112 -0
- package/tests/__mocks__/@aws-sdk/client-s3.js +6 -0
- package/tests/__mocks__/@aws-sdk/s3-request-presigner.js +3 -0
- package/tests/__mocks__/@google-cloud/storage.js +7 -0
- package/tests/__mocks__/argon2.js +3 -0
- package/tests/__mocks__/axios.js +7 -0
- package/tests/__mocks__/connect-pg-simple.js +24 -0
- package/tests/__mocks__/express-mysql-session.js +24 -0
- package/tests/__mocks__/mysql2/promise.js +3 -0
- package/tests/__mocks__/pg.js +3 -0
- package/tests/__mocks__/rollup.js +5 -0
- package/tests/__mocks__/ssh2-sftp-client.js +10 -0
- package/tests/api-helpers.test.ts +376 -0
- package/tests/dbManager.init.test.ts +129 -0
- package/tests/dbManager.test.ts +265 -0
- package/tests/edge-cases.test.ts +167 -0
- package/tests/helpers/app.ts +69 -0
- package/tests/helpers/fakeDb.ts +66 -0
- package/tests/helpers/fakePool.ts +76 -0
- package/tests/helpers/fixtures.ts +68 -0
- package/tests/imageDimensions.test.ts +111 -0
- package/tests/index.sessionStore.test.ts +19 -0
- package/tests/index.test.ts +412 -0
- package/tests/integration/__mocks__/axios.js +3 -0
- package/tests/integration/dbHarness.ts +381 -0
- package/tests/integration/dbManager.integration.test.ts +69 -0
- package/tests/integration/globalSetup.ts +43 -0
- package/tests/integration/globalTeardown.ts +42 -0
- package/tests/integration/routes.authorization.integration.test.ts +319 -0
- package/tests/integration/routes.config.integration.test.ts +189 -0
- package/tests/integration/routes.crud.integration.test.ts +805 -0
- package/tests/integration/routes.custom.integration.test.ts +376 -0
- package/tests/integration/routes.foreign.integration.test.ts +306 -0
- package/tests/integration/routes.importExport.integration.test.ts +404 -0
- package/tests/integration/routes.integration.test.ts +366 -0
- package/tests/integration/routes.postgresql.integration.test.ts +88 -0
- package/tests/integration/routes.records.integration.test.ts +634 -0
- package/tests/integration/routes.upload.integration.test.ts +425 -0
- package/tests/setup/silence-console.ts +15 -0
package/dist/api.js
CHANGED
|
@@ -11,6 +11,15 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
11
11
|
return t;
|
|
12
12
|
};
|
|
13
13
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.looksLikeSqlBreakout = exports.writableTables = exports.areSafeColumnNames = exports.MUTATING_VIEW_ROUTES = exports.viewFromRequest = exports.canWriteView = exports.roleOf = exports.ROOT_ROLE = void 0;
|
|
15
|
+
exports.generateUniqueIdForObject = generateUniqueIdForObject;
|
|
16
|
+
exports.sqlAnd = sqlAnd;
|
|
17
|
+
exports.getFormattedValue = getFormattedValue;
|
|
18
|
+
exports.evalExpression = evalExpression;
|
|
19
|
+
exports.formatValueForInsertion = formatValueForInsertion;
|
|
20
|
+
exports.collectAllowedQueries = collectAllowedQueries;
|
|
21
|
+
exports.enrichConfig = enrichConfig;
|
|
22
|
+
exports.enrichColumn = enrichColumn;
|
|
14
23
|
const express = require("express");
|
|
15
24
|
const multiparty = require("multiparty");
|
|
16
25
|
const fs = require("fs");
|
|
@@ -20,7 +29,7 @@ const mime = require("mime");
|
|
|
20
29
|
const client_s3_1 = require("@aws-sdk/client-s3");
|
|
21
30
|
const s3_request_presigner_1 = require("@aws-sdk/s3-request-presigner");
|
|
22
31
|
const storage_1 = require("@google-cloud/storage");
|
|
23
|
-
const
|
|
32
|
+
const imageDimensions_1 = require("./utils/imageDimensions");
|
|
24
33
|
const serializeJs = require("serialize-javascript");
|
|
25
34
|
const SftpClient = require("ssh2-sftp-client");
|
|
26
35
|
const rollup = require("rollup");
|
|
@@ -34,6 +43,158 @@ function generateUniqueIdForObject(obj) {
|
|
|
34
43
|
const hash = crypto.createHash("sha256").update(str).digest("hex");
|
|
35
44
|
return hash;
|
|
36
45
|
}
|
|
46
|
+
exports.ROOT_ROLE = "root";
|
|
47
|
+
const roleOf = (req) => req.session && req.session.user && req.session.user.role;
|
|
48
|
+
exports.roleOf = roleOf;
|
|
49
|
+
const canWriteView = (view, role) => role === exports.ROOT_ROLE || !view || !view.roles || view.roles.includes(role);
|
|
50
|
+
exports.canWriteView = canWriteView;
|
|
51
|
+
const viewFromRequest = (req) => {
|
|
52
|
+
const idx = req.body && req.body.viewIndex !== undefined
|
|
53
|
+
? req.body.viewIndex
|
|
54
|
+
: req.query && req.query.viewIndex !== undefined
|
|
55
|
+
? req.query.viewIndex
|
|
56
|
+
: undefined;
|
|
57
|
+
if (idx === undefined || !req.config || !req.config.views)
|
|
58
|
+
return undefined;
|
|
59
|
+
return req.config.views[idx];
|
|
60
|
+
};
|
|
61
|
+
exports.viewFromRequest = viewFromRequest;
|
|
62
|
+
exports.MUTATING_VIEW_ROUTES = new Set([
|
|
63
|
+
"/updateRecord",
|
|
64
|
+
"/createRecord",
|
|
65
|
+
"/deleteRecord",
|
|
66
|
+
"/deleteRecords",
|
|
67
|
+
"/deleteRecordFromPanel",
|
|
68
|
+
"/customAction",
|
|
69
|
+
"/uploadFile",
|
|
70
|
+
]);
|
|
71
|
+
const areSafeColumnNames = (data) => !data || Object.keys(data).every((k) => /^[A-Za-z0-9_]+$/.test(k));
|
|
72
|
+
exports.areSafeColumnNames = areSafeColumnNames;
|
|
73
|
+
const writableTables = (req) => {
|
|
74
|
+
var _a, _b;
|
|
75
|
+
const role = (0, exports.roleOf)(req);
|
|
76
|
+
const tables = new Set();
|
|
77
|
+
for (const view of ((_a = req.config) === null || _a === void 0 ? void 0 : _a.views) || []) {
|
|
78
|
+
if (!(0, exports.canWriteView)(view, role))
|
|
79
|
+
continue;
|
|
80
|
+
if (view.tableName)
|
|
81
|
+
tables.add(view.tableName);
|
|
82
|
+
for (const panel of view.recordViewPanels || []) {
|
|
83
|
+
if (panel.tableName)
|
|
84
|
+
tables.add(panel.tableName);
|
|
85
|
+
if ((_b = panel.createConfig) === null || _b === void 0 ? void 0 : _b.tableName)
|
|
86
|
+
tables.add(panel.createConfig.tableName);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return tables;
|
|
90
|
+
};
|
|
91
|
+
exports.writableTables = writableTables;
|
|
92
|
+
const looksLikeSqlBreakout = (frag) => typeof frag === "string" &&
|
|
93
|
+
/(;|--|\/\*|\bunion\b|\binto\s+(out|dump)file\b|\bsleep\s*\(|\bbenchmark\s*\(|\bload_file\s*\()/i.test(frag);
|
|
94
|
+
exports.looksLikeSqlBreakout = looksLikeSqlBreakout;
|
|
95
|
+
function sqlAnd(conditions) {
|
|
96
|
+
const existingConditions = [];
|
|
97
|
+
for (const condition of conditions) {
|
|
98
|
+
if (condition && condition.length > 0) {
|
|
99
|
+
existingConditions.push(condition);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (existingConditions.length <= 1) {
|
|
103
|
+
return existingConditions[0] || null;
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
let result = "";
|
|
107
|
+
for (let i = 0; i < existingConditions.length; i++) {
|
|
108
|
+
if (i > 0) {
|
|
109
|
+
result += " AND ";
|
|
110
|
+
}
|
|
111
|
+
result += "(" + existingConditions[i] + ")";
|
|
112
|
+
}
|
|
113
|
+
return result;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async function getFormattedValue(column, record) {
|
|
117
|
+
let value;
|
|
118
|
+
try {
|
|
119
|
+
record.options = column.options;
|
|
120
|
+
if (column.formatValueForExport) {
|
|
121
|
+
value = await column.formatValueForExport(record);
|
|
122
|
+
}
|
|
123
|
+
else if (column.formattedValue) {
|
|
124
|
+
value = evalExpression(column.formattedValue, record);
|
|
125
|
+
}
|
|
126
|
+
else if (column.formatValue) {
|
|
127
|
+
value = await column.formatValue(record);
|
|
128
|
+
}
|
|
129
|
+
else if (column.dataType == "tinyint") {
|
|
130
|
+
value = record[column.name] == 1 ? "Oui" : "Non";
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
value = record[column.name];
|
|
134
|
+
}
|
|
135
|
+
return value === null || typeof value == "undefined" ? "" : value;
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
return value || "";
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function evalExpression(expression, data) {
|
|
142
|
+
let varString = "";
|
|
143
|
+
let statement = "";
|
|
144
|
+
try {
|
|
145
|
+
for (let key in data) {
|
|
146
|
+
statement =
|
|
147
|
+
"var " +
|
|
148
|
+
key +
|
|
149
|
+
" = " +
|
|
150
|
+
(typeof data[key] === "string"
|
|
151
|
+
? "'" +
|
|
152
|
+
data[key]
|
|
153
|
+
.replace(/'/g, "\\'")
|
|
154
|
+
.replace(/\n/g, "")
|
|
155
|
+
.replace(/\r/g, "") +
|
|
156
|
+
"'"
|
|
157
|
+
: typeof data[key] === "object"
|
|
158
|
+
? JSON.stringify(data[key])
|
|
159
|
+
: data[key]) +
|
|
160
|
+
";";
|
|
161
|
+
varString += statement;
|
|
162
|
+
}
|
|
163
|
+
const result = eval(varString + expression);
|
|
164
|
+
return result;
|
|
165
|
+
}
|
|
166
|
+
catch (e) {
|
|
167
|
+
console.log("Problème d'évaluation de l'expression", statement);
|
|
168
|
+
console.error(e);
|
|
169
|
+
return "Error";
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
function formatValueForInsertion(value, dbType) {
|
|
173
|
+
if (value !== 0 && !value) {
|
|
174
|
+
return "NULL";
|
|
175
|
+
}
|
|
176
|
+
if (value instanceof Date) {
|
|
177
|
+
return value.toISOString();
|
|
178
|
+
}
|
|
179
|
+
if (typeof value === "string") {
|
|
180
|
+
if (dbType === "POSTGRESQL") {
|
|
181
|
+
return "'" + value.replace(/'/g, "''") + "'";
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
return "'" + value.replace(/'/g, "\\'") + "'";
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (typeof value === "object") {
|
|
188
|
+
return "'" + JSON.stringify(value).replace(/'/g, "\\'") + "'";
|
|
189
|
+
}
|
|
190
|
+
if (typeof value === "boolean") {
|
|
191
|
+
return value ? "TRUE" : "FALSE";
|
|
192
|
+
}
|
|
193
|
+
if (typeof value === "number") {
|
|
194
|
+
return value;
|
|
195
|
+
}
|
|
196
|
+
return value;
|
|
197
|
+
}
|
|
37
198
|
exports.default = (db, config) => {
|
|
38
199
|
const app = express();
|
|
39
200
|
app.use((req, res, next) => {
|
|
@@ -70,59 +231,16 @@ exports.default = (db, config) => {
|
|
|
70
231
|
}
|
|
71
232
|
next();
|
|
72
233
|
});
|
|
73
|
-
const ROOT_ROLE = "root";
|
|
74
|
-
const roleOf = (req) => req.session && req.session.user && req.session.user.role;
|
|
75
|
-
const canWriteView = (view, role) => role === ROOT_ROLE || !view || !view.roles || view.roles.includes(role);
|
|
76
|
-
const viewFromRequest = (req) => {
|
|
77
|
-
const idx = req.body && req.body.viewIndex !== undefined
|
|
78
|
-
? req.body.viewIndex
|
|
79
|
-
: req.query && req.query.viewIndex !== undefined
|
|
80
|
-
? req.query.viewIndex
|
|
81
|
-
: undefined;
|
|
82
|
-
if (idx === undefined || !req.config || !req.config.views)
|
|
83
|
-
return undefined;
|
|
84
|
-
return req.config.views[idx];
|
|
85
|
-
};
|
|
86
|
-
const MUTATING_VIEW_ROUTES = new Set([
|
|
87
|
-
"/updateRecord",
|
|
88
|
-
"/createRecord",
|
|
89
|
-
"/deleteRecord",
|
|
90
|
-
"/deleteRecords",
|
|
91
|
-
"/deleteRecordFromPanel",
|
|
92
|
-
"/customAction",
|
|
93
|
-
"/uploadFile",
|
|
94
|
-
]);
|
|
95
|
-
const areSafeColumnNames = (data) => !data || Object.keys(data).every((k) => /^[A-Za-z0-9_]+$/.test(k));
|
|
96
|
-
const writableTables = (req) => {
|
|
97
|
-
var _a, _b;
|
|
98
|
-
const role = roleOf(req);
|
|
99
|
-
const tables = new Set();
|
|
100
|
-
for (const view of ((_a = req.config) === null || _a === void 0 ? void 0 : _a.views) || []) {
|
|
101
|
-
if (!canWriteView(view, role))
|
|
102
|
-
continue;
|
|
103
|
-
if (view.tableName)
|
|
104
|
-
tables.add(view.tableName);
|
|
105
|
-
for (const panel of view.recordViewPanels || []) {
|
|
106
|
-
if (panel.tableName)
|
|
107
|
-
tables.add(panel.tableName);
|
|
108
|
-
if ((_b = panel.createConfig) === null || _b === void 0 ? void 0 : _b.tableName)
|
|
109
|
-
tables.add(panel.createConfig.tableName);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
return tables;
|
|
113
|
-
};
|
|
114
|
-
const looksLikeSqlBreakout = (frag) => typeof frag === "string" &&
|
|
115
|
-
/(;|--|\/\*|\bunion\b|\binto\s+(out|dump)file\b|\bsleep\s*\(|\bbenchmark\s*\(|\bload_file\s*\()/i.test(frag);
|
|
116
234
|
app.use((req, res, next) => {
|
|
117
|
-
const role = roleOf(req);
|
|
235
|
+
const role = (0, exports.roleOf)(req);
|
|
118
236
|
if (req.body && Array.isArray(req.body.filters)) {
|
|
119
|
-
if (req.body.filters.some(looksLikeSqlBreakout)) {
|
|
237
|
+
if (req.body.filters.some(exports.looksLikeSqlBreakout)) {
|
|
120
238
|
return res.status(400).send({ error: "Invalid filter" });
|
|
121
239
|
}
|
|
122
240
|
}
|
|
123
|
-
if (MUTATING_VIEW_ROUTES.has(req.path)) {
|
|
124
|
-
const view = viewFromRequest(req);
|
|
125
|
-
if (!view || !canWriteView(view, role)) {
|
|
241
|
+
if (exports.MUTATING_VIEW_ROUTES.has(req.path)) {
|
|
242
|
+
const view = (0, exports.viewFromRequest)(req);
|
|
243
|
+
if (!view || !(0, exports.canWriteView)(view, role)) {
|
|
126
244
|
return res.sendStatus(403);
|
|
127
245
|
}
|
|
128
246
|
}
|
|
@@ -158,7 +276,10 @@ exports.default = (db, config) => {
|
|
|
158
276
|
.map((x) => (0, dbManager_1.quoteIdentifier)(x, db.dbType))
|
|
159
277
|
.join(".");
|
|
160
278
|
}
|
|
161
|
-
|
|
279
|
+
const searchEscaped = db.dbType === "POSTGRESQL"
|
|
280
|
+
? params.query.replace(/'/g, "''")
|
|
281
|
+
: params.query.replace(/'/g, "\\'");
|
|
282
|
+
queryString += " LIKE '%" + searchEscaped + "%'";
|
|
162
283
|
isNewCondition = false;
|
|
163
284
|
}
|
|
164
285
|
}
|
|
@@ -243,6 +364,7 @@ exports.default = (db, config) => {
|
|
|
243
364
|
}
|
|
244
365
|
catch (err) {
|
|
245
366
|
console.error(err);
|
|
367
|
+
res.sendStatus(500);
|
|
246
368
|
}
|
|
247
369
|
});
|
|
248
370
|
app.post("/adjacentRecordId", async function (req, res, next) {
|
|
@@ -255,27 +377,6 @@ exports.default = (db, config) => {
|
|
|
255
377
|
recordId: recordId,
|
|
256
378
|
});
|
|
257
379
|
});
|
|
258
|
-
const sqlAnd = (conditions) => {
|
|
259
|
-
const existingConditions = [];
|
|
260
|
-
for (const condition of conditions) {
|
|
261
|
-
if (condition && condition.length > 0) {
|
|
262
|
-
existingConditions.push(condition);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
if (existingConditions.length <= 1) {
|
|
266
|
-
return existingConditions[0] || null;
|
|
267
|
-
}
|
|
268
|
-
else {
|
|
269
|
-
let result = "";
|
|
270
|
-
for (let i = 0; i < existingConditions.length; i++) {
|
|
271
|
-
if (i > 0) {
|
|
272
|
-
result += " AND ";
|
|
273
|
-
}
|
|
274
|
-
result += "(" + existingConditions[i] + ")";
|
|
275
|
-
}
|
|
276
|
-
return result;
|
|
277
|
-
}
|
|
278
|
-
};
|
|
279
380
|
app.post("/foreignSelectRecords", async (req, res, next) => {
|
|
280
381
|
var _a, _b, _c, _d, _e;
|
|
281
382
|
const view = req.config.views[req.body.viewIndex];
|
|
@@ -380,88 +481,6 @@ exports.default = (db, config) => {
|
|
|
380
481
|
});
|
|
381
482
|
});
|
|
382
483
|
const formattedValueToFunction = {};
|
|
383
|
-
async function getFormattedValue(column, record) {
|
|
384
|
-
let value;
|
|
385
|
-
try {
|
|
386
|
-
record.options = column.options;
|
|
387
|
-
if (column.formatValueForExport) {
|
|
388
|
-
value = await column.formatValueForExport(record);
|
|
389
|
-
}
|
|
390
|
-
else if (column.formattedValue) {
|
|
391
|
-
value = evalExpression(column.formattedValue, record);
|
|
392
|
-
}
|
|
393
|
-
else if (column.formatValue) {
|
|
394
|
-
value = await column.formatValue(record);
|
|
395
|
-
}
|
|
396
|
-
else if (column.dataType == "tinyint") {
|
|
397
|
-
value = record[column.name] == 1 ? "Oui" : "Non";
|
|
398
|
-
}
|
|
399
|
-
else {
|
|
400
|
-
value = record[column.name];
|
|
401
|
-
}
|
|
402
|
-
return value === null || typeof value == "undefined" ? "" : value;
|
|
403
|
-
}
|
|
404
|
-
catch (err) {
|
|
405
|
-
return value || "";
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
function evalExpression(expression, data) {
|
|
409
|
-
let varString = "";
|
|
410
|
-
let statement = "";
|
|
411
|
-
try {
|
|
412
|
-
for (let key in data) {
|
|
413
|
-
statement =
|
|
414
|
-
"var " +
|
|
415
|
-
key +
|
|
416
|
-
" = " +
|
|
417
|
-
(typeof data[key] === "string"
|
|
418
|
-
? "'" +
|
|
419
|
-
data[key]
|
|
420
|
-
.replace(/'/g, "\\'")
|
|
421
|
-
.replace(/\n/g, "")
|
|
422
|
-
.replace(/\r/g, "") +
|
|
423
|
-
"'"
|
|
424
|
-
: typeof data[key] === "object"
|
|
425
|
-
? JSON.stringify(data[key])
|
|
426
|
-
: data[key]) +
|
|
427
|
-
";";
|
|
428
|
-
varString += statement;
|
|
429
|
-
}
|
|
430
|
-
const result = eval(varString + expression);
|
|
431
|
-
return result;
|
|
432
|
-
}
|
|
433
|
-
catch (e) {
|
|
434
|
-
console.log("Problème d'évaluation de l'expression", statement);
|
|
435
|
-
console.error(e);
|
|
436
|
-
return "Error";
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
function formatValueForInsertion(value, dbType) {
|
|
440
|
-
if (value !== 0 && !value) {
|
|
441
|
-
return "NULL";
|
|
442
|
-
}
|
|
443
|
-
if (value instanceof Date) {
|
|
444
|
-
return value.toISOString();
|
|
445
|
-
}
|
|
446
|
-
if (typeof value === "string") {
|
|
447
|
-
if (dbType === "POSTGRESQL") {
|
|
448
|
-
return "'" + value.replace(/'/g, "''") + "'";
|
|
449
|
-
}
|
|
450
|
-
else {
|
|
451
|
-
return "'" + value.replace(/'/g, "\\'") + "'";
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
if (typeof value === "object") {
|
|
455
|
-
return "'" + JSON.stringify(value).replace(/'/g, "\\'") + "'";
|
|
456
|
-
}
|
|
457
|
-
if (typeof value === "boolean") {
|
|
458
|
-
return value ? "TRUE" : "FALSE";
|
|
459
|
-
}
|
|
460
|
-
if (typeof value === "number") {
|
|
461
|
-
return value;
|
|
462
|
-
}
|
|
463
|
-
return value;
|
|
464
|
-
}
|
|
465
484
|
app.post("/recordsExport", async function (req, res) {
|
|
466
485
|
const view = req.config.views[req.body.viewIndex];
|
|
467
486
|
const orderBy = req.body.orderBy || view.orderBy;
|
|
@@ -473,7 +492,14 @@ exports.default = (db, config) => {
|
|
|
473
492
|
table: view.tableName,
|
|
474
493
|
page: 1,
|
|
475
494
|
allRecords: true,
|
|
476
|
-
filters: sqlAnd([
|
|
495
|
+
filters: sqlAnd([
|
|
496
|
+
view.filters,
|
|
497
|
+
subview === null || subview === void 0 ? void 0 : subview.filters,
|
|
498
|
+
req.body.filters,
|
|
499
|
+
view.filtersFromUser
|
|
500
|
+
? view.filtersFromUser(req.session.user.userId, req.session.user.role)
|
|
501
|
+
: null,
|
|
502
|
+
]),
|
|
477
503
|
having: subview === null || subview === void 0 ? void 0 : subview.having,
|
|
478
504
|
customQuery: view.customQuery,
|
|
479
505
|
join: view.join,
|
|
@@ -525,6 +551,9 @@ exports.default = (db, config) => {
|
|
|
525
551
|
return res.status(500).send(err);
|
|
526
552
|
}
|
|
527
553
|
const view = req.config.views[fields.viewIndex[0]];
|
|
554
|
+
if (!view || !(0, exports.canWriteView)(view, (0, exports.roleOf)(req))) {
|
|
555
|
+
return res.sendStatus(403);
|
|
556
|
+
}
|
|
528
557
|
if (typeof view.parseImportFile !== "function") {
|
|
529
558
|
return res
|
|
530
559
|
.status(500)
|
|
@@ -608,7 +637,15 @@ exports.default = (db, config) => {
|
|
|
608
637
|
return res.status(500).send(err);
|
|
609
638
|
}
|
|
610
639
|
const view = req.config.views[fields.viewIndex[0]];
|
|
611
|
-
const panel = view
|
|
640
|
+
const panel = view
|
|
641
|
+
? view.recordViewPanels[fields.panelIndex[0]]
|
|
642
|
+
: undefined;
|
|
643
|
+
if (!view ||
|
|
644
|
+
!(0, exports.canWriteView)(view, (0, exports.roleOf)(req)) ||
|
|
645
|
+
!panel ||
|
|
646
|
+
!(0, exports.canWriteView)(panel, (0, exports.roleOf)(req))) {
|
|
647
|
+
return res.sendStatus(403);
|
|
648
|
+
}
|
|
612
649
|
if (typeof panel.parseImportFile !== "function") {
|
|
613
650
|
return res
|
|
614
651
|
.status(500)
|
|
@@ -706,7 +743,7 @@ exports.default = (db, config) => {
|
|
|
706
743
|
});
|
|
707
744
|
app.post("/updateRecord", async function (req, res, next) {
|
|
708
745
|
const view = req.config.views[req.body.viewIndex];
|
|
709
|
-
if (!areSafeColumnNames(req.body.data)) {
|
|
746
|
+
if (!(0, exports.areSafeColumnNames)(req.body.data)) {
|
|
710
747
|
return res.status(400).send({ error: "Invalid column name" });
|
|
711
748
|
}
|
|
712
749
|
for (let key in req.body.data) {
|
|
@@ -858,7 +895,8 @@ exports.default = (db, config) => {
|
|
|
858
895
|
});
|
|
859
896
|
app.post("/deleteRecords", async function (req, res, next) {
|
|
860
897
|
const view = req.config.views[req.body.viewIndex];
|
|
861
|
-
const
|
|
898
|
+
const placeholders = req.body.primaryValues.map(() => "?").join(", ");
|
|
899
|
+
const { err } = await db.query(`DELETE FROM ?? WHERE ?? IN(${placeholders})`, [view.tableName, view.primaryId, ...req.body.primaryValues]);
|
|
862
900
|
if (err) {
|
|
863
901
|
return res.status(500).send(err);
|
|
864
902
|
}
|
|
@@ -894,7 +932,7 @@ exports.default = (db, config) => {
|
|
|
894
932
|
});
|
|
895
933
|
app.post("/duplicateRecord", async function (req, res, next) {
|
|
896
934
|
var _a;
|
|
897
|
-
const allowedTables = writableTables(req);
|
|
935
|
+
const allowedTables = (0, exports.writableTables)(req);
|
|
898
936
|
const requestedTables = [
|
|
899
937
|
req.body.table,
|
|
900
938
|
...(((_a = req.body.duplicateConfig) === null || _a === void 0 ? void 0 : _a.foreignTables) || []).map((t) => t.table),
|
|
@@ -946,9 +984,8 @@ exports.default = (db, config) => {
|
|
|
946
984
|
app.post("/createRecord", async function (req, res, next) {
|
|
947
985
|
var _a, _b, _c;
|
|
948
986
|
const view = req.config.views[req.body.viewIndex];
|
|
949
|
-
const
|
|
950
|
-
|
|
951
|
-
: null;
|
|
987
|
+
const hasPanel = req.body.panelIndex !== undefined && req.body.panelIndex !== null;
|
|
988
|
+
const panel = hasPanel ? view.recordViewPanels[req.body.panelIndex] : null;
|
|
952
989
|
try {
|
|
953
990
|
for (let key in req.body.data) {
|
|
954
991
|
if (req.body.data[key] == "NULL" ||
|
|
@@ -971,7 +1008,7 @@ exports.default = (db, config) => {
|
|
|
971
1008
|
delete createPayload[key];
|
|
972
1009
|
}
|
|
973
1010
|
}
|
|
974
|
-
if (!areSafeColumnNames(createPayload)) {
|
|
1011
|
+
if (!(0, exports.areSafeColumnNames)(createPayload)) {
|
|
975
1012
|
return res.status(400).send({ error: "Invalid column name" });
|
|
976
1013
|
}
|
|
977
1014
|
const queryExpression = Object.keys(createPayload)
|
|
@@ -1001,10 +1038,15 @@ exports.default = (db, config) => {
|
|
|
1001
1038
|
catch (e) { }
|
|
1002
1039
|
}
|
|
1003
1040
|
if (panel ? panel.createCallbackFunction : view.createCallbackFunction) {
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1041
|
+
try {
|
|
1042
|
+
await (panel ? panel.createCallbackFunction : view.createCallbackFunction)({
|
|
1043
|
+
id: r.insertId,
|
|
1044
|
+
data: req.body.data,
|
|
1045
|
+
});
|
|
1046
|
+
}
|
|
1047
|
+
catch (callbackErr) {
|
|
1048
|
+
console.error(callbackErr);
|
|
1049
|
+
}
|
|
1008
1050
|
}
|
|
1009
1051
|
res.send({
|
|
1010
1052
|
id: r.insertId,
|
|
@@ -1018,26 +1060,6 @@ exports.default = (db, config) => {
|
|
|
1018
1060
|
});
|
|
1019
1061
|
}
|
|
1020
1062
|
});
|
|
1021
|
-
function collectAllowedQueries(node, acc = new Set()) {
|
|
1022
|
-
if (!node || typeof node !== "object")
|
|
1023
|
-
return acc;
|
|
1024
|
-
if (Array.isArray(node)) {
|
|
1025
|
-
for (const item of node)
|
|
1026
|
-
collectAllowedQueries(item, acc);
|
|
1027
|
-
return acc;
|
|
1028
|
-
}
|
|
1029
|
-
for (const key of Object.keys(node)) {
|
|
1030
|
-
const value = node[key];
|
|
1031
|
-
if (typeof value === "string" &&
|
|
1032
|
-
(key === "query" || /Query$/.test(key))) {
|
|
1033
|
-
acc.add(value.trim());
|
|
1034
|
-
}
|
|
1035
|
-
else if (value && typeof value === "object") {
|
|
1036
|
-
collectAllowedQueries(value, acc);
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
1039
|
-
return acc;
|
|
1040
|
-
}
|
|
1041
1063
|
const CONFIG_USES_CUSTOM_QUERY = collectAllowedQueries(config.views).size > 0;
|
|
1042
1064
|
if (!CONFIG_USES_CUSTOM_QUERY) {
|
|
1043
1065
|
app.get("/customQuery", (req, res) => res.sendStatus(404));
|
|
@@ -1076,7 +1098,7 @@ exports.default = (db, config) => {
|
|
|
1076
1098
|
function uploadFile(column, fileParamName, req, res) {
|
|
1077
1099
|
if (column.s3Config || column.gcpConfig) {
|
|
1078
1100
|
const form = new multiparty.Form();
|
|
1079
|
-
form.parse(req, function (err, fields, files) {
|
|
1101
|
+
form.parse(req, async function (err, fields, files) {
|
|
1080
1102
|
if (err) {
|
|
1081
1103
|
return res.status(500).send(err);
|
|
1082
1104
|
}
|
|
@@ -1085,8 +1107,14 @@ exports.default = (db, config) => {
|
|
|
1085
1107
|
const extension = originalFilename.match(/(?:\.([^.]+))?$/)[1];
|
|
1086
1108
|
let aspectRatio = null;
|
|
1087
1109
|
if (["png", "jpeg", "jpg", "gif"].includes(extension)) {
|
|
1088
|
-
|
|
1089
|
-
|
|
1110
|
+
try {
|
|
1111
|
+
const dimensions = await (0, imageDimensions_1.getImageDimensions)(files[fileParamName][0].path);
|
|
1112
|
+
if (dimensions) {
|
|
1113
|
+
aspectRatio = dimensions.height / dimensions.width;
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
catch (_a) {
|
|
1117
|
+
}
|
|
1090
1118
|
}
|
|
1091
1119
|
const originalFilenameWithoutExtension = originalFilename.replace("." + extension, "");
|
|
1092
1120
|
if (column.gcpConfig) {
|
|
@@ -1237,6 +1265,10 @@ exports.default = (db, config) => {
|
|
|
1237
1265
|
}
|
|
1238
1266
|
}
|
|
1239
1267
|
app.post("/uploadFileFromBetterRichTextInput", async function (req, res, next) {
|
|
1268
|
+
const role = (0, exports.roleOf)(req);
|
|
1269
|
+
if (!req.config.views.some((view) => (0, exports.canWriteView)(view, role))) {
|
|
1270
|
+
return res.sendStatus(403);
|
|
1271
|
+
}
|
|
1240
1272
|
const fileUploadProperties = JSON.parse(req.headers["file-upload-properties"]);
|
|
1241
1273
|
uploadFile(fileUploadProperties, "upload", req, res);
|
|
1242
1274
|
});
|
|
@@ -1279,6 +1311,25 @@ exports.default = (db, config) => {
|
|
|
1279
1311
|
});
|
|
1280
1312
|
return app;
|
|
1281
1313
|
};
|
|
1314
|
+
function collectAllowedQueries(node, acc = new Set()) {
|
|
1315
|
+
if (!node || typeof node !== "object")
|
|
1316
|
+
return acc;
|
|
1317
|
+
if (Array.isArray(node)) {
|
|
1318
|
+
for (const item of node)
|
|
1319
|
+
collectAllowedQueries(item, acc);
|
|
1320
|
+
return acc;
|
|
1321
|
+
}
|
|
1322
|
+
for (const key of Object.keys(node)) {
|
|
1323
|
+
const value = node[key];
|
|
1324
|
+
if (typeof value === "string" && (key === "query" || /Query$/.test(key))) {
|
|
1325
|
+
acc.add(value.trim());
|
|
1326
|
+
}
|
|
1327
|
+
else if (value && typeof value === "object") {
|
|
1328
|
+
collectAllowedQueries(value, acc);
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
return acc;
|
|
1332
|
+
}
|
|
1282
1333
|
async function enrichConfig(config, db) {
|
|
1283
1334
|
var _a, _b;
|
|
1284
1335
|
const { rows: info, err } = await db.query("SELECT * FROM information_schema.columns WHERE table_schema = ?", [((_a = config.mysql) === null || _a === void 0 ? void 0 : _a.database) || ((_b = config.postgresql) === null || _b === void 0 ? void 0 : _b.database)]);
|