@galadrim/galadmin 0.1.235 → 0.2.6

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.
Files changed (68) hide show
  1. package/.github/workflows/main.yml +33 -2
  2. package/README.md +65 -0
  3. package/dist/api.d.ts +16 -0
  4. package/dist/api.js +276 -215
  5. package/dist/api.js.map +1 -1
  6. package/dist/index.js +1 -1
  7. package/dist/index.js.map +1 -1
  8. package/dist/tsconfig.tsbuildinfo +1 -1
  9. package/dist/utils/dbManager.d.ts +6 -0
  10. package/dist/utils/dbManager.js +150 -10
  11. package/dist/utils/dbManager.js.map +1 -1
  12. package/dist/utils/imageDimensions.d.ts +5 -0
  13. package/dist/utils/imageDimensions.js +98 -0
  14. package/dist/utils/imageDimensions.js.map +1 -0
  15. package/jest.config.js +43 -0
  16. package/jest.integration.config.js +16 -0
  17. package/package.json +30 -10
  18. package/src/api.ts +403 -303
  19. package/src/front/ckeditor5-build/node_modules/.bin/semver +12 -0
  20. package/src/front/ckeditor5-build/node_modules/.bin/semver.cmd +17 -0
  21. package/src/front/ckeditor5-build/node_modules/.bin/semver.ps1 +28 -0
  22. package/src/front/ckeditor5-build/node_modules/semver/bin/semver.js +0 -0
  23. package/src/front/src/App.test.js +28 -0
  24. package/src/front/src/components/OutsideClickHandler.test.js +53 -0
  25. package/src/front/src/components/RedirectToFirstView.test.js +38 -0
  26. package/src/front/src/utils/checkRequiredColumns.test.js +46 -0
  27. package/src/front/src/utils/evalExpression.test.js +29 -0
  28. package/src/index.ts +1 -1
  29. package/src/utils/dbManager.ts +248 -102
  30. package/src/utils/imageDimensions.ts +112 -0
  31. package/tests/__mocks__/@aws-sdk/client-s3.js +6 -0
  32. package/tests/__mocks__/@aws-sdk/s3-request-presigner.js +3 -0
  33. package/tests/__mocks__/@google-cloud/storage.js +7 -0
  34. package/tests/__mocks__/argon2.js +3 -0
  35. package/tests/__mocks__/axios.js +7 -0
  36. package/tests/__mocks__/connect-pg-simple.js +24 -0
  37. package/tests/__mocks__/express-mysql-session.js +24 -0
  38. package/tests/__mocks__/mysql2/promise.js +3 -0
  39. package/tests/__mocks__/pg.js +3 -0
  40. package/tests/__mocks__/rollup.js +5 -0
  41. package/tests/__mocks__/ssh2-sftp-client.js +10 -0
  42. package/tests/api-helpers.test.ts +376 -0
  43. package/tests/dbManager.init.test.ts +129 -0
  44. package/tests/dbManager.test.ts +265 -0
  45. package/tests/edge-cases.test.ts +167 -0
  46. package/tests/helpers/app.ts +69 -0
  47. package/tests/helpers/fakeDb.ts +66 -0
  48. package/tests/helpers/fakePool.ts +76 -0
  49. package/tests/helpers/fixtures.ts +68 -0
  50. package/tests/imageDimensions.test.ts +111 -0
  51. package/tests/index.sessionStore.test.ts +19 -0
  52. package/tests/index.test.ts +363 -0
  53. package/tests/integration/__mocks__/axios.js +3 -0
  54. package/tests/integration/dbHarness.ts +381 -0
  55. package/tests/integration/dbManager.integration.test.ts +69 -0
  56. package/tests/integration/globalSetup.ts +43 -0
  57. package/tests/integration/globalTeardown.ts +42 -0
  58. package/tests/integration/routes.authorization.integration.test.ts +319 -0
  59. package/tests/integration/routes.config.integration.test.ts +189 -0
  60. package/tests/integration/routes.crud.integration.test.ts +805 -0
  61. package/tests/integration/routes.custom.integration.test.ts +376 -0
  62. package/tests/integration/routes.foreign.integration.test.ts +306 -0
  63. package/tests/integration/routes.importExport.integration.test.ts +404 -0
  64. package/tests/integration/routes.integration.test.ts +366 -0
  65. package/tests/integration/routes.postgresql.integration.test.ts +88 -0
  66. package/tests/integration/routes.records.integration.test.ts +634 -0
  67. package/tests/integration/routes.upload.integration.test.ts +425 -0
  68. 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 image_size_1 = require("image-size");
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");
@@ -28,19 +37,175 @@ const plugin_babel_1 = require("@rollup/plugin-babel");
28
37
  const plugin_node_resolve_1 = require("@rollup/plugin-node-resolve");
