dbgate-api 6.5.4 → 6.5.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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dbgate-api",
3
3
  "main": "src/index.js",
4
- "version": "6.5.4",
4
+ "version": "6.5.6",
5
5
  "homepage": "https://dbgate.org/",
6
6
  "repository": {
7
7
  "type": "git",
@@ -30,10 +30,10 @@
30
30
  "compare-versions": "^3.6.0",
31
31
  "cors": "^2.8.5",
32
32
  "cross-env": "^6.0.3",
33
- "dbgate-datalib": "^6.5.4",
33
+ "dbgate-datalib": "^6.5.6",
34
34
  "dbgate-query-splitter": "^4.11.5",
35
- "dbgate-sqltree": "^6.5.4",
36
- "dbgate-tools": "^6.5.4",
35
+ "dbgate-sqltree": "^6.5.6",
36
+ "dbgate-tools": "^6.5.6",
37
37
  "debug": "^4.3.4",
38
38
  "diff": "^5.0.0",
39
39
  "diff2html": "^3.4.13",
@@ -85,7 +85,7 @@
85
85
  "devDependencies": {
86
86
  "@types/fs-extra": "^9.0.11",
87
87
  "@types/lodash": "^4.14.149",
88
- "dbgate-types": "^6.5.4",
88
+ "dbgate-types": "^6.5.6",
89
89
  "env-cmd": "^10.1.0",
90
90
  "jsdoc-to-markdown": "^9.0.5",
91
91
  "node-loader": "^1.0.2",
@@ -11,7 +11,7 @@ const logger = getLogger('authProvider');
11
11
  class AuthProviderBase {
12
12
  amoid = 'none';
13
13
 
14
- async login(login, password, options = undefined) {
14
+ async login(login, password, options = undefined, req = undefined) {
15
15
  return {
16
16
  accessToken: jwt.sign(
17
17
  {
@@ -23,7 +23,7 @@ class AuthProviderBase {
23
23
  };
24
24
  }
25
25
 
26
- oauthToken(params) {
26
+ oauthToken(params, req) {
27
27
  return {};
28
28
  }
29
29
 
@@ -13,8 +13,21 @@ const {
13
13
  } = require('../auth/authProvider');
14
14
  const storage = require('./storage');
15
15
  const { decryptPasswordString } = require('../utility/crypting');
16
- const { createDbGateIdentitySession, startCloudTokenChecking } = require('../utility/cloudIntf');
16
+ const {
17
+ createDbGateIdentitySession,
18
+ startCloudTokenChecking,
19
+ readCloudTokenHolder,
20
+ readCloudTestTokenHolder,
21
+ } = require('../utility/cloudIntf');
17
22
  const socket = require('../utility/socket');
23
+ const { sendToAuditLog } = require('../utility/auditlog');
24
+ const {
25
+ isLoginLicensed,
26
+ LOGIN_LIMIT_ERROR,
27
+ markTokenAsLoggedIn,
28
+ markUserAsActive,
29
+ markLoginAsLoggedOut,
30
+ } = require('../utility/loginchecker');
18
31
 
19
32
  const logger = getLogger('auth');
20
33
 
@@ -54,6 +67,11 @@ function authMiddleware(req, res, next) {
54
67
 
55
68
  // const isAdminPage = req.headers['x-is-admin-page'] == 'true';
56
69
 
70
+ if (process.env.SKIP_ALL_AUTH) {
71
+ // API is not authorized for basic auth
72
+ return next();
73
+ }
74
+
57
75
  if (process.env.BASIC_AUTH) {
58
76
  // API is not authorized for basic auth
59
77
  return next();
@@ -72,6 +90,8 @@ function authMiddleware(req, res, next) {
72
90
  try {
73
91
  const decoded = jwt.verify(token, getTokenSecret());
74
92
  req.user = decoded;
93
+ markUserAsActive(decoded.licenseUid, token);
94
+
75
95
  return next();
76
96
  } catch (err) {
77
97
  if (skipAuth) {
@@ -87,12 +107,12 @@ function authMiddleware(req, res, next) {
87
107
 
88
108
  module.exports = {
89
109
  oauthToken_meta: true,
90
- async oauthToken(params) {
110
+ async oauthToken(params, req) {
91
111
  const { amoid } = params;
92
- return getAuthProviderById(amoid).oauthToken(params);
112
+ return getAuthProviderById(amoid).oauthToken(params, req);
93
113
  },
94
114
  login_meta: true,
95
- async login(params) {
115
+ async login(params, req) {
96
116
  const { amoid, login, password, isAdminPage } = params;
97
117
 
98
118
  if (isAdminPage) {
@@ -102,25 +122,52 @@ module.exports = {
102
122
  adminPassword = decryptPasswordString(adminConfig?.adminPassword);
103
123
  }
104
124
  if (adminPassword && adminPassword == password) {
125
+ if (!(await isLoginLicensed(req, `superadmin`))) {
126
+ return { error: LOGIN_LIMIT_ERROR };
127
+ }
128
+
129
+ sendToAuditLog(req, {
130
+ category: 'auth',
131
+ component: 'AuthController',
132
+ action: 'login',
133
+ event: 'login.admin',
134
+ severity: 'info',
135
+ message: 'Administration login successful',
136
+ });
137
+
138
+ const licenseUid = `superadmin`;
139
+ const accessToken = jwt.sign(
140
+ {
141
+ login: 'superadmin',
142
+ permissions: await storage.loadSuperadminPermissions(),
143
+ roleId: -3,
144
+ licenseUid,
145
+ },
146
+ getTokenSecret(),
147
+ {
148
+ expiresIn: getTokenLifetime(),
149
+ }
150
+ );
151
+ markTokenAsLoggedIn(licenseUid, accessToken);
152
+
105
153
  return {
106
- accessToken: jwt.sign(
107
- {
108
- login: 'superadmin',
109
- permissions: await storage.loadSuperadminPermissions(),
110
- roleId: -3,
111
- },
112
- getTokenSecret(),
113
- {
114
- expiresIn: getTokenLifetime(),
115
- }
116
- ),
154
+ accessToken,
117
155
  };
118
156
  }
119
157
 
158
+ sendToAuditLog(req, {
159
+ category: 'auth',
160
+ component: 'AuthController',
161
+ action: 'loginFail',
162
+ event: 'login.adminFailed',
163
+ severity: 'warn',
164
+ message: 'Administraton login failed',
165
+ });
166
+
120
167
  return { error: 'Login failed' };
121
168
  }
122
169
 
123
- return getAuthProviderById(amoid).login(login, password);
170
+ return getAuthProviderById(amoid).login(login, password, undefined, req);
124
171
  },
125
172
 
126
173
  getProviders_meta: true,
@@ -138,8 +185,8 @@ module.exports = {
138
185
  },
139
186
 
140
187
  createCloudLoginSession_meta: true,
141
- async createCloudLoginSession({ client }) {
142
- const res = await createDbGateIdentitySession(client);
188
+ async createCloudLoginSession({ client, redirectUri }) {
189
+ const res = await createDbGateIdentitySession(client, redirectUri);
143
190
  startCloudTokenChecking(res.sid, tokenHolder => {
144
191
  socket.emit('got-cloud-token', tokenHolder);
145
192
  socket.emitChanged('cloud-content-changed');
@@ -148,5 +195,29 @@ module.exports = {
148
195
  return res;
149
196
  },
150
197
 
198
+ cloudLoginRedirected_meta: true,
199
+ async cloudLoginRedirected({ sid }) {
200
+ const tokenHolder = await readCloudTokenHolder(sid);
201
+ return tokenHolder;
202
+ },
203
+
204
+ cloudTestLogin_meta: true,
205
+ async cloudTestLogin({ email }) {
206
+ const tokenHolder = await readCloudTestTokenHolder(email);
207
+ return tokenHolder;
208
+ },
209
+
210
+ logoutAdmin_meta: true,
211
+ async logoutAdmin() {
212
+ await markLoginAsLoggedOut('superadmin');
213
+ return true;
214
+ },
215
+
216
+ logoutUser_meta: true,
217
+ async logoutUser({}, req) {
218
+ await markLoginAsLoggedOut(req?.user?.licenseUid);
219
+ return true;
220
+ },
221
+
151
222
  authMiddleware,
152
223
  };
@@ -258,4 +258,22 @@ module.exports = {
258
258
  await fs.writeFile(filePath, content);
259
259
  return true;
260
260
  },
261
+
262
+ folderUsers_meta: true,
263
+ async folderUsers({ folid }) {
264
+ const resp = await callCloudApiGet(`content-folders/users/${folid}`);
265
+ return resp;
266
+ },
267
+
268
+ setFolderUserRole_meta: true,
269
+ async setFolderUserRole({ folid, email, role }) {
270
+ const resp = await callCloudApiPost(`content-folders/set-user-role/${folid}`, { email, role });
271
+ return resp;
272
+ },
273
+
274
+ removeFolderUser_meta: true,
275
+ async removeFolderUser({ folid, email }) {
276
+ const resp = await callCloudApiPost(`content-folders/remove-user/${folid}`, { email });
277
+ return resp;
278
+ },
261
279
  };
@@ -16,7 +16,7 @@ const connections = require('../controllers/connections');
16
16
  const { getAuthProviderFromReq } = require('../auth/authProvider');
17
17
  const { checkLicense, checkLicenseKey } = require('../utility/checkLicense');
18
18
  const storage = require('./storage');
19
- const { getAuthProxyUrl } = require('../utility/authProxy');
19
+ const { getAuthProxyUrl, tryToGetRefreshedLicense } = require('../utility/authProxy');
20
20
  const { getPublicHardwareFingerprint } = require('../utility/hardwareFingerprint');
21
21
  const { extractErrorMessage } = require('dbgate-tools');
22
22
  const {
@@ -29,6 +29,7 @@ const {
29
29
  } = require('../utility/crypting');
30
30
 
31
31
  const lock = new AsyncLock();
32
+ let cachedSettingsValue = null;
32
33
 
33
34
  module.exports = {
34
35
  // settingsValue: {},
@@ -108,6 +109,7 @@ module.exports = {
108
109
  ),
109
110
  isAdminPasswordMissing,
110
111
  isInvalidToken: req?.isInvalidToken,
112
+ skipAllAuth: !!process.env.SKIP_ALL_AUTH,
111
113
  adminPasswordState: adminConfig?.adminPasswordState,
112
114
  storageDatabase: process.env.STORAGE_DATABASE,
113
115
  logsFilePath: getLogsFilePath(),
@@ -118,6 +120,7 @@ module.exports = {
118
120
  supportCloudAutoUpgrade: !!process.env.CLOUD_UPGRADE_FILE,
119
121
  allowPrivateCloud: platformInfo.isElectron || !!process.env.ALLOW_DBGATE_PRIVATE_CLOUD,
120
122
  ...currentVersion,
123
+ redirectToDbGateCloudLogin: !!process.env.REDIRECT_TO_DBGATE_CLOUD_LOGIN,
121
124
  };
122
125
 
123
126
  return configResult;
@@ -144,6 +147,13 @@ module.exports = {
144
147
  return res;
145
148
  },
146
149
 
150
+ async getCachedSettings() {
151
+ if (!cachedSettingsValue) {
152
+ cachedSettingsValue = await this.loadSettings();
153
+ }
154
+ return cachedSettingsValue;
155
+ },
156
+
147
157
  deleteSettings_meta: true,
148
158
  async deleteSettings() {
149
159
  await fs.unlink(path.join(datadir(), processArgs.runE2eTests ? 'settings-e2etests.json' : 'settings.json'));
@@ -182,6 +192,7 @@ module.exports = {
182
192
  return {
183
193
  ...this.fillMissingSettings(JSON.parse(settingsText)),
184
194
  'other.licenseKey': platformInfo.isElectron ? await this.loadLicenseKey() : undefined,
195
+ // 'other.licenseKey': await this.loadLicenseKey(),
185
196
  };
186
197
  }
187
198
  } catch (err) {
@@ -199,21 +210,34 @@ module.exports = {
199
210
  },
200
211
 
201
212
  saveLicenseKey_meta: true,
202
- async saveLicenseKey({ licenseKey }) {
203
- const decoded = jwt.decode(licenseKey?.trim());
204
- if (!decoded) {
205
- return {
206
- status: 'error',
207
- errorMessage: 'Invalid license key',
208
- };
209
- }
213
+ async saveLicenseKey({ licenseKey, forceSave = false, tryToRenew = false }) {
214
+ if (!forceSave) {
215
+ const decoded = jwt.decode(licenseKey?.trim());
216
+ if (!decoded) {
217
+ return {
218
+ status: 'error',
219
+ errorMessage: 'Invalid license key',
220
+ };
221
+ }
210
222
 
211
- const { exp } = decoded;
212
- if (exp * 1000 < Date.now()) {
213
- return {
214
- status: 'error',
215
- errorMessage: 'License key is expired',
216
- };
223
+ const { exp } = decoded;
224
+ if (exp * 1000 < Date.now()) {
225
+ let renewed = false;
226
+ if (tryToRenew) {
227
+ const newLicenseKey = await tryToGetRefreshedLicense(licenseKey);
228
+ if (newLicenseKey.status == 'ok') {
229
+ licenseKey = newLicenseKey.token;
230
+ renewed = true;
231
+ }
232
+ }
233
+
234
+ if (!renewed) {
235
+ return {
236
+ status: 'error',
237
+ errorMessage: 'License key is expired',
238
+ };
239
+ }
240
+ }
217
241
  }
218
242
 
219
243
  try {
@@ -257,6 +281,7 @@ module.exports = {
257
281
  updateSettings_meta: true,
258
282
  async updateSettings(values, req) {
259
283
  if (!hasPermission(`settings/change`, req)) return false;
284
+ cachedSettingsValue = null;
260
285
 
261
286
  const res = await lock.acquire('settings', async () => {
262
287
  const currentValue = await this.loadSettings();
@@ -265,7 +290,11 @@ module.exports = {
265
290
  if (process.env.STORAGE_DATABASE) {
266
291
  updated = {
267
292
  ...currentValue,
268
- ...values,
293
+ ..._.mapValues(values, v => {
294
+ if (v === true) return 'true';
295
+ if (v === false) return 'false';
296
+ return v;
297
+ }),
269
298
  };
270
299
  await storage.writeConfig({
271
300
  group: 'settings',
@@ -283,7 +312,7 @@ module.exports = {
283
312
  // this.settingsValue = updated;
284
313
 
285
314
  if (currentValue['other.licenseKey'] != values['other.licenseKey']) {
286
- await this.saveLicenseKey({ licenseKey: values['other.licenseKey'] });
315
+ await this.saveLicenseKey({ licenseKey: values['other.licenseKey'], forceSave: true });
287
316
  socket.emitChanged(`config-changed`);
288
317
  }
289
318
  }
@@ -303,7 +332,7 @@ module.exports = {
303
332
  const resp = await axios.default.get('https://raw.githubusercontent.com/dbgate/dbgate/master/CHANGELOG.md');
304
333
  return resp.data;
305
334
  } catch (err) {
306
- return ''
335
+ return '';
307
336
  }
308
337
  },
309
338
 
@@ -313,6 +342,16 @@ module.exports = {
313
342
  return resp;
314
343
  },
315
344
 
345
+ getNewLicense_meta: true,
346
+ async getNewLicense({ oldLicenseKey }) {
347
+ const newLicenseKey = await tryToGetRefreshedLicense(oldLicenseKey);
348
+ const res = await checkLicenseKey(newLicenseKey.token);
349
+ if (res.status == 'ok') {
350
+ res.licenseKey = newLicenseKey.token;
351
+ }
352
+ return res;
353
+ },
354
+
316
355
  recryptDatabaseForExport(db) {
317
356
  const encryptionKey = generateTransportEncryptionKey();
318
357
  const transportEncryptor = createTransportEncryptor(encryptionKey);
@@ -536,14 +536,14 @@ module.exports = {
536
536
  },
537
537
 
538
538
  dbloginAuthToken_meta: true,
539
- async dbloginAuthToken({ amoid, code, conid, redirectUri, sid }) {
539
+ async dbloginAuthToken({ amoid, code, conid, redirectUri, sid }, req) {
540
540
  try {
541
541
  const connection = await this.getCore({ conid });
542
542
  const driver = requireEngineDriver(connection);
543
543
  const accessToken = await driver.getAuthTokenFromCode(connection, { code, redirectUri, sid });
544
544
  const volatile = await this.saveVolatile({ conid, accessToken });
545
545
  const authProvider = getAuthProviderById(amoid);
546
- const resp = await authProvider.login(null, null, { conid: volatile._id });
546
+ const resp = await authProvider.login(null, null, { conid: volatile._id }, req);
547
547
  return resp;
548
548
  } catch (err) {
549
549
  logger.error(extractErrorLogData(err), 'Error getting DB token');
@@ -552,18 +552,18 @@ module.exports = {
552
552
  },
553
553
 
554
554
  dbloginAuth_meta: true,
555
- async dbloginAuth({ amoid, conid, user, password }) {
555
+ async dbloginAuth({ amoid, conid, user, password }, req) {
556
556
  if (user || password) {
557
557
  const saveResp = await this.saveVolatile({ conid, user, password, test: true });
558
558
  if (saveResp.msgtype == 'connected') {
559
- const loginResp = await getAuthProviderById(amoid).login(user, password, { conid: saveResp._id });
559
+ const loginResp = await getAuthProviderById(amoid).login(user, password, { conid: saveResp._id }, req);
560
560
  return loginResp;
561
561
  }
562
562
  return saveResp;
563
563
  }
564
564
 
565
565
  // user and password is stored in connection, volatile connection is not needed
566
- const loginResp = await getAuthProviderById(amoid).login(null, null, { conid });
566
+ const loginResp = await getAuthProviderById(amoid).login(null, null, { conid }, req);
567
567
  return loginResp;
568
568
  },
569
569
 
@@ -41,6 +41,7 @@ const { decryptConnection } = require('../utility/crypting');
41
41
  const { getSshTunnel } = require('../utility/sshTunnel');
42
42
  const sessions = require('./sessions');
43
43
  const jsldata = require('./jsldata');
44
+ const { sendToAuditLog } = require('../utility/auditlog');
44
45
 
45
46
  const logger = getLogger('databaseConnections');
46
47
 
@@ -83,8 +84,11 @@ module.exports = {
83
84
  }
84
85
  },
85
86
  handle_response(conid, database, { msgid, ...response }) {
86
- const [resolve, reject] = this.requests[msgid];
87
+ const [resolve, reject, additionalData] = this.requests[msgid];
87
88
  resolve(response);
89
+ if (additionalData?.auditLogger) {
90
+ additionalData?.auditLogger(response);
91
+ }
88
92
  delete this.requests[msgid];
89
93
  },
90
94
  handle_status(conid, database, { status }) {
@@ -215,10 +219,10 @@ module.exports = {
215
219
  },
216
220
 
217
221
  /** @param {import('dbgate-types').OpenedDatabaseConnection} conn */
218
- sendRequest(conn, message) {
222
+ sendRequest(conn, message, additionalData = {}) {
219
223
  const msgid = crypto.randomUUID();
220
224
  const promise = new Promise((resolve, reject) => {
221
- this.requests[msgid] = [resolve, reject];
225
+ this.requests[msgid] = [resolve, reject, additionalData];
222
226
  try {
223
227
  conn.subprocess.send({ msgid, ...message });
224
228
  } catch (err) {
@@ -242,18 +246,57 @@ module.exports = {
242
246
  },
243
247
 
244
248
  sqlSelect_meta: true,
245
- async sqlSelect({ conid, database, select }, req) {
249
+ async sqlSelect({ conid, database, select, auditLogSessionGroup }, req) {
246
250
  testConnectionPermission(conid, req);
247
251
  const opened = await this.ensureOpened(conid, database);
248
- const res = await this.sendRequest(opened, { msgtype: 'sqlSelect', select });
252
+ const res = await this.sendRequest(
253
+ opened,
254
+ { msgtype: 'sqlSelect', select },
255
+ {
256
+ auditLogger:
257
+ auditLogSessionGroup && select?.from?.name?.pureName
258
+ ? response => {
259
+ sendToAuditLog(req, {
260
+ category: 'dbop',
261
+ component: 'DatabaseConnectionsController',
262
+ event: 'sql.select',
263
+ action: 'select',
264
+ severity: 'info',
265
+ conid,
266
+ database,
267
+ schemaName: select?.from?.name?.schemaName,
268
+ pureName: select?.from?.name?.pureName,
269
+ sumint1: response?.rows?.length,
270
+ sessionParam: `${conid}::${database}::${select?.from?.name?.schemaName || '0'}::${
271
+ select?.from?.name?.pureName
272
+ }`,
273
+ sessionGroup: auditLogSessionGroup,
274
+ message: `Loaded table data from ${select?.from?.name?.pureName}`,
275
+ });
276
+ }
277
+ : null,
278
+ }
279
+ );
249
280
  return res;
250
281
  },
251
282
 
252
283
  runScript_meta: true,
253
- async runScript({ conid, database, sql, useTransaction }, req) {
284
+ async runScript({ conid, database, sql, useTransaction, logMessage }, req) {
254
285
  testConnectionPermission(conid, req);
255
286
  logger.info({ conid, database, sql }, 'Processing script');
256
287
  const opened = await this.ensureOpened(conid, database);
288
+ sendToAuditLog(req, {
289
+ category: 'dbop',
290
+ component: 'DatabaseConnectionsController',
291
+ event: 'sql.runscript',
292
+ action: 'runscript',
293
+ severity: 'info',
294
+ conid,
295
+ database,
296
+ detail: sql,
297
+ message: logMessage || `Running SQL script`,
298
+ });
299
+
257
300
  const res = await this.sendRequest(opened, { msgtype: 'runScript', sql, useTransaction });
258
301
  return res;
259
302
  },
@@ -262,16 +305,53 @@ module.exports = {
262
305
  async runOperation({ conid, database, operation, useTransaction }, req) {
263
306
  testConnectionPermission(conid, req);
264
307
  logger.info({ conid, database, operation }, 'Processing operation');
308
+
309
+ sendToAuditLog(req, {
310
+ category: 'dbop',
311
+ component: 'DatabaseConnectionsController',
312
+ event: 'sql.runoperation',
313
+ action: operation.type,
314
+ severity: 'info',
315
+ conid,
316
+ database,
317
+ detail: operation,
318
+ message: `Running DB operation: ${operation.type}`,
319
+ });
320
+
265
321
  const opened = await this.ensureOpened(conid, database);
266
322
  const res = await this.sendRequest(opened, { msgtype: 'runOperation', operation, useTransaction });
267
323
  return res;
268
324
  },
269
325
 
270
326
  collectionData_meta: true,
271
- async collectionData({ conid, database, options }, req) {
327
+ async collectionData({ conid, database, options, auditLogSessionGroup }, req) {
272
328
  testConnectionPermission(conid, req);
273
329
  const opened = await this.ensureOpened(conid, database);
274
- const res = await this.sendRequest(opened, { msgtype: 'collectionData', options });
330
+ const res = await this.sendRequest(
331
+ opened,
332
+ { msgtype: 'collectionData', options },
333
+ {
334
+ auditLogger:
335
+ auditLogSessionGroup && options?.pureName
336
+ ? response => {
337
+ sendToAuditLog(req, {
338
+ category: 'dbop',
339
+ component: 'DatabaseConnectionsController',
340
+ event: 'nosql.collectionData',
341
+ action: 'select',
342
+ severity: 'info',
343
+ conid,
344
+ database,
345
+ pureName: options?.pureName,
346
+ sumint1: response?.result?.rows?.length,
347
+ sessionParam: `${conid}::${database}::${options?.pureName}`,
348
+ sessionGroup: auditLogSessionGroup,
349
+ message: `Loaded collection data ${options?.pureName}`,
350
+ });
351
+ }
352
+ : null,
353
+ }
354
+ );
275
355
  return res.result || null;
276
356
  },
277
357
 
@@ -492,6 +572,20 @@ module.exports = {
492
572
  }
493
573
 
494
574
  const opened = await this.ensureOpened(conid, database);
575
+
576
+ sendToAuditLog(req, {
577
+ category: 'dbop',
578
+ component: 'DatabaseConnectionsController',
579
+ action: 'structure',
580
+ event: 'dbStructure.get',
581
+ severity: 'info',
582
+ conid,
583
+ database,
584
+ sessionParam: `${conid}::${database}`,
585
+ sessionGroup: 'getStructure',
586
+ message: `Loaded database structure for ${database}`,
587
+ });
588
+
495
589
  return opened.structure;
496
590
  // const existing = this.opened.find((x) => x.conid == conid && x.database == database);
497
591
  // if (existing) return existing.status;
@@ -203,10 +203,10 @@ module.exports = {
203
203
  },
204
204
 
205
205
  exportChart_meta: true,
206
- async exportChart({ filePath, title, config, image }) {
206
+ async exportChart({ filePath, title, config, image, plugins }) {
207
207
  const fileName = path.parse(filePath).base;
208
208
  const imageFile = fileName.replace('.html', '-preview.png');
209
- const html = getChartExport(title, config, imageFile);
209
+ const html = getChartExport(title, config, imageFile, plugins);
210
210
  await fs.writeFile(filePath, html);
211
211
  if (image) {
212
212
  const index = image.indexOf('base64,');
@@ -20,6 +20,7 @@ const { handleProcessCommunication } = require('../utility/processComm');
20
20
  const processArgs = require('../utility/processArgs');
21
21
  const platformInfo = require('../utility/platformInfo');
22
22
  const { checkSecureDirectories, checkSecureDirectoriesInScript } = require('../utility/security');
23
+ const { sendToAuditLog, logJsonRunnerScript } = require('../utility/auditlog');
23
24
  const logger = getLogger('runners');
24
25
 
25
26
  function extractPlugins(script) {
@@ -270,7 +271,7 @@ module.exports = {
270
271
  },
271
272
 
272
273
  start_meta: true,
273
- async start({ script }) {
274
+ async start({ script }, req) {
274
275
  const runid = crypto.randomUUID();
275
276
 
276
277
  if (script.type == 'json') {
@@ -280,14 +281,36 @@ module.exports = {
280
281
  }
281
282
  }
282
283
 
284
+ logJsonRunnerScript(req, script);
285
+
283
286
  const js = await jsonScriptToJavascript(script);
284
287
  return this.startCore(runid, scriptTemplate(js, false));
285
288
  }
286
289
 
287
290
  if (!platformInfo.allowShellScripting) {
291
+ sendToAuditLog(req, {
292
+ category: 'shell',
293
+ component: 'RunnersController',
294
+ event: 'script.runFailed',
295
+ action: 'script',
296
+ severity: 'warn',
297
+ detail: script,
298
+ message: 'Scripts are not allowed',
299
+ });
300
+
288
301
  return { errorMessage: 'Shell scripting is not allowed' };
289
302
  }
290
303
 
304
+ sendToAuditLog(req, {
305
+ category: 'shell',
306
+ component: 'RunnersController',
307
+ event: 'script.run.shell',
308
+ action: 'script',
309
+ severity: 'info',
310
+ detail: script,
311
+ message: 'Running JS script',
312
+ });
313
+
291
314
  return this.startCore(runid, scriptTemplate(script, false));
292
315
  },
293
316