dbgate-api 5.3.3 → 5.4.0

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.
@@ -3,7 +3,7 @@ const os = require('os');
3
3
  const path = require('path');
4
4
  const axios = require('axios');
5
5
  const { datadir, getLogsFilePath } = require('../utility/directories');
6
- const { hasPermission, getLogins } = require('../utility/hasPermission');
6
+ const { hasPermission } = require('../utility/hasPermission');
7
7
  const socket = require('../utility/socket');
8
8
  const _ = require('lodash');
9
9
  const AsyncLock = require('async-lock');
@@ -11,6 +11,9 @@ const AsyncLock = require('async-lock');
11
11
  const currentVersion = require('../currentVersion');
12
12
  const platformInfo = require('../utility/platformInfo');
13
13
  const connections = require('../controllers/connections');
14
+ const { getAuthProviderFromReq } = require('../auth/authProvider');
15
+ const { checkLicense, checkLicenseKey } = require('../utility/checkLicense');
16
+ const storage = require('./storage');
14
17
 
15
18
  const lock = new AsyncLock();
16
19
 
@@ -27,27 +30,51 @@ module.exports = {
27
30
 
28
31
  get_meta: true,
29
32
  async get(_params, req) {
30
- const logins = getLogins();
31
- const loginName =
32
- req && req.user && req.user.login ? req.user.login : req && req.auth && req.auth.user ? req.auth.user : null;
33
- const login = logins && loginName ? logins.find(x => x.login == loginName) : null;
34
- const permissions = login ? login.permissions : process.env.PERMISSIONS;
33
+ const authProvider = getAuthProviderFromReq(req);
34
+ const login = authProvider.getCurrentLogin(req);
35
+ const permissions = authProvider.getCurrentPermissions(req);
36
+ const isUserLoggedIn = authProvider.isUserLoggedIn(req);
37
+
38
+ const singleConid = authProvider.getSingleConnectionId(req);
39
+
40
+ const singleConnection = singleConid
41
+ ? await connections.getCore({ conid: singleConid })
42
+ : connections.singleConnection;
43
+
44
+ let configurationError = null;
45
+ if (process.env.STORAGE_DATABASE && process.env.BASIC_AUTH) {
46
+ configurationError =
47
+ 'Basic authentization is not allowed, when using storage. Cannot use both STORAGE_DATABASE and BASIC_AUTH';
48
+ }
49
+
50
+ const checkedLicense = await checkLicense();
51
+ const isLicenseValid = checkedLicense?.status == 'ok';
35
52
 
36
53
  return {
37
54
  runAsPortal: !!connections.portalConnections,
38
55
  singleDbConnection: connections.singleDbConnection,
39
- singleConnection: connections.singleConnection,
56
+ singleConnection: singleConnection,
57
+ isUserLoggedIn,
40
58
  // hideAppEditor: !!process.env.HIDE_APP_EDITOR,
41
59
  allowShellConnection: platformInfo.allowShellConnection,
42
60
  allowShellScripting: platformInfo.allowShellScripting,
43
61
  isDocker: platformInfo.isDocker,
62
+ isElectron: platformInfo.isElectron,
63
+ isLicenseValid,
64
+ checkedLicense,
65
+ configurationError,
66
+ logoutUrl: await authProvider.getLogoutUrl(),
44
67
  permissions,
45
68
  login,
46
- oauth: process.env.OAUTH_AUTH,
47
- oauthClient: process.env.OAUTH_CLIENT_ID,
48
- oauthScope: process.env.OAUTH_SCOPE,
49
- oauthLogout: process.env.OAUTH_LOGOUT,
50
- isLoginForm: !!process.env.AD_URL || (!!logins && !process.env.BASIC_AUTH),
69
+ // ...additionalConfigProps,
70
+ isBasicAuth: !!process.env.BASIC_AUTH,
71
+ isAdminLoginForm: !!(
72
+ process.env.STORAGE_DATABASE &&
73
+ process.env.ADMIN_PASSWORD &&
74
+ !process.env.BASIC_AUTH &&
75
+ checkedLicense?.type == 'premium'
76
+ ),
77
+ storageDatabase: process.env.STORAGE_DATABASE,
51
78
  logsFilePath: getLogsFilePath(),
52
79
  connectionsFilePath: path.join(datadir(), 'connections.jsonl'),
53
80
  ...currentVersion,
@@ -75,6 +102,12 @@ module.exports = {
75
102
  return res;
76
103
  },
77
104
 
105
+ deleteSettings_meta: true,
106
+ async deleteSettings() {
107
+ await fs.unlink(path.join(datadir(), 'settings.json'));
108
+ return true;
109
+ },
110
+
78
111
  fillMissingSettings(value) {
79
112
  const res = {
80
113
  ...value,
@@ -97,12 +130,39 @@ module.exports = {
97
130
  async loadSettings() {
98
131
  try {
99
132
  const settingsText = await fs.readFile(path.join(datadir(), 'settings.json'), { encoding: 'utf-8' });
100
- return this.fillMissingSettings(JSON.parse(settingsText));
133
+ return {
134
+ ...this.fillMissingSettings(JSON.parse(settingsText)),
135
+ 'other.licenseKey': platformInfo.isElectron ? await this.loadLicenseKey() : undefined,
136
+ };
101
137
  } catch (err) {
102
138
  return this.fillMissingSettings({});
103
139
  }
104
140
  },
105
141
 
142
+ async loadLicenseKey() {
143
+ try {
144
+ const licenseKey = await fs.readFile(path.join(datadir(), 'license.key'), { encoding: 'utf-8' });
145
+ return licenseKey;
146
+ } catch (err) {
147
+ return null;
148
+ }
149
+ },
150
+
151
+ saveLicenseKey_meta: true,
152
+ async saveLicenseKey({ licenseKey }) {
153
+ try {
154
+ if (process.env.STORAGE_DATABASE) {
155
+ await storage.writeConfig({ group: 'license', config: { licenseKey } });
156
+ // await storageWriteConfig('license', { licenseKey });
157
+ } else {
158
+ await fs.writeFile(path.join(datadir(), 'license.key'), licenseKey);
159
+ }
160
+ socket.emitChanged(`config-changed`);
161
+ } catch (err) {
162
+ return null;
163
+ }
164
+ },
165
+
106
166
  updateSettings_meta: true,
107
167
  async updateSettings(values, req) {
108
168
  if (!hasPermission(`settings/change`, req)) return false;
@@ -112,10 +172,16 @@ module.exports = {
112
172
  try {
113
173
  const updated = {
114
174
  ...currentValue,
115
- ...values,
175
+ ..._.omit(values, ['other.licenseKey']),
116
176
  };
117
177
  await fs.writeFile(path.join(datadir(), 'settings.json'), JSON.stringify(updated, undefined, 2));
118
178
  // this.settingsValue = updated;
179
+
180
+ if (currentValue['other.licenseKey'] != values['other.licenseKey']) {
181
+ await this.saveLicenseKey({ licenseKey: values['other.licenseKey'] });
182
+ socket.emitChanged(`config-changed`);
183
+ }
184
+
119
185
  socket.emitChanged(`settings-changed`);
120
186
  return updated;
121
187
  } catch (err) {
@@ -130,4 +196,10 @@ module.exports = {
130
196
  const resp = await axios.default.get('https://raw.githubusercontent.com/dbgate/dbgate/master/CHANGELOG.md');
131
197
  return resp.data;
132
198
  },
199
+
200
+ checkLicense_meta: true,
201
+ async checkLicense({ licenseKey }) {
202
+ const resp = await checkLicenseKey(licenseKey);
203
+ return resp;
204
+ },
133
205
  };
@@ -16,6 +16,9 @@ const { safeJsonParse, getLogger } = require('dbgate-tools');
16
16
  const platformInfo = require('../utility/platformInfo');
17
17
  const { connectionHasPermission, testConnectionPermission } = require('../utility/hasPermission');
18
18
  const pipeForkLogs = require('../utility/pipeForkLogs');
19
+ const requireEngineDriver = require('../utility/requireEngineDriver');
20
+ const { getAuthProviderById } = require('../auth/authProvider');
21
+ const { startTokenChecking } = require('../utility/authProxy');
19
22
 
20
23
  const logger = getLogger('connections');
21
24
 
@@ -70,6 +73,8 @@ function getPortalCollections() {
70
73
  displayName: process.env[`LABEL_${id}`],
71
74
  isReadOnly: process.env[`READONLY_${id}`],
72
75
  databases: process.env[`DBCONFIG_${id}`] ? safeJsonParse(process.env[`DBCONFIG_${id}`]) : null,
76
+ allowedDatabases: process.env[`ALLOWED_DATABASES_${id}`]?.replace(/\|/g, '\n'),
77
+ allowedDatabasesRegex: process.env[`ALLOWED_DATABASES_REGEX_${id}`],
73
78
  parent: process.env[`PARENT_${id}`] || undefined,
74
79
 
75
80
  // SSH tunnel
@@ -199,6 +204,12 @@ module.exports = {
199
204
 
200
205
  list_meta: true,
201
206
  async list(_params, req) {
207
+ const storage = require('./storage');
208
+
209
+ const storageConnections = await storage.connections(req);
210
+ if (storageConnections) {
211
+ return storageConnections;
212
+ }
202
213
  if (portalConnections) {
203
214
  if (platformInfo.allowShellConnection) return portalConnections;
204
215
  return portalConnections.map(maskConnection).filter(x => connectionHasPermission(x, req));
@@ -236,14 +247,16 @@ module.exports = {
236
247
  },
237
248
 
238
249
  saveVolatile_meta: true,
239
- async saveVolatile({ conid, user, password, test }) {
250
+ async saveVolatile({ conid, user = undefined, password = undefined, accessToken = undefined, test = false }) {
240
251
  const old = await this.getCore({ conid });
241
252
  const res = {
242
253
  ...old,
243
254
  _id: crypto.randomUUID(),
244
255
  password,
256
+ accessToken,
245
257
  passwordMode: undefined,
246
258
  unsaved: true,
259
+ useRedirectDbLogin: false,
247
260
  };
248
261
  if (old.passwordMode == 'askUser') {
249
262
  res.user = user;
@@ -336,6 +349,14 @@ module.exports = {
336
349
  if (volatile) {
337
350
  return volatile;
338
351
  }
352
+
353
+ const storage = require('./storage');
354
+
355
+ const storageConnection = await storage.getConnection({ conid });
356
+ if (storageConnection) {
357
+ return storageConnection;
358
+ }
359
+
339
360
  if (portalConnections) {
340
361
  const res = portalConnections.find(x => x._id == conid) || null;
341
362
  return mask && !platformInfo.allowShellConnection ? maskConnection(res) : res;
@@ -365,4 +386,95 @@ module.exports = {
365
386
  });
366
387
  return res;
367
388
  },
389
+
390
+ dbloginWeb_meta: {
391
+ raw: true,
392
+ method: 'get',
393
+ },
394
+ async dbloginWeb(req, res) {
395
+ const { conid, state, redirectUri } = req.query;
396
+ const connection = await this.getCore({ conid });
397
+ const driver = requireEngineDriver(connection);
398
+ const authResp = await driver.getRedirectAuthUrl(connection, {
399
+ redirectUri,
400
+ state,
401
+ client: 'web',
402
+ });
403
+ res.redirect(authResp.url);
404
+ },
405
+
406
+ dbloginApp_meta: true,
407
+ async dbloginApp({ conid, state }) {
408
+ const connection = await this.getCore({ conid });
409
+ const driver = requireEngineDriver(connection);
410
+ const resp = await driver.getRedirectAuthUrl(connection, {
411
+ state,
412
+ client: 'app',
413
+ });
414
+ startTokenChecking(resp.sid, async token => {
415
+ const volatile = await this.saveVolatile({ conid, accessToken: token });
416
+ socket.emit('got-volatile-token', { savedConId: conid, volatileConId: volatile._id });
417
+ });
418
+ return resp;
419
+ },
420
+
421
+ dbloginToken_meta: true,
422
+ async dbloginToken({ code, conid, strmid, redirectUri, sid }) {
423
+ try {
424
+ const connection = await this.getCore({ conid });
425
+ const driver = requireEngineDriver(connection);
426
+ const accessToken = await driver.getAuthTokenFromCode(connection, { sid, code, redirectUri });
427
+ const volatile = await this.saveVolatile({ conid, accessToken });
428
+ // console.log('******************************** WE HAVE ACCESS TOKEN', accessToken);
429
+ socket.emit('got-volatile-token', { strmid, savedConId: conid, volatileConId: volatile._id });
430
+ return { success: true };
431
+ } catch (err) {
432
+ logger.error({ err }, 'Error getting DB token');
433
+ return { error: err.message };
434
+ }
435
+ },
436
+
437
+ dbloginAuthToken_meta: true,
438
+ async dbloginAuthToken({ amoid, code, conid, redirectUri, sid }) {
439
+ try {
440
+ const connection = await this.getCore({ conid });
441
+ const driver = requireEngineDriver(connection);
442
+ const accessToken = await driver.getAuthTokenFromCode(connection, { code, redirectUri, sid });
443
+ const volatile = await this.saveVolatile({ conid, accessToken });
444
+ const authProvider = getAuthProviderById(amoid);
445
+ const resp = await authProvider.login(null, null, { conid: volatile._id });
446
+ return resp;
447
+ } catch (err) {
448
+ logger.error({ err }, 'Error getting DB token');
449
+ return { error: err.message };
450
+ }
451
+ },
452
+
453
+ dbloginAuth_meta: true,
454
+ async dbloginAuth({ amoid, conid, user, password }) {
455
+ if (user || password) {
456
+ const saveResp = await this.saveVolatile({ conid, user, password, test: true });
457
+ if (saveResp.msgtype == 'connected') {
458
+ const loginResp = await getAuthProviderById(amoid).login(user, password, { conid: saveResp._id });
459
+ return loginResp;
460
+ }
461
+ return saveResp;
462
+ }
463
+
464
+ // user and password is stored in connection, volatile connection is not needed
465
+ const loginResp = await getAuthProviderById(amoid).login(null, null, { conid });
466
+ return loginResp;
467
+ },
468
+
469
+ volatileDbloginFromAuth_meta: true,
470
+ async volatileDbloginFromAuth({ conid }, req) {
471
+ const connection = await this.getCore({ conid });
472
+ const driver = requireEngineDriver(connection);
473
+ const accessToken = await driver.getAccessTokenFromAuth(connection, req);
474
+ if (accessToken) {
475
+ const volatile = await this.saveVolatile({ conid, accessToken });
476
+ return volatile;
477
+ }
478
+ return null;
479
+ },
368
480
  };
@@ -89,6 +89,9 @@ module.exports = {
89
89
  if (connection.passwordMode == 'askPassword' || connection.passwordMode == 'askUser') {
90
90
  throw new MissingCredentialsError({ conid, passwordMode: connection.passwordMode });
91
91
  }
92
+ if (connection.useRedirectDbLogin) {
93
+ throw new MissingCredentialsError({ conid, redirectToDbLogin: true });
94
+ }
92
95
  const subprocess = fork(
93
96
  global['API_PACKAGE'] || process.argv[1],
94
97
  [
@@ -179,6 +182,15 @@ module.exports = {
179
182
  return res;
180
183
  },
181
184
 
185
+ runOperation_meta: true,
186
+ async runOperation({ conid, database, operation, useTransaction }, req) {
187
+ testConnectionPermission(conid, req);
188
+ logger.info({ conid, database, operation }, 'Processing operation');
189
+ const opened = await this.ensureOpened(conid, database);
190
+ const res = await this.sendRequest(opened, { msgtype: 'runOperation', operation, useTransaction });
191
+ return res;
192
+ },
193
+
182
194
  collectionData_meta: true,
183
195
  async collectionData({ conid, database, options }, req) {
184
196
  testConnectionPermission(conid, req);
@@ -42,13 +42,14 @@ module.exports = {
42
42
 
43
43
  info_meta: true,
44
44
  async info({ packageName }) {
45
+ // @ts-ignore
46
+ const isPackaged = await fs.exists(path.join(packagedPluginsDir(), packageName));
47
+
45
48
  try {
46
49
  const infoResp = await axios.default.get(`https://registry.npmjs.org/${packageName}`);
47
50
  const { latest } = infoResp.data['dist-tags'];
48
51
  const manifest = infoResp.data.versions[latest];
49
52
  const { readme } = infoResp.data;
50
- // @ts-ignore
51
- const isPackaged = await fs.exists(path.join(packagedPluginsDir(), packageName));
52
53
 
53
54
  return {
54
55
  readme,
@@ -57,6 +58,7 @@ module.exports = {
57
58
  };
58
59
  } catch (err) {
59
60
  return {
61
+ isPackaged,
60
62
  state: 'error',
61
63
  error: err.message,
62
64
  };
@@ -1,3 +1,4 @@
1
+ const crypto = require('crypto');
1
2
  const connections = require('./connections');
2
3
  const socket = require('../utility/socket');
3
4
  const { fork } = require('child_process');
@@ -56,6 +57,9 @@ module.exports = {
56
57
  if (connection.passwordMode == 'askPassword' || connection.passwordMode == 'askUser') {
57
58
  throw new MissingCredentialsError({ conid, passwordMode: connection.passwordMode });
58
59
  }
60
+ if (connection.useRedirectDbLogin) {
61
+ throw new MissingCredentialsError({ conid, redirectToDbLogin: true });
62
+ }
59
63
  const subprocess = fork(
60
64
  global['API_PACKAGE'] || process.argv[1],
61
65
  [
@@ -181,22 +185,29 @@ module.exports = {
181
185
  return { status: 'ok' };
182
186
  },
183
187
 
184
- createDatabase_meta: true,
185
- async createDatabase({ conid, name }, req) {
188
+ async sendDatabaseOp({ conid, msgtype, name }, req) {
186
189
  testConnectionPermission(conid, req);
187
190
  const opened = await this.ensureOpened(conid);
188
191
  if (opened.connection.isReadOnly) return false;
189
- opened.subprocess.send({ msgtype: 'createDatabase', name });
190
- return { status: 'ok' };
192
+ const res = await this.sendRequest(opened, { msgtype, name });
193
+ if (res.errorMessage) {
194
+ console.error(res.errorMessage);
195
+
196
+ return {
197
+ apiErrorMessage: res.errorMessage,
198
+ };
199
+ }
200
+ return res.result || null;
201
+ },
202
+
203
+ createDatabase_meta: true,
204
+ async createDatabase({ conid, name }, req) {
205
+ return this.sendDatabaseOp({ conid, msgtype: 'createDatabase', name }, req);
191
206
  },
192
207
 
193
208
  dropDatabase_meta: true,
194
209
  async dropDatabase({ conid, name }, req) {
195
- testConnectionPermission(conid, req);
196
- const opened = await this.ensureOpened(conid);
197
- if (opened.connection.isReadOnly) return false;
198
- opened.subprocess.send({ msgtype: 'dropDatabase', name });
199
- return { status: 'ok' };
210
+ return this.sendDatabaseOp({ conid, msgtype: 'dropDatabase', name }, req);
200
211
  },
201
212
 
202
213
  sendRequest(conn, message) {
@@ -0,0 +1,20 @@
1
+ module.exports = {
2
+ connections_meta: true,
3
+ async connections(req) {
4
+ return null;
5
+ },
6
+
7
+ getConnection_meta: true,
8
+ async getConnection({ conid }) {
9
+ return null;
10
+ },
11
+
12
+ async loadSuperadminPermissions() {
13
+ return [];
14
+ },
15
+
16
+ getConnectionsForLoginPage_meta: true,
17
+ async getConnectionsForLoginPage() {
18
+ return null;
19
+ },
20
+ };
@@ -1,5 +1,5 @@
1
1
 
2
2
  module.exports = {
3
- version: '5.3.3',
4
- buildTime: '2024-08-01T06:28:34.974Z'
3
+ version: '5.4.0',
4
+ buildTime: '2024-09-03T13:07:02.765Z'
5
5
  };
package/src/index.js CHANGED
@@ -97,9 +97,12 @@ if (processArgs.listenApi) {
97
97
  }
98
98
 
99
99
  const shell = require('./shell/index');
100
- const dbgateTools = require('dbgate-tools');
100
+ const currentVersion = require('./currentVersion');
101
101
 
102
- global['DBGATE_TOOLS'] = dbgateTools;
102
+ global.DBGATE_PACKAGES = {
103
+ 'dbgate-tools': require('dbgate-tools'),
104
+ 'dbgate-sqltree': require('dbgate-sqltree'),
105
+ };
103
106
 
104
107
  if (processArgs.startProcess) {
105
108
  const proc = require('./proc');
@@ -116,6 +119,7 @@ module.exports = {
116
119
  ...shell,
117
120
  getLogger,
118
121
  configureLogger,
122
+ currentVersion,
119
123
  // loadLogsContent,
120
124
  getMainModule: () => require('./main'),
121
125
  };
package/src/main.js CHANGED
@@ -18,6 +18,7 @@ const sessions = require('./controllers/sessions');
18
18
  const runners = require('./controllers/runners');
19
19
  const jsldata = require('./controllers/jsldata');
20
20
  const config = require('./controllers/config');
21
+ const storage = require('./controllers/storage');
21
22
  const archive = require('./controllers/archive');
22
23
  const apps = require('./controllers/apps');
23
24
  const auth = require('./controllers/auth');
@@ -31,9 +32,9 @@ const onFinished = require('on-finished');
31
32
  const { rundir } = require('./utility/directories');
32
33
  const platformInfo = require('./utility/platformInfo');
33
34
  const getExpressPath = require('./utility/getExpressPath');
34
- const { getLogins } = require('./utility/hasPermission');
35
35
  const _ = require('lodash');
36
36
  const { getLogger } = require('dbgate-tools');
37
+ const { getDefaultAuthProvider } = require('./auth/authProvider');
37
38
 
38
39
  const logger = getLogger('main');
39
40
 
@@ -44,11 +45,23 @@ function start() {
44
45
 
45
46
  const server = http.createServer(app);
46
47
 
47
- const logins = getLogins();
48
- if (logins && process.env.BASIC_AUTH) {
48
+ if (process.env.BASIC_AUTH && !process.env.STORAGE_DATABASE) {
49
+ async function authorizer(username, password, cb) {
50
+ try {
51
+ const resp = await getDefaultAuthProvider().login(username, password);
52
+ if (resp.accessToken) {
53
+ cb(null, true);
54
+ } else {
55
+ cb(null, false);
56
+ }
57
+ } catch (err) {
58
+ cb(err, false);
59
+ }
60
+ }
49
61
  app.use(
50
62
  basicAuth({
51
- users: _.fromPairs(logins.filter(x => x.password).map(x => [x.login, x.password])),
63
+ authorizer,
64
+ authorizeAsync: true,
52
65
  challenge: true,
53
66
  realm: 'DbGate Web App',
54
67
  })
@@ -72,9 +85,7 @@ function start() {
72
85
  });
73
86
  }
74
87
 
75
- if (auth.shouldAuthorizeApi()) {
76
- app.use(auth.authMiddleware);
77
- }
88
+ app.use(auth.authMiddleware);
78
89
 
79
90
  app.get(getExpressPath('/stream'), async function (req, res) {
80
91
  const strmid = req.query.strmid;
@@ -162,6 +173,7 @@ function useAllControllers(app, electron) {
162
173
  useController(app, electron, '/runners', runners);
163
174
  useController(app, electron, '/jsldata', jsldata);
164
175
  useController(app, electron, '/config', config);
176
+ useController(app, electron, '/storage', storage);
165
177
  useController(app, electron, '/archive', archive);
166
178
  useController(app, electron, '/uploads', uploads);
167
179
  useController(app, electron, '/plugins', plugins);
@@ -3,5 +3,7 @@
3
3
  const content = {};
4
4
 
5
5
  content['better-sqlite3'] = () => require('better-sqlite3');
6
+ content['oracledb'] = () => require('oracledb');
7
+
6
8
 
7
9
  module.exports = content;
@@ -170,6 +170,18 @@ async function handleRunScript({ msgid, sql, useTransaction }, skipReadonlyCheck
170
170
  }
171
171
  }
172
172
 
173
+ async function handleRunOperation({ msgid, operation, useTransaction }, skipReadonlyCheck = false) {
174
+ await waitConnected();
175
+ const driver = requireEngineDriver(storedConnection);
176
+ try {
177
+ if (!skipReadonlyCheck) ensureExecuteCustomScript(driver);
178
+ await driver.operation(systemConnection, operation, { useTransaction });
179
+ process.send({ msgtype: 'response', msgid });
180
+ } catch (err) {
181
+ process.send({ msgtype: 'response', msgid, errorMessage: err.message });
182
+ }
183
+ }
184
+
173
185
  async function handleQueryData({ msgid, sql }, skipReadonlyCheck = false) {
174
186
  await waitConnected();
175
187
  const driver = requireEngineDriver(storedConnection);
@@ -311,6 +323,7 @@ const messageHandlers = {
311
323
  connect: handleConnect,
312
324
  queryData: handleQueryData,
313
325
  runScript: handleRunScript,
326
+ runOperation: handleRunOperation,
314
327
  updateCollection: handleUpdateCollection,
315
328
  collectionData: handleCollectionData,
316
329
  loadKeys: handleLoadKeys,
@@ -16,7 +16,18 @@ let afterConnectCallbacks = [];
16
16
  async function handleRefresh() {
17
17
  const driver = requireEngineDriver(storedConnection);
18
18
  try {
19
- const databases = await driver.listDatabases(systemConnection);
19
+ let databases = await driver.listDatabases(systemConnection);
20
+ if (storedConnection?.allowedDatabases?.trim()) {
21
+ const allowedDatabaseList = storedConnection.allowedDatabases
22
+ .split('\n')
23
+ .map(x => x.trim().toLowerCase())
24
+ .filter(x => x);
25
+ databases = databases.filter(x => allowedDatabaseList.includes(x.name.toLocaleLowerCase()));
26
+ }
27
+ if (storedConnection?.allowedDatabasesRegex?.trim()) {
28
+ const regex = new RegExp(storedConnection.allowedDatabasesRegex, 'i');
29
+ databases = databases.filter(x => regex.test(x.name));
30
+ }
20
31
  setStatusName('ok');
21
32
  const databasesString = stableStringify(databases);
22
33
  if (lastDatabases != databasesString) {
@@ -94,18 +105,24 @@ function handlePing() {
94
105
  lastPing = new Date().getTime();
95
106
  }
96
107
 
97
- async function handleDatabaseOp(op, { name }) {
98
- const driver = requireEngineDriver(storedConnection);
99
- systemConnection = await connectUtility(driver, storedConnection, 'app');
100
- if (driver[op]) {
101
- await driver[op](systemConnection, name);
102
- } else {
103
- const dmp = driver.createDumper();
104
- dmp[op](name);
105
- logger.info({ sql: dmp.s }, 'Running script');
106
- await driver.query(systemConnection, dmp.s);
108
+ async function handleDatabaseOp(op, { msgid, name }) {
109
+ try {
110
+ const driver = requireEngineDriver(storedConnection);
111
+ systemConnection = await connectUtility(driver, storedConnection, 'app');
112
+ if (driver[op]) {
113
+ await driver[op](systemConnection, name);
114
+ } else {
115
+ const dmp = driver.createDumper();
116
+ dmp[op](name);
117
+ logger.info({ sql: dmp.s }, 'Running script');
118
+ await driver.query(systemConnection, dmp.s);
119
+ }
120
+ await handleRefresh();
121
+
122
+ process.send({ msgtype: 'response', msgid, status: 'ok' });
123
+ } catch (err) {
124
+ process.send({ msgtype: 'response', msgid, errorMessage: err.message });
107
125
  }
108
- await handleRefresh();
109
126
  }
110
127
 
111
128
  async function handleDriverDataCore(msgid, callMethod) {
@@ -15,10 +15,12 @@ async function getSshConnection(connection) {
15
15
  agentForward: connection.sshMode == 'agent',
16
16
  passphrase: connection.sshMode == 'keyFile' ? connection.sshKeyfilePassword : undefined,
17
17
  username: connection.sshLogin,
18
- password: connection.sshMode == 'userPassword' ? connection.sshPassword : undefined,
18
+ password: (connection.sshMode || 'userPassword') == 'userPassword' ? connection.sshPassword : undefined,
19
19
  agentSocket: connection.sshMode == 'agent' ? platformInfo.sshAuthSock : undefined,
20
20
  privateKey:
21
- connection.sshMode == 'keyFile' && connection.sshKeyfile ? await fs.readFile(connection.sshKeyfile) : undefined,
21
+ connection.sshMode == 'keyFile' && (connection.sshKeyfile || platformInfo?.defaultKeyfile)
22
+ ? await fs.readFile(connection.sshKeyfile || platformInfo?.defaultKeyfile)
23
+ : undefined,
22
24
  skipAutoPrivateKey: true,
23
25
  noReadline: true,
24
26
  };
@@ -0,0 +1,16 @@
1
+ const importDbModel = require('../utility/importDbModel');
2
+ const fs = require('fs');
3
+
4
+ async function dbModelToJson({ modelFolder, outputFile, commonjs }) {
5
+ const dbInfo = await importDbModel(modelFolder);
6
+
7
+ const json = JSON.stringify(dbInfo, null, 2);
8
+ if (commonjs) {
9
+ fs.writeFileSync(outputFile, `module.exports = ${json};`);
10
+ return;
11
+ } else {
12
+ fs.writeFileSync(outputFile, json);
13
+ }
14
+ }
15
+
16
+ module.exports = dbModelToJson;