29
38
  const axios_1 = require("axios");
30
39
  const uuid_1 = require("uuid");
40
+ const dbManager_1 = require("./utils/dbManager");
31
41
  function generateUniqueIdForObject(obj) {
32
42
  const str = JSON.stringify(obj);
33
43
  const hash = crypto.createHash("sha256").update(str).digest("hex");
34
44
  return hash;
35
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
+ }
36
198
  exports.default = (db, config) => {
37
199
  const app = express();
38
200
  app.use((req, res, next) => {
39
201
  req.config = Object.assign(Object.assign({}, config), { views: config.views
40
- .filter(x => !x.roles
41
- || (req.session.user && ([...(x.roles || []), ...(x.readRoles || [])].includes(req.session.user.role))))
42
- .map(view => {
43
- const readOnly = req.session.user && view.roles && !view.roles.includes(req.session.user.role);
202
+ .filter((x) => !x.roles ||
203
+ (req.session.user &&
204
+ [...(x.roles || []), ...(x.readRoles || [])].includes(req.session.user.role)))
205
+ .map((view) => {
206
+ const readOnly = req.session.user &&
207
+ view.roles &&
208
+ !view.roles.includes(req.session.user.role);
44
209
  if (readOnly) {
45
210
  return Object.assign(Object.assign({}, view), { canEdit: false, canCreate: false, canDelete: false, clickToEdit: false });
46
211
  }
@@ -48,9 +213,11 @@ exports.default = (db, config) => {
48
213
  return view;
49
214
  }
50
215
  })
51
- .map(view => {
216
+ .map((view) => {
52
217
  if (view.type === "table") {
53
- return Object.assign(Object.assign({}, view), { columns: view.columns.filter(column => !column.roles || (req.session.user && column.roles.includes(req.session.user.role))) });
218
+ return Object.assign(Object.assign({}, view), { columns: view.columns.filter((column) => !column.roles ||
219
+ (req.session.user &&
220
+ column.roles.includes(req.session.user.role))) });
54
221
  }
55
222
  else {
56
223
  return view;
@@ -64,60 +231,16 @@ exports.default = (db, config) => {
64
231
  }
65
232
  next();
66
233
  });
67
- const ROOT_ROLE = "root";
68
- const roleOf = (req) => req.session && req.session.user && req.session.user.role;
69
- const canWriteView = (view, role) => role === ROOT_ROLE || !view || !view.roles || view.roles.includes(role);
70
- const viewFromRequest = (req) => {
71
- const idx = req.body && req.body.viewIndex !== undefined
72
- ? req.body.viewIndex
73
- : req.query && req.query.viewIndex !== undefined
74
- ? req.query.viewIndex
75
- : undefined;
76
- if (idx === undefined || !req.config || !req.config.views)
77
- return undefined;
78
- return req.config.views[idx];
79
- };
80
- const MUTATING_VIEW_ROUTES = new Set([
81
- "/updateRecord",
82
- "/createRecord",
83
- "/deleteRecord",
84
- "/deleteRecords",
85
- "/deleteRecordFromPanel",
86
- "/customAction",
87
- "/uploadFile",
88
- ]);
89
- const areSafeColumnNames = (data) => !data ||
90
- Object.keys(data).every((k) => /^[A-Za-z0-9_]+$/.test(k));
91
- const writableTables = (req) => {
92
- var _a, _b;
93
- const role = roleOf(req);
94
- const tables = new Set();
95
- for (const view of ((_a = req.config) === null || _a === void 0 ? void 0 : _a.views) || []) {
96
- if (!canWriteView(view, role))
97
- continue;
98
- if (view.tableName)
99
- tables.add(view.tableName);
100
- for (const panel of view.recordViewPanels || []) {
101
- if (panel.tableName)
102
- tables.add(panel.tableName);
103
- if ((_b = panel.createConfig) === null || _b === void 0 ? void 0 : _b.tableName)
104
- tables.add(panel.createConfig.tableName);
105
- }
106
- }
107
- return tables;
108
- };
109
- const looksLikeSqlBreakout = (frag) => typeof frag === "string" &&
110
- /(;|--|\/\*|\bunion\b|\binto\s+(out|dump)file\b|\bsleep\s*\(|\bbenchmark\s*\(|\bload_file\s*\()/i.test(frag);
111
234
  app.use((req, res, next) => {
112
- const role = roleOf(req);
235
+ const role = (0, exports.roleOf)(req);
113
236
  if (req.body && Array.isArray(req.body.filters)) {
114
- if (req.body.filters.some(looksLikeSqlBreakout)) {
237
+ if (req.body.filters.some(exports.looksLikeSqlBreakout)) {
115
238
  return res.status(400).send({ error: "Invalid filter" });
116
239
  }
117
240
  }
118
- if (MUTATING_VIEW_ROUTES.has(req.path)) {
119
- const view = viewFromRequest(req);
120
- 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)) {
121
244
  return res.sendStatus(403);
122
245
  }
123
246
  }
@@ -150,10 +273,13 @@ exports.default = (db, config) => {
150
273
  else if (columns[i].name) {
151
274
  queryString += columns[i].name
152
275
  .split(".")
153
- .map((x) => (db.dbType === "POSTGRESQL" ? x : "`" + x + "`"))
276
+ .map((x) => (0, dbManager_1.quoteIdentifier)(x, db.dbType))
154
277
  .join(".");
155
278
  }
156
- queryString += " LIKE '%" + params.query.replace(/'/g, "\\'") + "%'";
279
+ const searchEscaped = db.dbType === "POSTGRESQL"
280
+ ? params.query.replace(/'/g, "''")
281
+ : params.query.replace(/'/g, "\\'");
282
+ queryString += " LIKE '%" + searchEscaped + "%'";
157
283
  isNewCondition = false;
158
284
  }
159
285
  }
@@ -161,13 +287,13 @@ exports.default = (db, config) => {
161
287
  }
162
288
  const limit = params.allRecords ? 999999 : parseInt(params.limit);
163
289
  const query = params.customQuery ||
164
- `SELECT
165
- ${params.selectExpression || "*"}
166
- FROM ${params.table}
167
- ${params.join || ""}
168
- ${queryString}
169
- ${params.groupBy ? "GROUP BY " + params.groupBy : ""}
170
- ${params.having ? " HAVING " + params.having : ""}
290
+ `SELECT
291
+ ${params.selectExpression || "*"}
292
+ FROM ${params.table}
293
+ ${params.join || ""}
294
+ ${queryString}
295
+ ${params.groupBy ? "GROUP BY " + params.groupBy : ""}
296
+ ${params.having ? " HAVING " + params.having : ""}
171
297
  ${params.orderBy || params.primaryId ? " ORDER BY " + (params.orderBy || `${params.table}.${params.primaryId}`) : ""}`;
172
298
  const { err, rows } = await db.query(`${query} LIMIT ${limit} OFFSET ${Math.max((params.page - 1) * limit, 0)}`);
173
299
  if (err) {
@@ -238,6 +364,7 @@ exports.default = (db, config) => {
238
364
  }
239
365
  catch (err) {
240
366
  console.error(err);
367
+ res.sendStatus(500);
241
368
  }
242
369
  });
243
370
  app.post("/adjacentRecordId", async function (req, res, next) {
@@ -250,27 +377,6 @@ exports.default = (db, config) => {
250
377
  recordId: recordId,
251
378
  });
252
379
  });
253
- const sqlAnd = (conditions) => {
254
- const existingConditions = [];
255
- for (const condition of conditions) {
256
- if (condition && condition.length > 0) {
257
- existingConditions.push(condition);
258
- }
259
- }
260
- if (existingConditions.length <= 1) {
261
- return existingConditions[0] || null;
262
- }
263
- else {
264
- let result = "";
265
- for (let i = 0; i < existingConditions.length; i++) {
266
- if (i > 0) {
267
- result += " AND ";
268
- }
269
- result += "(" + existingConditions[i] + ")";
270
- }
271
- return result;
272
- }
273
- };
274
380
  app.post("/foreignSelectRecords", async (req, res, next) => {
275
381
  var _a, _b, _c, _d, _e;
276
382
  const view = req.config.views[req.body.viewIndex];
@@ -375,88 +481,6 @@ exports.default = (db, config) => {
375
481
  });
376
482
  });
377
483
  const formattedValueToFunction = {};
378
- async function getFormattedValue(column, record) {
379
- let value;
380
- try {
381
- record.options = column.options;
382
- if (column.formatValueForExport) {
383
- value = await column.formatValueForExport(record);
384
- }
385
- else if (column.formattedValue) {
386
- value = evalExpression(column.formattedValue, record);
387
- }
388
- else if (column.formatValue) {
389
- value = await column.formatValue(record);
390
- }
391
- else if (column.dataType == "tinyint") {
392
- value = record[column.name] == 1 ? "Oui" : "Non";
393
- }
394
- else {
395
- value = record[column.name];
396
- }
397
- return value === null || typeof value == "undefined" ? "" : value;
398
- }
399
- catch (err) {
400
- return value || "";
401
- }
402
- }
403
- function evalExpression(expression, data) {
404
- let varString = "";
405
- let statement = "";
406
- try {
407
- for (let key in data) {
408
- statement =
409
- "var " +
410
- key +
411
- " = " +
412
- (typeof data[key] === "string"
413
- ? "'" +
414
- data[key]
415
- .replace(/'/g, "\\'")
416
- .replace(/\n/g, "")
417
- .replace(/\r/g, "") +
418
- "'"
419
- : typeof data[key] === "object"
420
- ? JSON.stringify(data[key])
421
- : data[key]) +
422
- ";";
423
- varString += statement;
424
- }
425
- const result = eval(varString + expression);
426
- return result;
427
- }
428
- catch (e) {
429
- console.log("Problème d'évaluation de l'expression", statement);
430
- console.error(e);
431
- return "Error";
432
- }
433
- }
434
- function formatValueForInsertion(value, dbType) {
435
- if (value !== 0 && !value) {
436
- return "NULL";
437
- }
438
- if (value instanceof Date) {
439
- return value.toISOString();
440
- }
441
- if (typeof value === "string") {
442
- if (dbType === "POSTGRESQL") {
443
- return "'" + value.replace(/'/g, "''") + "'";
444
- }
445
- else {
446
- return "'" + value.replace(/'/g, "\\'") + "'";
447
- }
448
- }
449
- if (typeof value === "object") {
450
- return "'" + JSON.stringify(value).replace(/'/g, "\\'") + "'";
451
- }
452
- if (typeof value === "boolean") {
453
- return value ? "TRUE" : "FALSE";
454
- }
455
- if (typeof value === "number") {
456
- return value;
457
- }
458
- return value;
459
- }
460
484
  app.post("/recordsExport", async function (req, res) {
461
485
  const view = req.config.views[req.body.viewIndex];
462
486
  const orderBy = req.body.orderBy || view.orderBy;
@@ -468,7 +492,14 @@ exports.default = (db, config) => {
468
492
  table: view.tableName,
469
493
  page: 1,
470
494
  allRecords: true,
471
- filters: sqlAnd([view.filters, subview === null || subview === void 0 ? void 0 : subview.filters, req.body.filters]),
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
+ ]),
472
503
  having: subview === null || subview === void 0 ? void 0 : subview.having,
473
504
  customQuery: view.customQuery,
474
505
  join: view.join,
@@ -520,6 +551,9 @@ exports.default = (db, config) => {
520
551
  return res.status(500).send(err);
521
552
  }
522
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
+ }
523
557
  if (typeof view.parseImportFile !== "function") {
524
558
  return res
525
559
  .status(500)
@@ -542,7 +576,7 @@ exports.default = (db, config) => {
542
576
  }
543
577
  let queryString;
544
578
  if (db.dbType === "POSTGRESQL") {
545
- queryString = `INSERT INTO ?? (${columns.map((x) => `"${x}"`).join(", ")}) VALUES`;
579
+ queryString = `INSERT INTO ?? (${columns.map((x) => (0, dbManager_1.quoteIdentifier)(x, db.dbType)).join(", ")}) VALUES`;
546
580
  for (let record of records) {
547
581
  const valuesList = columns
548
582
  .map((x) => formatValueForInsertion(record[x], db.dbType))
@@ -553,13 +587,13 @@ exports.default = (db, config) => {
553
587
  queryString.slice(0, -1) +
554
588
  ` ON CONFLICT (${view.importFileUniqueField}) DO UPDATE SET `;
555
589
  for (let column of columns) {
556
- queryString += `"${column}" = EXCLUDED."${column}",`;
590
+ queryString += `${(0, dbManager_1.quoteIdentifier)(column, db.dbType)} = EXCLUDED.${(0, dbManager_1.quoteIdentifier)(column, db.dbType)},`;
557
591
  }
558
592
  }
559
593
  else {
560
594
  queryString =
561
595
  "INSERT INTO ?? (" +
562
- columns.map((x) => "`" + x + "`").join(", ") +
596
+ columns.map((x) => (0, dbManager_1.quoteIdentifier)(x, db.dbType)).join(", ") +
563
597
  ") VALUES";
564
598
  for (let record of records) {
565
599
  queryString +=
@@ -571,7 +605,7 @@ exports.default = (db, config) => {
571
605
  }
572
606
  queryString = queryString.slice(0, -1) + " ON DUPLICATE KEY UPDATE";
573
607
  for (let column of columns) {
574
- queryString += " `" + column + "` = VALUES(`" + column + "`),";
608
+ queryString += ` ${(0, dbManager_1.quoteIdentifier)(column, db.dbType)} = VALUES(${(0, dbManager_1.quoteIdentifier)(column, db.dbType)}),`;
575
609
  }
576
610
  }
577
611
  queryString = queryString.slice(0, -1);
@@ -603,7 +637,15 @@ exports.default = (db, config) => {
603
637
  return res.status(500).send(err);
604
638
  }
605
639
  const view = req.config.views[fields.viewIndex[0]];
606
- const panel = view.recordViewPanels[fields.panelIndex[0]];
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
+ }
607
649
  if (typeof panel.parseImportFile !== "function") {
608
650
  return res
609
651
  .status(500)
@@ -629,10 +671,10 @@ exports.default = (db, config) => {
629
671
  }
630
672
  }
631
673
  }
632
- let queryString = "INSERT IGNORE INTO ?? (`" +
633
- panel.createConfig.recordIdKey +
634
- "`, " +
635
- columns.map((x) => "`" + x + "`").join(", ") +
674
+ let queryString = "INSERT IGNORE INTO ?? (" +
675
+ (0, dbManager_1.quoteIdentifier)(panel.createConfig.recordIdKey, db.dbType) +
676
+ ", " +
677
+ columns.map((x) => (0, dbManager_1.quoteIdentifier)(x, db.dbType)).join(", ") +
636
678
  ") VALUES";
637
679
  for (let record of records) {
638
680
  queryString +=
@@ -646,7 +688,7 @@ exports.default = (db, config) => {
646
688
  }
647
689
  queryString = queryString.slice(0, -1) + " ON DUPLICATE KEY UPDATE";
648
690
  for (let column of columns) {
649
- queryString += " `" + column + "` = VALUES(`" + column + "`),";
691
+ queryString += ` ${(0, dbManager_1.quoteIdentifier)(column, db.dbType)} = VALUES(${(0, dbManager_1.quoteIdentifier)(column, db.dbType)}),`;
650
692
  }
651
693
  const { err, rows: r } = await db.query(queryString.slice(0, -1), [
652
694
  panel.tableName,
@@ -701,7 +743,7 @@ exports.default = (db, config) => {
701
743
  });
702
744
  app.post("/updateRecord", async function (req, res, next) {
703
745
  const view = req.config.views[req.body.viewIndex];
704
- if (!areSafeColumnNames(req.body.data)) {
746
+ if (!(0, exports.areSafeColumnNames)(req.body.data)) {
705
747
  return res.status(400).send({ error: "Invalid column name" });
706
748
  }
707
749
  for (let key in req.body.data) {
@@ -715,7 +757,7 @@ exports.default = (db, config) => {
715
757
  return res.sendStatus(200);
716
758
  }
717
759
  const queryExpression = Object.keys(req.body.data)
718
- .map((x) => `${x} = ?`)
760
+ .map((x) => `${(0, dbManager_1.quoteIdentifier)(x, db.dbType)} = ?`)
719
761
  .join(", ");
720
762
  const queryParameters = Object.keys(req.body.data).map((x) => req.body.data[x]);
721
763
  const { err } = await db.query(`UPDATE ?? SET ${queryExpression} WHERE ?? = ?`, [
@@ -853,7 +895,8 @@ exports.default = (db, config) => {
853
895
  });
854
896
  app.post("/deleteRecords", async function (req, res, next) {
855
897
  const view = req.config.views[req.body.viewIndex];
856
- const { err } = await db.query(`DELETE FROM ?? WHERE ?? IN(${req.body.primaryValues.map((x) => "'" + x + "'").join(",")})`, [view.tableName, view.primaryId]);
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]);
857
900
  if (err) {
858
901
  return res.status(500).send(err);
859
902
  }
@@ -889,7 +932,7 @@ exports.default = (db, config) => {
889
932
  });
890
933
  app.post("/duplicateRecord", async function (req, res, next) {
891
934
  var _a;
892
- const allowedTables = writableTables(req);
935
+ const allowedTables = (0, exports.writableTables)(req);
893
936
  const requestedTables = [
894
937
  req.body.table,
895
938
  ...(((_a = req.body.duplicateConfig) === null || _a === void 0 ? void 0 : _a.foreignTables) || []).map((t) => t.table),
@@ -941,9 +984,8 @@ exports.default = (db, config) => {
941
984
  app.post("/createRecord", async function (req, res, next) {
942
985
  var _a, _b, _c;
943
986
  const view = req.config.views[req.body.viewIndex];
944
- const panel = req.body.panelIndex
945
- ? view.recordViewPanels[req.body.panelIndex]
946
- : null;
987
+ const hasPanel = req.body.panelIndex !== undefined && req.body.panelIndex !== null;
988
+ const panel = hasPanel ? view.recordViewPanels[req.body.panelIndex] : null;
947
989
  try {
948
990
  for (let key in req.body.data) {
949
991
  if (req.body.data[key] == "NULL" ||
@@ -966,10 +1008,12 @@ exports.default = (db, config) => {
966
1008
  delete createPayload[key];
967
1009
  }
968
1010
  }
969
- if (!areSafeColumnNames(createPayload)) {
1011
+ if (!(0, exports.areSafeColumnNames)(createPayload)) {
970
1012
  return res.status(400).send({ error: "Invalid column name" });
971
1013
  }
972
- const queryExpression = Object.keys(createPayload).join(", ");
1014
+ const queryExpression = Object.keys(createPayload)
1015
+ .map((x) => (0, dbManager_1.quoteIdentifier)(x, db.dbType))
1016
+ .join(", ");
973
1017
  const queryPlaceholders = Object.keys(createPayload)
974
1018
  .map((x) => "?")
975
1019
  .join(", ");
@@ -994,10 +1038,15 @@ exports.default = (db, config) => {
994
1038
  catch (e) { }
995
1039
  }
996
1040
  if (panel ? panel.createCallbackFunction : view.createCallbackFunction) {
997
- await (panel ? panel.createCallbackFunction : view.createCallbackFunction)({
998
- id: r.insertId,
999
- data: req.body.data,
1000
- });
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
+ }
1001
1050
  }
1002
1051
  res.send({
1003
1052
  id: r.insertId,
@@ -1011,25 +1060,6 @@ exports.default = (db, config) => {
1011
1060
  });
1012
1061
  }
1013
1062
  });
1014
- function collectAllowedQueries(node, acc = new Set()) {
1015
- if (!node || typeof node !== "object")
1016
- return acc;
1017
- if (Array.isArray(node)) {
1018
- for (const item of node)
1019
- collectAllowedQueries(item, acc);
1020
- return acc;
1021
- }
1022
- for (const key of Object.keys(node)) {
1023
- const value = node[key];
1024
- if (typeof value === "string" && (key === "query" || /Query$/.test(key))) {
1025
- acc.add(value.trim());
1026
- }
1027
- else if (value && typeof value === "object") {
1028
- collectAllowedQueries(value, acc);
1029
- }
1030
- }
1031
- return acc;
1032
- }
1033
1063
  const CONFIG_USES_CUSTOM_QUERY = collectAllowedQueries(config.views).size > 0;
1034
1064
  if (!CONFIG_USES_CUSTOM_QUERY) {
1035
1065
  app.get("/customQuery", (req, res) => res.sendStatus(404));
@@ -1042,7 +1072,9 @@ exports.default = (db, config) => {
1042
1072
  return res.status(403).send({ error: "Query not allowed" });
1043
1073
  }
1044
1074
  if (!/^\s*\(?\s*(select|with)\b/i.test(template) || /;/.test(template)) {
1045
- return res.status(400).send({ error: "Only read-only queries are allowed" });
1075
+ return res
1076
+ .status(400)
1077
+ .send({ error: "Only read-only queries are allowed" });
1046
1078
  }
1047
1079
  const placeholderCount = (template.match(/\?/g) || []).length;
1048
1080
  const params = new Array(placeholderCount).fill(req.query.primaryValue);
@@ -1066,7 +1098,7 @@ exports.default = (db, config) => {
1066
1098
  function uploadFile(column, fileParamName, req, res) {
1067
1099
  if (column.s3Config || column.gcpConfig) {
1068
1100
  const form = new multiparty.Form();
1069
- form.parse(req, function (err, fields, files) {
1101
+ form.parse(req, async function (err, fields, files) {
1070
1102
  if (err) {
1071
1103
  return res.status(500).send(err);
1072
1104
  }
@@ -1075,8 +1107,14 @@ exports.default = (db, config) => {
1075
1107
  const extension = originalFilename.match(/(?:\.([^.]+))?$/)[1];
1076
1108
  let aspectRatio = null;
1077
1109
  if (["png", "jpeg", "jpg", "gif"].includes(extension)) {
1078
- const dimensions = (0, image_size_1.default)(files[fileParamName][0].path);
1079
- aspectRatio = dimensions.height / dimensions.width;
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
+ }
1080
1118
  }
1081
1119
  const originalFilenameWithoutExtension = originalFilename.replace("." + extension, "");
1082
1120
  if (column.gcpConfig) {
@@ -1227,6 +1265,10 @@ exports.default = (db, config) => {
1227
1265
  }
1228
1266
  }
1229
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
+ }
1230
1272
  const fileUploadProperties = JSON.parse(req.headers["file-upload-properties"]);
1231
1273
  uploadFile(fileUploadProperties, "upload", req, res);
1232
1274
  });
@@ -1269,6 +1311,25 @@ exports.default = (db, config) => {
1269
1311
  });
1270
1312
  return app;
1271
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
+ }
1272
1333
  async function enrichConfig(config, db) {
1273
1334
  var _a, _b;
1274
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)]);