@galadrim/galadmin 0.1.230 → 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.js +122 -14
- package/dist/api.js.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utils/dbManager.js +1 -1
- package/dist/utils/dbManager.js.map +1 -1
- package/package.json +1 -1
- package/src/api.ts +168 -20
- package/src/front/ckeditor5-build/node_modules/semver/bin/semver.js +0 -0
- package/src/index.ts +5 -1
- package/src/utils/dbManager.ts +4 -1
- 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/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
package/src/api.ts
CHANGED
|
@@ -76,6 +76,100 @@ export default (db, config) => {
|
|
|
76
76
|
next();
|
|
77
77
|
});
|
|
78
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
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
next();
|
|
171
|
+
});
|
|
172
|
+
|
|
79
173
|
async function getRecords(params, computeCount = false) {
|
|
80
174
|
let queryString = "";
|
|
81
175
|
if (params.filters) {
|
|
@@ -709,6 +803,9 @@ export default (db, config) => {
|
|
|
709
803
|
|
|
710
804
|
app.post("/updateRecord", async function (req, res, next) {
|
|
711
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
|
+
}
|
|
712
809
|
for (let key in req.body.data) {
|
|
713
810
|
if (
|
|
714
811
|
req.body.data[key] == "NULL" ||
|
|
@@ -921,6 +1018,15 @@ export default (db, config) => {
|
|
|
921
1018
|
});
|
|
922
1019
|
|
|
923
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
|
+
}
|
|
924
1030
|
const primaryId = req.body.duplicateConfig.primaryId;
|
|
925
1031
|
const { err, rows } = await db.query("SELECT * FROM ?? WHERE ?? = ?", [
|
|
926
1032
|
req.body.table,
|
|
@@ -998,6 +1104,9 @@ export default (db, config) => {
|
|
|
998
1104
|
delete createPayload[key];
|
|
999
1105
|
}
|
|
1000
1106
|
}
|
|
1107
|
+
if (!areSafeColumnNames(createPayload)) {
|
|
1108
|
+
return res.status(400).send({ error: "Invalid column name" });
|
|
1109
|
+
}
|
|
1001
1110
|
const queryExpression = Object.keys(createPayload).join(", ");
|
|
1002
1111
|
const queryPlaceholders = Object.keys(createPayload)
|
|
1003
1112
|
.map((x) => "?")
|
|
@@ -1047,30 +1156,69 @@ export default (db, config) => {
|
|
|
1047
1156
|
}
|
|
1048
1157
|
});
|
|
1049
1158
|
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
if (
|
|
1057
|
-
|
|
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;
|
|
1168
|
+
}
|
|
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
|
+
}
|
|
1058
1176
|
}
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
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[
|
|
1209
|
+
req.query.panelIndex as string
|
|
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
|
+
}
|
|
1068
1216
|
}
|
|
1069
1217
|
}
|
|
1070
1218
|
}
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
}
|
|
1219
|
+
res.send(rows);
|
|
1220
|
+
});
|
|
1221
|
+
}
|
|
1074
1222
|
|
|
1075
1223
|
function uploadFile(column, fileParamName, req, res) {
|
|
1076
1224
|
if (column.s3Config || column.gcpConfig) {
|
|
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;
|
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) {
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
#!/bin/sh
|
|
2
|
-
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
|
3
|
-
|
|
4
|
-
case `uname` in
|
|
5
|
-
*CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
|
|
6
|
-
esac
|
|
7
|
-
|
|
8
|
-
if [ -x "$basedir/node" ]; then
|
|
9
|
-
exec "$basedir/node" "$basedir/../semver/bin/semver.js" "$@"
|
|
10
|
-
else
|
|
11
|
-
exec node "$basedir/../semver/bin/semver.js" "$@"
|
|
12
|
-
fi
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
@ECHO off
|
|
2
|
-
GOTO start
|
|
3
|
-
:find_dp0
|
|
4
|
-
SET dp0=%~dp0
|
|
5
|
-
EXIT /b
|
|
6
|
-
:start
|
|
7
|
-
SETLOCAL
|
|
8
|
-
CALL :find_dp0
|
|
9
|
-
|
|
10
|
-
IF EXIST "%dp0%\node.exe" (
|
|
11
|
-
SET "_prog=%dp0%\node.exe"
|
|
12
|
-
) ELSE (
|
|
13
|
-
SET "_prog=node"
|
|
14
|
-
SET PATHEXT=%PATHEXT:;.JS;=;%
|
|
15
|
-
)
|
|
16
|
-
|
|
17
|
-
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\semver\bin\semver.js" %*
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env pwsh
|
|
2
|
-
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
|
|
3
|
-
|
|
4
|
-
$exe=""
|
|
5
|
-
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
|
|
6
|
-
# Fix case when both the Windows and Linux builds of Node
|
|
7
|
-
# are installed in the same directory
|
|
8
|
-
$exe=".exe"
|
|
9
|
-
}
|
|
10
|
-
$ret=0
|
|
11
|
-
if (Test-Path "$basedir/node$exe") {
|
|
12
|
-
# Support pipeline input
|
|
13
|
-
if ($MyInvocation.ExpectingInput) {
|
|
14
|
-
$input | & "$basedir/node$exe" "$basedir/../semver/bin/semver.js" $args
|
|
15
|
-
} else {
|
|
16
|
-
& "$basedir/node$exe" "$basedir/../semver/bin/semver.js" $args
|
|
17
|
-
}
|
|
18
|
-
$ret=$LASTEXITCODE
|
|
19
|
-
} else {
|
|
20
|
-
# Support pipeline input
|
|
21
|
-
if ($MyInvocation.ExpectingInput) {
|
|
22
|
-
$input | & "node$exe" "$basedir/../semver/bin/semver.js" $args
|
|
23
|
-
} else {
|
|
24
|
-
& "node$exe" "$basedir/../semver/bin/semver.js" $args
|
|
25
|
-
}
|
|
26
|
-
$ret=$LASTEXITCODE
|
|
27
|
-
}
|
|
28
|
-
exit $ret
|