@galadrim/galadmin 0.1.228 → 0.1.232
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/dist/api.d.ts +8 -1
- package/dist/api.js +166 -35
- package/dist/api.js.map +1 -1
- package/dist/front/ckeditor5-build/src/ckeditor.d.ts +1 -1
- package/dist/index.js +3 -23
- package/dist/index.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/config.d.ts +1 -0
- package/dist/utils/dbManager.js +1 -1
- package/dist/utils/dbManager.js.map +1 -1
- package/package.json +3 -2
- package/src/api.ts +255 -64
- package/src/front/ckeditor5-build/node_modules/semver/bin/semver.js +0 -0
- package/src/index.ts +6 -34
- package/src/types/config.ts +3 -0
- package/src/utils/dbManager.ts +4 -1
- package/tsconfig.json +1 -0
- package/src/front/ckeditor5-build/node_modules/.bin/semver +0 -12
- package/src/front/ckeditor5-build/node_modules/.bin/semver.cmd +0 -17
- package/src/front/ckeditor5-build/node_modules/.bin/semver.ps1 +0 -28
package/dist/types/config.d.ts
CHANGED
package/dist/utils/dbManager.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dbManager.js","sourceRoot":"","sources":["../../src/utils/dbManager.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"dbManager.js","sourceRoot":"","sources":["../../src/utils/dbManager.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@galadrim/galadmin",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.232",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
@@ -41,7 +41,8 @@
|
|
|
41
41
|
"@types/express": "^4.17.13",
|
|
42
42
|
"@types/express-session": "1.17.0",
|
|
43
43
|
"@types/mime": "^2.0.3",
|
|
44
|
-
"@types/node": "^16.18.11"
|
|
44
|
+
"@types/node": "^16.18.11",
|
|
45
|
+
"typescript": "^5.9.3"
|
|
45
46
|
},
|
|
46
47
|
"repository": {
|
|
47
48
|
"type": "git",
|
package/src/api.ts
CHANGED
|
@@ -23,13 +23,150 @@ function generateUniqueIdForObject(obj) {
|
|
|
23
23
|
return hash;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
declare global {
|
|
27
|
+
namespace Express {
|
|
28
|
+
interface Request {
|
|
29
|
+
config?: any;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default (db, config) => {
|
|
27
35
|
const app = express();
|
|
28
36
|
|
|
37
|
+
app.use((req, res, next) => {
|
|
38
|
+
req.config = {
|
|
39
|
+
...config,
|
|
40
|
+
views: config.views
|
|
41
|
+
.filter(x => !x.roles
|
|
42
|
+
|| (req.session.user && ([...(x.roles || []), ...(x.readRoles || [])].includes(req.session.user.role)))
|
|
43
|
+
)
|
|
44
|
+
.map(view => {
|
|
45
|
+
const readOnly = req.session.user && view.roles && !view.roles.includes(req.session.user.role)
|
|
46
|
+
if (readOnly) {
|
|
47
|
+
return {
|
|
48
|
+
...view,
|
|
49
|
+
canEdit: false,
|
|
50
|
+
canCreate: false,
|
|
51
|
+
canDelete: false,
|
|
52
|
+
clickToEdit: false
|
|
53
|
+
}
|
|
54
|
+
} else {
|
|
55
|
+
return view;
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
.map(view => {
|
|
59
|
+
if (view.type === "table") {
|
|
60
|
+
return {
|
|
61
|
+
...view,
|
|
62
|
+
columns: view.columns.filter(column => !column.roles || (req.session.user && column.roles.includes(req.session.user.role)))
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
return view;
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
next();
|
|
70
|
+
});
|
|
71
|
+
|
|
29
72
|
app.use(async (req, res, next) => {
|
|
30
|
-
if (!config.enriched) {
|
|
31
|
-
await enrichConfig(config, db);
|
|
73
|
+
if (!req.config.enriched) {
|
|
74
|
+
await enrichConfig(req.config, db);
|
|
75
|
+
}
|
|
76
|
+
next();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
// SECURITY: server-side authorization.
|
|
81
|
+
//
|
|
82
|
+
// The config-filtering middleware above only decides which views the *client*
|
|
83
|
+
// is shown, and sets canEdit/canCreate/canDelete flags on that served config.
|
|
84
|
+
// Nothing enforced those decisions server-side, so a session with the lowest
|
|
85
|
+
// role could POST directly to /updateRecord, /deleteRecord, /customQuery, …
|
|
86
|
+
// and act on any view — including read-only ones — as the DB user (root).
|
|
87
|
+
// This block re-checks every sensitive route against the view's declared roles.
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
const ROOT_ROLE = "root";
|
|
90
|
+
const roleOf = (req) => req.session && req.session.user && req.session.user.role;
|
|
91
|
+
// Write access = the view's `roles` (root always allowed).
|
|
92
|
+
const canWriteView = (view, role) =>
|
|
93
|
+
role === ROOT_ROLE || !view || !view.roles || view.roles.includes(role);
|
|
94
|
+
|
|
95
|
+
const viewFromRequest = (req) => {
|
|
96
|
+
const idx =
|
|
97
|
+
req.body && req.body.viewIndex !== undefined
|
|
98
|
+
? req.body.viewIndex
|
|
99
|
+
: req.query && req.query.viewIndex !== undefined
|
|
100
|
+
? req.query.viewIndex
|
|
101
|
+
: undefined;
|
|
102
|
+
if (idx === undefined || !req.config || !req.config.views) return undefined;
|
|
103
|
+
return req.config.views[idx];
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// Routes that mutate data and reference their target view via viewIndex
|
|
107
|
+
// (body for POST bodies, query for /uploadFile). Import routes carry the
|
|
108
|
+
// viewIndex inside a multipart body and are guarded inline in their handlers.
|
|
109
|
+
const MUTATING_VIEW_ROUTES = new Set([
|
|
110
|
+
"/updateRecord",
|
|
111
|
+
"/createRecord",
|
|
112
|
+
"/deleteRecord",
|
|
113
|
+
"/deleteRecords",
|
|
114
|
+
"/deleteRecordFromPanel",
|
|
115
|
+
"/customAction",
|
|
116
|
+
"/uploadFile",
|
|
117
|
+
]);
|
|
118
|
+
// /duplicateRecord references its table via req.body.table (not a viewIndex),
|
|
119
|
+
// so it is guarded inline in its handler instead of by the generic middleware.
|
|
120
|
+
|
|
121
|
+
// Column names are interpolated into INSERT/UPDATE statements; only accept plain
|
|
122
|
+
// SQL identifiers so a crafted key cannot break out of the column list.
|
|
123
|
+
const areSafeColumnNames = (data) =>
|
|
124
|
+
!data ||
|
|
125
|
+
Object.keys(data).every((k) => /^[A-Za-z0-9_]+$/.test(k));
|
|
126
|
+
|
|
127
|
+
// Tables the current role is allowed to write, gathered from its writable views
|
|
128
|
+
// and their creatable panels — used to bound /duplicateRecord's target table.
|
|
129
|
+
const writableTables = (req) => {
|
|
130
|
+
const role = roleOf(req);
|
|
131
|
+
const tables = new Set<string>();
|
|
132
|
+
for (const view of req.config?.views || []) {
|
|
133
|
+
if (!canWriteView(view, role)) continue;
|
|
134
|
+
if (view.tableName) tables.add(view.tableName);
|
|
135
|
+
for (const panel of view.recordViewPanels || []) {
|
|
136
|
+
if (panel.tableName) tables.add(panel.tableName);
|
|
137
|
+
if (panel.createConfig?.tableName) tables.add(panel.createConfig.tableName);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return tables;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// Reject client-supplied SQL fragments (the `filters` array is concatenated
|
|
144
|
+
// raw into the WHERE clause). With multipleStatements off this already blocks
|
|
145
|
+
// stacked writes; the pattern also blocks UNION/comment/file exfil breakouts.
|
|
146
|
+
const looksLikeSqlBreakout = (frag) =>
|
|
147
|
+
typeof frag === "string" &&
|
|
148
|
+
/(;|--|\/\*|\bunion\b|\binto\s+(out|dump)file\b|\bsleep\s*\(|\bbenchmark\s*\(|\bload_file\s*\()/i.test(
|
|
149
|
+
frag
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
app.use((req, res, next) => {
|
|
153
|
+
const role = roleOf(req);
|
|
154
|
+
|
|
155
|
+
// 1) Validate any raw filter fragments on read endpoints.
|
|
156
|
+
if (req.body && Array.isArray(req.body.filters)) {
|
|
157
|
+
if (req.body.filters.some(looksLikeSqlBreakout)) {
|
|
158
|
+
return res.status(400).send({ error: "Invalid filter" });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// 2) Enforce write authorization on mutating routes.
|
|
163
|
+
if (MUTATING_VIEW_ROUTES.has(req.path)) {
|
|
164
|
+
const view = viewFromRequest(req);
|
|
165
|
+
if (!view || !canWriteView(view, role)) {
|
|
166
|
+
return res.sendStatus(403);
|
|
167
|
+
}
|
|
32
168
|
}
|
|
169
|
+
|
|
33
170
|
next();
|
|
34
171
|
});
|
|
35
172
|
|
|
@@ -124,7 +261,7 @@ export default (db, config, user) => {
|
|
|
124
261
|
|
|
125
262
|
app.get("/config", async (req, res) => {
|
|
126
263
|
try {
|
|
127
|
-
const { mysql, auth, sessionSecret, ...safeConfig } = config;
|
|
264
|
+
const { mysql, auth, sessionSecret, ...safeConfig } = req.config;
|
|
128
265
|
res.send({
|
|
129
266
|
config: serializeJs(safeConfig),
|
|
130
267
|
});
|
|
@@ -201,7 +338,7 @@ export default (db, config, user) => {
|
|
|
201
338
|
};
|
|
202
339
|
|
|
203
340
|
app.post("/foreignSelectRecords", async (req, res, next) => {
|
|
204
|
-
const view = config.views[req.body.viewIndex];
|
|
341
|
+
const view = req.config.views[req.body.viewIndex];
|
|
205
342
|
const column = [
|
|
206
343
|
...view.columns,
|
|
207
344
|
...(view.recordViewPanels
|
|
@@ -222,7 +359,7 @@ export default (db, config, user) => {
|
|
|
222
359
|
? `${column.searchableName} LIKE '${column.wholeSearch ? "%" : ""}${queryEscaped}%'`
|
|
223
360
|
: null,
|
|
224
361
|
column.filtersFromUser
|
|
225
|
-
? column.filtersFromUser(user.userId, user.role)
|
|
362
|
+
? column.filtersFromUser(req.session.user.userId, req.session.user.role)
|
|
226
363
|
: null,
|
|
227
364
|
]),
|
|
228
365
|
join: column.join,
|
|
@@ -256,7 +393,7 @@ export default (db, config, user) => {
|
|
|
256
393
|
});
|
|
257
394
|
|
|
258
395
|
app.post("/records", async function (req, res, next) {
|
|
259
|
-
const view = config.views[req.body.viewIndex];
|
|
396
|
+
const view = req.config.views[req.body.viewIndex];
|
|
260
397
|
const isDesc = req.body.isDesc === null ? view.isDesc : req.body.isDesc;
|
|
261
398
|
const subview =
|
|
262
399
|
view.subviews && req.body.subviewIndex !== undefined
|
|
@@ -277,7 +414,7 @@ export default (db, config, user) => {
|
|
|
277
414
|
subview?.filters,
|
|
278
415
|
...req.body.filters,
|
|
279
416
|
view.filtersFromUser
|
|
280
|
-
? view.filtersFromUser(user.userId, user.role)
|
|
417
|
+
? view.filtersFromUser(req.session.user.userId, req.session.user.role)
|
|
281
418
|
: null,
|
|
282
419
|
]),
|
|
283
420
|
having: subview?.having,
|
|
@@ -352,11 +489,11 @@ export default (db, config, user) => {
|
|
|
352
489
|
" = " +
|
|
353
490
|
(typeof data[key] === "string"
|
|
354
491
|
? "'" +
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
492
|
+
data[key]
|
|
493
|
+
.replace(/'/g, "\\'")
|
|
494
|
+
.replace(/\n/g, "")
|
|
495
|
+
.replace(/\r/g, "") +
|
|
496
|
+
"'"
|
|
360
497
|
: typeof data[key] === "object"
|
|
361
498
|
? JSON.stringify(data[key])
|
|
362
499
|
: data[key]) +
|
|
@@ -400,7 +537,7 @@ export default (db, config, user) => {
|
|
|
400
537
|
}
|
|
401
538
|
|
|
402
539
|
app.post("/recordsExport", async function (req, res) {
|
|
403
|
-
const view = config.views[req.body.viewIndex];
|
|
540
|
+
const view = req.config.views[req.body.viewIndex];
|
|
404
541
|
const orderBy = req.body.orderBy || view.orderBy;
|
|
405
542
|
const isDesc = req.body.isDesc === null ? view.isDesc : req.body.isDesc;
|
|
406
543
|
const subview =
|
|
@@ -462,7 +599,7 @@ export default (db, config, user) => {
|
|
|
462
599
|
return res.status(500).send(err);
|
|
463
600
|
}
|
|
464
601
|
// Get viewIndex in fields
|
|
465
|
-
const view = config.views[fields.viewIndex[0]];
|
|
602
|
+
const view = req.config.views[fields.viewIndex[0]];
|
|
466
603
|
if (typeof view.parseImportFile !== "function") {
|
|
467
604
|
return res
|
|
468
605
|
.status(500)
|
|
@@ -553,7 +690,7 @@ export default (db, config, user) => {
|
|
|
553
690
|
return res.status(500).send(err);
|
|
554
691
|
}
|
|
555
692
|
// Get viewIndex and panelIndex in fields
|
|
556
|
-
const view = config.views[fields.viewIndex[0]];
|
|
693
|
+
const view = req.config.views[fields.viewIndex[0]];
|
|
557
694
|
const panel = view.recordViewPanels[fields.panelIndex[0]];
|
|
558
695
|
if (typeof panel.parseImportFile !== "function") {
|
|
559
696
|
return res
|
|
@@ -634,13 +771,13 @@ export default (db, config, user) => {
|
|
|
634
771
|
});
|
|
635
772
|
|
|
636
773
|
app.get("/record", async function (req, res) {
|
|
637
|
-
const view = config.views[req.query.viewIndex as string];
|
|
774
|
+
const view = req.config.views[req.query.viewIndex as string];
|
|
638
775
|
const { err, rows } = await db.query(
|
|
639
776
|
"SELECT " +
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
777
|
+
(view.selectExpression || "*") +
|
|
778
|
+
" FROM ?? " +
|
|
779
|
+
(view.join || "") +
|
|
780
|
+
" WHERE ?? = ?",
|
|
644
781
|
[
|
|
645
782
|
view.tableName,
|
|
646
783
|
view.tableName + "." + view.primaryId,
|
|
@@ -665,7 +802,10 @@ export default (db, config, user) => {
|
|
|
665
802
|
});
|
|
666
803
|
|
|
667
804
|
app.post("/updateRecord", async function (req, res, next) {
|
|
668
|
-
const view = config.views[req.body.viewIndex];
|
|
805
|
+
const view = req.config.views[req.body.viewIndex];
|
|
806
|
+
if (!areSafeColumnNames(req.body.data)) {
|
|
807
|
+
return res.status(400).send({ error: "Invalid column name" });
|
|
808
|
+
}
|
|
669
809
|
for (let key in req.body.data) {
|
|
670
810
|
if (
|
|
671
811
|
req.body.data[key] == "NULL" ||
|
|
@@ -703,7 +843,7 @@ export default (db, config, user) => {
|
|
|
703
843
|
data: req.body.data,
|
|
704
844
|
previousData: req.body.previousData,
|
|
705
845
|
});
|
|
706
|
-
} catch (e) {}
|
|
846
|
+
} catch (e) { }
|
|
707
847
|
}
|
|
708
848
|
if (view.editCallbackFunction) {
|
|
709
849
|
await view.editCallbackFunction({
|
|
@@ -715,20 +855,20 @@ export default (db, config, user) => {
|
|
|
715
855
|
});
|
|
716
856
|
|
|
717
857
|
app.post("/customAction", async function (req, res, next) {
|
|
718
|
-
const view = config.views[req.body.viewIndex];
|
|
858
|
+
const view = req.config.views[req.body.viewIndex];
|
|
719
859
|
const action = view.customActions[req.body.actionIndex];
|
|
720
860
|
for (const recordId of req.body.selectedRecordIds) {
|
|
721
861
|
try {
|
|
722
862
|
const { rows } = await db.query(
|
|
723
863
|
"SELECT " +
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
864
|
+
(view.selectExpression || "*") +
|
|
865
|
+
" FROM ?? " +
|
|
866
|
+
(view.join || "") +
|
|
867
|
+
" WHERE ?? = ?",
|
|
728
868
|
[view.tableName, view.tableName + "." + view.primaryId, recordId]
|
|
729
869
|
);
|
|
730
870
|
const record = rows[0];
|
|
731
|
-
const actionResult = await action.action(record, user.userId);
|
|
871
|
+
const actionResult = await action.action(record, req.session.user.userId);
|
|
732
872
|
if (actionResult) {
|
|
733
873
|
return res.send({
|
|
734
874
|
success: true,
|
|
@@ -750,13 +890,13 @@ export default (db, config, user) => {
|
|
|
750
890
|
});
|
|
751
891
|
|
|
752
892
|
app.post("/deleteRecord", async function (req, res) {
|
|
753
|
-
const view = config.views[req.body.viewIndex];
|
|
893
|
+
const view = req.config.views[req.body.viewIndex];
|
|
754
894
|
const { rows, err: err2 } = await db.query(
|
|
755
895
|
"SELECT " +
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
896
|
+
(view.selectExpression || "*") +
|
|
897
|
+
" FROM ?? " +
|
|
898
|
+
(view.join || "") +
|
|
899
|
+
" WHERE ?? = ?",
|
|
760
900
|
[
|
|
761
901
|
view.tableName,
|
|
762
902
|
view.tableName + "." + view.primaryId,
|
|
@@ -783,7 +923,7 @@ export default (db, config, user) => {
|
|
|
783
923
|
await axios.post(view.deleteCallback, {
|
|
784
924
|
id: req.body.primaryValue,
|
|
785
925
|
});
|
|
786
|
-
} catch (e) {}
|
|
926
|
+
} catch (e) { }
|
|
787
927
|
}
|
|
788
928
|
if (view.deleteCallbackFunction) {
|
|
789
929
|
await view.deleteCallbackFunction({
|
|
@@ -795,7 +935,7 @@ export default (db, config, user) => {
|
|
|
795
935
|
});
|
|
796
936
|
|
|
797
937
|
app.post("/deleteRecordFromPanel", async function (req, res) {
|
|
798
|
-
const view = config.views[req.body.viewIndex];
|
|
938
|
+
const view = req.config.views[req.body.viewIndex];
|
|
799
939
|
const panel = view.recordViewPanels.find(
|
|
800
940
|
(x) => x.__id__ === req.body.panelId
|
|
801
941
|
);
|
|
@@ -820,7 +960,7 @@ export default (db, config, user) => {
|
|
|
820
960
|
await axios.post(panel.deleteCallback, {
|
|
821
961
|
id: req.body.primaryValue,
|
|
822
962
|
});
|
|
823
|
-
} catch (e) {}
|
|
963
|
+
} catch (e) { }
|
|
824
964
|
}
|
|
825
965
|
if (panel.deleteCallbackFunction) {
|
|
826
966
|
await panel.deleteCallbackFunction({
|
|
@@ -832,7 +972,7 @@ export default (db, config, user) => {
|
|
|
832
972
|
});
|
|
833
973
|
|
|
834
974
|
app.post("/deleteRecords", async function (req, res, next) {
|
|
835
|
-
const view = config.views[req.body.viewIndex];
|
|
975
|
+
const view = req.config.views[req.body.viewIndex];
|
|
836
976
|
const { err } = await db.query(
|
|
837
977
|
`DELETE FROM ?? WHERE ?? IN(${req.body.primaryValues.map((x) => "'" + x + "'").join(",")})`,
|
|
838
978
|
[view.tableName, view.primaryId]
|
|
@@ -866,7 +1006,7 @@ export default (db, config, user) => {
|
|
|
866
1006
|
}
|
|
867
1007
|
}
|
|
868
1008
|
|
|
869
|
-
for (const view of config.views) {
|
|
1009
|
+
for (const view of req.config.views) {
|
|
870
1010
|
await checkAndGetNotificationBadge(view);
|
|
871
1011
|
if (view.subviews) {
|
|
872
1012
|
for (const subview of view.subviews) {
|
|
@@ -878,6 +1018,15 @@ export default (db, config, user) => {
|
|
|
878
1018
|
});
|
|
879
1019
|
|
|
880
1020
|
app.post("/duplicateRecord", async function (req, res, next) {
|
|
1021
|
+
// SECURITY: bound the (client-supplied) target table to tables the role may write.
|
|
1022
|
+
const allowedTables = writableTables(req);
|
|
1023
|
+
const requestedTables = [
|
|
1024
|
+
req.body.table,
|
|
1025
|
+
...(req.body.duplicateConfig?.foreignTables || []).map((t) => t.table),
|
|
1026
|
+
];
|
|
1027
|
+
if (requestedTables.some((t) => !allowedTables.has(t))) {
|
|
1028
|
+
return res.sendStatus(403);
|
|
1029
|
+
}
|
|
881
1030
|
const primaryId = req.body.duplicateConfig.primaryId;
|
|
882
1031
|
const { err, rows } = await db.query("SELECT * FROM ?? WHERE ?? = ?", [
|
|
883
1032
|
req.body.table,
|
|
@@ -923,7 +1072,7 @@ export default (db, config, user) => {
|
|
|
923
1072
|
});
|
|
924
1073
|
|
|
925
1074
|
app.post("/createRecord", async function (req, res, next) {
|
|
926
|
-
const view = config.views[req.body.viewIndex];
|
|
1075
|
+
const view = req.config.views[req.body.viewIndex];
|
|
927
1076
|
const panel = req.body.panelIndex
|
|
928
1077
|
? view.recordViewPanels[req.body.panelIndex]
|
|
929
1078
|
: null;
|
|
@@ -955,6 +1104,9 @@ export default (db, config, user) => {
|
|
|
955
1104
|
delete createPayload[key];
|
|
956
1105
|
}
|
|
957
1106
|
}
|
|
1107
|
+
if (!areSafeColumnNames(createPayload)) {
|
|
1108
|
+
return res.status(400).send({ error: "Invalid column name" });
|
|
1109
|
+
}
|
|
958
1110
|
const queryExpression = Object.keys(createPayload).join(", ");
|
|
959
1111
|
const queryPlaceholders = Object.keys(createPayload)
|
|
960
1112
|
.map((x) => "?")
|
|
@@ -982,7 +1134,7 @@ export default (db, config, user) => {
|
|
|
982
1134
|
id: r.insertId,
|
|
983
1135
|
data: req.body.data,
|
|
984
1136
|
});
|
|
985
|
-
} catch (e) {}
|
|
1137
|
+
} catch (e) { }
|
|
986
1138
|
}
|
|
987
1139
|
if (panel ? panel.createCallbackFunction : view.createCallbackFunction) {
|
|
988
1140
|
await (
|
|
@@ -1004,30 +1156,69 @@ export default (db, config, user) => {
|
|
|
1004
1156
|
}
|
|
1005
1157
|
});
|
|
1006
1158
|
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
if (
|
|
1014
|
-
|
|
1159
|
+
// Collect every SQL template declared anywhere in the (role-filtered) config:
|
|
1160
|
+
// panel `query`, `autocompleteListQuery`, etc. customQuery may only run a
|
|
1161
|
+
// template that already exists in the config the caller is allowed to see —
|
|
1162
|
+
// so it can never be turned into an arbitrary read/write primitive.
|
|
1163
|
+
function collectAllowedQueries(node, acc = new Set<string>()) {
|
|
1164
|
+
if (!node || typeof node !== "object") return acc;
|
|
1165
|
+
if (Array.isArray(node)) {
|
|
1166
|
+
for (const item of node) collectAllowedQueries(item, acc);
|
|
1167
|
+
return acc;
|
|
1015
1168
|
}
|
|
1016
|
-
|
|
1017
|
-
const
|
|
1018
|
-
|
|
1169
|
+
for (const key of Object.keys(node)) {
|
|
1170
|
+
const value = node[key];
|
|
1171
|
+
if (typeof value === "string" && (key === "query" || /Query$/.test(key))) {
|
|
1172
|
+
acc.add(value.trim());
|
|
1173
|
+
} else if (value && typeof value === "object") {
|
|
1174
|
+
collectAllowedQueries(value, acc);
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
return acc;
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
// Attack-surface reduction: only expose /customQuery at all when the config
|
|
1181
|
+
// actually declares SQL templates (panels, autocomplete lists, …). If nothing
|
|
1182
|
+
// uses it, the route is replaced by an explicit "disabled" stub so the raw-SQL
|
|
1183
|
+
// sink simply does not exist for that deployment.
|
|
1184
|
+
const CONFIG_USES_CUSTOM_QUERY = collectAllowedQueries(config.views).size > 0;
|
|
1185
|
+
|
|
1186
|
+
if (!CONFIG_USES_CUSTOM_QUERY) {
|
|
1187
|
+
app.get("/customQuery", (req, res) => res.sendStatus(404));
|
|
1188
|
+
} else {
|
|
1189
|
+
app.get("/customQuery", async function (req, res, next) {
|
|
1190
|
+
const template = typeof req.query.query === "string" ? req.query.query : "";
|
|
1191
|
+
// SECURITY: only run whitelisted, read-only templates, and bind the record id
|
|
1192
|
+
// as a parameter instead of substituting it into the SQL string.
|
|
1193
|
+
const allowed = collectAllowedQueries(req.config.views);
|
|
1194
|
+
if (!allowed.has(template.trim())) {
|
|
1195
|
+
return res.status(403).send({ error: "Query not allowed" });
|
|
1196
|
+
}
|
|
1197
|
+
if (!/^\s*\(?\s*(select|with)\b/i.test(template) || /;/.test(template)) {
|
|
1198
|
+
return res.status(400).send({ error: "Only read-only queries are allowed" });
|
|
1199
|
+
}
|
|
1200
|
+
const placeholderCount = (template.match(/\?/g) || []).length;
|
|
1201
|
+
const params = new Array(placeholderCount).fill(req.query.primaryValue);
|
|
1202
|
+
const { err, rows } = await db.query(template, params);
|
|
1203
|
+
if (err) {
|
|
1204
|
+
return res.status(500).send(err);
|
|
1205
|
+
}
|
|
1206
|
+
if (req.query.viewIndex && req.query.panelIndex) {
|
|
1207
|
+
const panel =
|
|
1208
|
+
req.config.views[req.query.viewIndex as string].recordViewPanels[
|
|
1019
1209
|
req.query.panelIndex as string
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1210
|
+
];
|
|
1211
|
+
if (panel.formatValue) {
|
|
1212
|
+
for (const row of rows) {
|
|
1213
|
+
for (const key in panel.formatValue) {
|
|
1214
|
+
row[key] = await panel.formatValue[key](row);
|
|
1215
|
+
}
|
|
1025
1216
|
}
|
|
1026
1217
|
}
|
|
1027
1218
|
}
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
}
|
|
1219
|
+
res.send(rows);
|
|
1220
|
+
});
|
|
1221
|
+
}
|
|
1031
1222
|
|
|
1032
1223
|
function uploadFile(column, fileParamName, req, res) {
|
|
1033
1224
|
if (column.s3Config || column.gcpConfig) {
|
|
@@ -1171,9 +1362,9 @@ export default (db, config, user) => {
|
|
|
1171
1362
|
await sftp.fastPut(
|
|
1172
1363
|
files[fileParamName][0].path,
|
|
1173
1364
|
column.publicPath +
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1365
|
+
(column.subdirectory ? "/" + column.subdirectory : "") +
|
|
1366
|
+
"/" +
|
|
1367
|
+
filename
|
|
1177
1368
|
);
|
|
1178
1369
|
await sftp.end();
|
|
1179
1370
|
res.send({
|
|
@@ -1211,7 +1402,7 @@ export default (db, config, user) => {
|
|
|
1211
1402
|
);
|
|
1212
1403
|
|
|
1213
1404
|
app.post("/uploadFile", async function (req, res, next) {
|
|
1214
|
-
const view = config.views[req.query.viewIndex as string];
|
|
1405
|
+
const view = req.config.views[req.query.viewIndex as string];
|
|
1215
1406
|
const column = [
|
|
1216
1407
|
...view.columns,
|
|
1217
1408
|
...(view.recordViewPanels
|
|
File without changes
|
package/src/index.ts
CHANGED
|
@@ -130,7 +130,11 @@ export default (config: Config): any => {
|
|
|
130
130
|
// we check that the user still exists
|
|
131
131
|
let stillExists = false;
|
|
132
132
|
for (const authRole of config.authRoles) {
|
|
133
|
-
|
|
133
|
+
// Re-apply the role's `filters` here too, so an account that becomes
|
|
134
|
+
// ineligible (e.g. deactivated) has its existing session invalidated
|
|
135
|
+
// on its next request — not just blocked at login time.
|
|
136
|
+
const filters = authRole.filters ? `AND ${authRole.filters}` : "";
|
|
137
|
+
const { rows } = await db.query(`SELECT * FROM ?? WHERE ?? = ? ${filters}`, [authRole.table, authRole.idField, req.session.user.userId]);
|
|
134
138
|
if (rows.length > 0) {
|
|
135
139
|
stillExists = true;
|
|
136
140
|
break;
|
|
@@ -151,39 +155,7 @@ export default (config: Config): any => {
|
|
|
151
155
|
}
|
|
152
156
|
})
|
|
153
157
|
|
|
154
|
-
app.use("/api", (
|
|
155
|
-
return api(db, {
|
|
156
|
-
...config,
|
|
157
|
-
views: config.views
|
|
158
|
-
.filter(x => !x.roles
|
|
159
|
-
|| (req.session.user && ([...(x.roles || []), ...(x.readRoles || [])].includes(req.session.user.role)))
|
|
160
|
-
)
|
|
161
|
-
.map(view => {
|
|
162
|
-
const readOnly = req.session.user && view.roles && !view.roles.includes(req.session.user.role)
|
|
163
|
-
if (readOnly) {
|
|
164
|
-
return {
|
|
165
|
-
...view,
|
|
166
|
-
canEdit: false,
|
|
167
|
-
canCreate: false,
|
|
168
|
-
canDelete: false,
|
|
169
|
-
clickToEdit: false
|
|
170
|
-
}
|
|
171
|
-
} else {
|
|
172
|
-
return view;
|
|
173
|
-
}
|
|
174
|
-
})
|
|
175
|
-
.map(view => {
|
|
176
|
-
if (view.type === "table") {
|
|
177
|
-
return {
|
|
178
|
-
...view,
|
|
179
|
-
columns: view.columns.filter(column => !column.roles || (req.session.user && column.roles.includes(req.session.user.role)))
|
|
180
|
-
}
|
|
181
|
-
} else {
|
|
182
|
-
return view;
|
|
183
|
-
}
|
|
184
|
-
})
|
|
185
|
-
}, req.session.user)(req, res, next)
|
|
186
|
-
})
|
|
158
|
+
app.use("/api", api(db, config))
|
|
187
159
|
|
|
188
160
|
// Serve static files
|
|
189
161
|
app.get("*", (req, res) => {
|
package/src/types/config.ts
CHANGED
package/src/utils/dbManager.ts
CHANGED
|
@@ -89,7 +89,10 @@ export default class dbManager {
|
|
|
89
89
|
port: config.mysql.port || 3306,
|
|
90
90
|
charset: 'UTF8MB4_GENERAL_CI',
|
|
91
91
|
dateStrings: true,
|
|
92
|
-
|
|
92
|
+
// SECURITY: multi-statement queries let any SQL-injection escalate to stacked
|
|
93
|
+
// writes / DROP. Keep OFF by default; opt in explicitly only if a config truly
|
|
94
|
+
// relies on `;`-separated statements (none of galadmin's internal queries do).
|
|
95
|
+
multipleStatements: config.mysql.multipleStatements === true
|
|
93
96
|
});
|
|
94
97
|
this.dbType = "MYSQL";
|
|
95
98
|
} else if (config.postgresql) {
|