@galadrim/galadmin 0.1.230 → 0.1.233

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.
@@ -74,7 +74,7 @@ class dbManager {
74
74
  port: config.mysql.port || 3306,
75
75
  charset: 'UTF8MB4_GENERAL_CI',
76
76
  dateStrings: true,
77
- multipleStatements: true
77
+ multipleStatements: config.mysql.multipleStatements === true
78
78
  });
79
79
  this.dbType = "MYSQL";
80
80
  }
@@ -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.230",
3
+ "version": "0.1.233",
4
4
  "main": "dist/index.js",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1",
@@ -16,8 +16,9 @@
16
16
  "@google-cloud/storage": "^7.17.2",
17
17
  "@rollup/plugin-babel": "^5.3.0",
18
18
  "@rollup/plugin-node-resolve": "^13.0.4",
19
+ "@aws-sdk/client-s3": "^3.1114.0",
20
+ "@aws-sdk/s3-request-presigner": "^3.1114.0",
19
21
  "argon2": "^0.31.2",
20
- "aws-sdk": "^2.958.0",
21
22
  "axios": "^0.28.1",
22
23
  "bcryptjs": "^2.4.3",
23
24
  "connect-pg-simple": "^9.0.1",
package/src/api.ts CHANGED
@@ -5,7 +5,8 @@ import * as path from "path";
5
5
  import * as crypto from "crypto";
6
6
  import * as mime from "mime";
7
7
 
8
- import { S3, Endpoint } from "aws-sdk";
8
+ import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
9
+ import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
9
10
  import { Storage } from "@google-cloud/storage";
10
11
  import sizeOf from "image-size";
11
12
  import * as serializeJs from "serialize-javascript";
@@ -76,6 +77,100 @@ export default (db, config) => {
76
77
  next();
77
78
  });
78
79
 
80
+ // ---------------------------------------------------------------------------
81
+ // SECURITY: server-side authorization.
82
+ //
83
+ // The config-filtering middleware above only decides which views the *client*
84
+ // is shown, and sets canEdit/canCreate/canDelete flags on that served config.
85
+ // Nothing enforced those decisions server-side, so a session with the lowest
86
+ // role could POST directly to /updateRecord, /deleteRecord, /customQuery, …
87
+ // and act on any view — including read-only ones — as the DB user (root).
88
+ // This block re-checks every sensitive route against the view's declared roles.
89
+ // ---------------------------------------------------------------------------
90
+ const ROOT_ROLE = "root";
91
+ const roleOf = (req) => req.session && req.session.user && req.session.user.role;
92
+ // Write access = the view's `roles` (root always allowed).
93
+ const canWriteView = (view, role) =>
94
+ role === ROOT_ROLE || !view || !view.roles || view.roles.includes(role);
95
+
96
+ const viewFromRequest = (req) => {
97
+ const idx =
98
+ req.body && req.body.viewIndex !== undefined
99
+ ? req.body.viewIndex
100
+ : req.query && req.query.viewIndex !== undefined
101
+ ? req.query.viewIndex
102
+ : undefined;
103
+ if (idx === undefined || !req.config || !req.config.views) return undefined;
104
+ return req.config.views[idx];
105
+ };
106
+
107
+ // Routes that mutate data and reference their target view via viewIndex
108
+ // (body for POST bodies, query for /uploadFile). Import routes carry the
109
+ // viewIndex inside a multipart body and are guarded inline in their handlers.
110
+ const MUTATING_VIEW_ROUTES = new Set([
111
+ "/updateRecord",
112
+ "/createRecord",
113
+ "/deleteRecord",
114
+ "/deleteRecords",
115
+ "/deleteRecordFromPanel",
116
+ "/customAction",
117
+ "/uploadFile",
118
+ ]);
119
+ // /duplicateRecord references its table via req.body.table (not a viewIndex),
120
+ // so it is guarded inline in its handler instead of by the generic middleware.
121
+
122
+ // Column names are interpolated into INSERT/UPDATE statements; only accept plain
123
+ // SQL identifiers so a crafted key cannot break out of the column list.
124
+ const areSafeColumnNames = (data) =>
125
+ !data ||
126
+ Object.keys(data).every((k) => /^[A-Za-z0-9_]+$/.test(k));
127
+
128
+ // Tables the current role is allowed to write, gathered from its writable views
129
+ // and their creatable panels — used to bound /duplicateRecord's target table.
130
+ const writableTables = (req) => {
131
+ const role = roleOf(req);
132
+ const tables = new Set<string>();
133
+ for (const view of req.config?.views || []) {
134
+ if (!canWriteView(view, role)) continue;
135
+ if (view.tableName) tables.add(view.tableName);
136
+ for (const panel of view.recordViewPanels || []) {
137
+ if (panel.tableName) tables.add(panel.tableName);
138
+ if (panel.createConfig?.tableName) tables.add(panel.createConfig.tableName);
139
+ }
140
+ }
141
+ return tables;
142
+ };
143
+
144
+ // Reject client-supplied SQL fragments (the `filters` array is concatenated
145
+ // raw into the WHERE clause). With multipleStatements off this already blocks
146
+ // stacked writes; the pattern also blocks UNION/comment/file exfil breakouts.
147
+ const looksLikeSqlBreakout = (frag) =>
148
+ typeof frag === "string" &&
149
+ /(;|--|\/\*|\bunion\b|\binto\s+(out|dump)file\b|\bsleep\s*\(|\bbenchmark\s*\(|\bload_file\s*\()/i.test(
150
+ frag
151
+ );
152
+
153
+ app.use((req, res, next) => {
154
+ const role = roleOf(req);
155
+
156
+ // 1) Validate any raw filter fragments on read endpoints.
157
+ if (req.body && Array.isArray(req.body.filters)) {
158
+ if (req.body.filters.some(looksLikeSqlBreakout)) {
159
+ return res.status(400).send({ error: "Invalid filter" });
160
+ }
161
+ }
162
+
163
+ // 2) Enforce write authorization on mutating routes.
164
+ if (MUTATING_VIEW_ROUTES.has(req.path)) {
165
+ const view = viewFromRequest(req);
166
+ if (!view || !canWriteView(view, role)) {
167
+ return res.sendStatus(403);
168
+ }
169
+ }
170
+
171
+ next();
172
+ });
173
+
79
174
  async function getRecords(params, computeCount = false) {
80
175
  let queryString = "";
81
176
  if (params.filters) {
@@ -709,6 +804,9 @@ export default (db, config) => {
709
804
 
710
805
  app.post("/updateRecord", async function (req, res, next) {
711
806
  const view = req.config.views[req.body.viewIndex];
807
+ if (!areSafeColumnNames(req.body.data)) {
808
+ return res.status(400).send({ error: "Invalid column name" });
809
+ }
712
810
  for (let key in req.body.data) {
713
811
  if (
714
812
  req.body.data[key] == "NULL" ||
@@ -921,6 +1019,15 @@ export default (db, config) => {
921
1019
  });
922
1020
 
923
1021
  app.post("/duplicateRecord", async function (req, res, next) {
1022
+ // SECURITY: bound the (client-supplied) target table to tables the role may write.
1023
+ const allowedTables = writableTables(req);
1024
+ const requestedTables = [
1025
+ req.body.table,
1026
+ ...(req.body.duplicateConfig?.foreignTables || []).map((t) => t.table),
1027
+ ];
1028
+ if (requestedTables.some((t) => !allowedTables.has(t))) {
1029
+ return res.sendStatus(403);
1030
+ }
924
1031
  const primaryId = req.body.duplicateConfig.primaryId;
925
1032
  const { err, rows } = await db.query("SELECT * FROM ?? WHERE ?? = ?", [
926
1033
  req.body.table,
@@ -998,6 +1105,9 @@ export default (db, config) => {
998
1105
  delete createPayload[key];
999
1106
  }
1000
1107
  }
1108
+ if (!areSafeColumnNames(createPayload)) {
1109
+ return res.status(400).send({ error: "Invalid column name" });
1110
+ }
1001
1111
  const queryExpression = Object.keys(createPayload).join(", ");
1002
1112
  const queryPlaceholders = Object.keys(createPayload)
1003
1113
  .map((x) => "?")
@@ -1047,30 +1157,69 @@ export default (db, config) => {
1047
1157
  }
1048
1158
  });
1049
1159
 
1050
- app.get("/customQuery", async function (req, res, next) {
1051
- const query = (req.query.query as string).replace(
1052
- /\?/g,
1053
- req.query.primaryValue as string
1054
- );
1055
- const { err, rows } = await db.query(query);
1056
- if (err) {
1057
- return res.status(500).send(err);
1160
+ // Collect every SQL template declared anywhere in the (role-filtered) config:
1161
+ // panel `query`, `autocompleteListQuery`, etc. customQuery may only run a
1162
+ // template that already exists in the config the caller is allowed to see —
1163
+ // so it can never be turned into an arbitrary read/write primitive.
1164
+ function collectAllowedQueries(node, acc = new Set<string>()) {
1165
+ if (!node || typeof node !== "object") return acc;
1166
+ if (Array.isArray(node)) {
1167
+ for (const item of node) collectAllowedQueries(item, acc);
1168
+ return acc;
1058
1169
  }
1059
- if (req.query.viewIndex && req.query.panelIndex) {
1060
- const panel =
1061
- req.config.views[req.query.viewIndex as string].recordViewPanels[
1062
- req.query.panelIndex as string
1063
- ];
1064
- if (panel.formatValue) {
1065
- for (const row of rows) {
1066
- for (const key in panel.formatValue) {
1067
- row[key] = await panel.formatValue[key](row);
1170
+ for (const key of Object.keys(node)) {
1171
+ const value = node[key];
1172
+ if (typeof value === "string" && (key === "query" || /Query$/.test(key))) {
1173
+ acc.add(value.trim());
1174
+ } else if (value && typeof value === "object") {
1175
+ collectAllowedQueries(value, acc);
1176
+ }
1177
+ }
1178
+ return acc;
1179
+ }
1180
+
1181
+ // Attack-surface reduction: only expose /customQuery at all when the config
1182
+ // actually declares SQL templates (panels, autocomplete lists, …). If nothing
1183
+ // uses it, the route is replaced by an explicit "disabled" stub so the raw-SQL
1184
+ // sink simply does not exist for that deployment.
1185
+ const CONFIG_USES_CUSTOM_QUERY = collectAllowedQueries(config.views).size > 0;
1186
+
1187
+ if (!CONFIG_USES_CUSTOM_QUERY) {
1188
+ app.get("/customQuery", (req, res) => res.sendStatus(404));
1189
+ } else {
1190
+ app.get("/customQuery", async function (req, res, next) {
1191
+ const template = typeof req.query.query === "string" ? req.query.query : "";
1192
+ // SECURITY: only run whitelisted, read-only templates, and bind the record id
1193
+ // as a parameter instead of substituting it into the SQL string.
1194
+ const allowed = collectAllowedQueries(req.config.views);
1195
+ if (!allowed.has(template.trim())) {
1196
+ return res.status(403).send({ error: "Query not allowed" });
1197
+ }
1198
+ if (!/^\s*\(?\s*(select|with)\b/i.test(template) || /;/.test(template)) {
1199
+ return res.status(400).send({ error: "Only read-only queries are allowed" });
1200
+ }
1201
+ const placeholderCount = (template.match(/\?/g) || []).length;
1202
+ const params = new Array(placeholderCount).fill(req.query.primaryValue);
1203
+ const { err, rows } = await db.query(template, params);
1204
+ if (err) {
1205
+ return res.status(500).send(err);
1206
+ }
1207
+ if (req.query.viewIndex && req.query.panelIndex) {
1208
+ const panel =
1209
+ req.config.views[req.query.viewIndex as string].recordViewPanels[
1210
+ req.query.panelIndex as string
1211
+ ];
1212
+ if (panel.formatValue) {
1213
+ for (const row of rows) {
1214
+ for (const key in panel.formatValue) {
1215
+ row[key] = await panel.formatValue[key](row);
1216
+ }
1068
1217
  }
1069
1218
  }
1070
1219
  }
1071
- }
1072
- res.send(rows);
1073
- });
1220
+ res.send(rows);
1221
+ });
1222
+ }
1074
1223
 
1075
1224
  function uploadFile(column, fileParamName, req, res) {
1076
1225
  if (column.s3Config || column.gcpConfig) {
@@ -1132,28 +1281,42 @@ export default (db, config) => {
1132
1281
  Math.random().toString().substr(2) +
1133
1282
  "." +
1134
1283
  extension;
1135
- const s3 = new S3({
1136
- endpoint: new Endpoint(s3Config.host),
1137
- accessKeyId: s3Config.accessKeyId,
1138
- secretAccessKey: s3Config.secretAccessKey,
1284
+ const rawHost = String(s3Config.host || "");
1285
+ const endpoint = /^https?:\/\//i.test(rawHost)
1286
+ ? rawHost
1287
+ : `https://${rawHost}`;
1288
+ const s3 = new S3Client({
1289
+ endpoint,
1139
1290
  region: s3Config.region,
1140
- signatureVersion: "v4",
1291
+ credentials: {
1292
+ accessKeyId: s3Config.accessKeyId,
1293
+ secretAccessKey: s3Config.secretAccessKey,
1294
+ },
1295
+ forcePathStyle: false,
1141
1296
  });
1142
1297
  if (s3Config.signedUrlNeeded) {
1143
- const signedUrl = s3.getSignedUrl("putObject", {
1144
- Bucket: s3Config.bucket,
1145
- Key: path,
1146
- ContentType: contentType,
1147
- Expires: 150,
1148
- });
1149
- axios
1150
- .put(signedUrl, fs.readFileSync(files[fileParamName][0].path), {
1151
- maxContentLength: 100000000000,
1152
- maxBodyLength: 100000000000,
1153
- headers: {
1154
- "Content-Type": contentType,
1155
- },
1156
- })
1298
+ getSignedUrl(
1299
+ s3,
1300
+ new PutObjectCommand({
1301
+ Bucket: s3Config.bucket,
1302
+ Key: path,
1303
+ ContentType: contentType,
1304
+ }),
1305
+ { expiresIn: 150 }
1306
+ )
1307
+ .then((signedUrl) =>
1308
+ axios.put(
1309
+ signedUrl,
1310
+ fs.readFileSync(files[fileParamName][0].path),
1311
+ {
1312
+ maxContentLength: 100000000000,
1313
+ maxBodyLength: 100000000000,
1314
+ headers: {
1315
+ "Content-Type": contentType,
1316
+ },
1317
+ }
1318
+ )
1319
+ )
1157
1320
  .then(() => {
1158
1321
  res.status(200).send({
1159
1322
  path: s3Config.pathTransform
@@ -1167,16 +1330,16 @@ export default (db, config) => {
1167
1330
  res.status(500).send(e);
1168
1331
  });
1169
1332
  } else {
1170
- s3.putObject(
1171
- {
1333
+ s3.send(
1334
+ new PutObjectCommand({
1172
1335
  Body: fs.readFileSync(files[fileParamName][0].path),
1173
1336
  Bucket: s3Config.bucket,
1174
1337
  ContentType: contentType,
1175
1338
  ACL: "public-read",
1176
1339
  Key: path,
1177
- },
1178
- function (err, data) {
1179
- if (err) return res.status(500).send(err);
1340
+ })
1341
+ )
1342
+ .then(() => {
1180
1343
  res.send({
1181
1344
  url: `https://${s3Config.host}/${s3Config.bucket}/${path}`,
1182
1345
  path: s3Config.pathTransform
@@ -1185,8 +1348,8 @@ export default (db, config) => {
1185
1348
  aspectRatio,
1186
1349
  originalFilename,
1187
1350
  });
1188
- }
1189
- );
1351
+ })
1352
+ .catch((err) => res.status(500).send(err));
1190
1353
  }
1191
1354
  }
1192
1355
  });
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
- const { rows } = await db.query("SELECT * FROM ?? WHERE ?? = ?", [authRole.table, authRole.idField, req.session.user.userId]);
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;
@@ -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
- multipleStatements: true
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) {