dbgate-api 4.7.3-alpha.5 → 4.7.4-alpha.10

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/.env CHANGED
@@ -1 +1,14 @@
1
1
  DEVMODE=1
2
+ # PERMISSIONS=~widgets/app,~widgets/plugins
3
+ # DISABLE_SHELL=1
4
+ # HIDE_APP_EDITOR=1
5
+
6
+
7
+ DEVWEB=1
8
+ LOGINS=admin,test
9
+
10
+ LOGIN_PASSWORD_admin=admin
11
+ LOGIN_PERMISSIONS_admin=*
12
+
13
+ LOGIN_PASSWORD_test=test
14
+ LOGIN_PERMISSIONS_test=~*, widgets/database
package/env/singledb/.env CHANGED
@@ -8,6 +8,8 @@ USER_mysql=root
8
8
  PASSWORD_mysql=test
9
9
  PORT_mysql=3307
10
10
  ENGINE_mysql=mysql@dbgate-plugin-mysql
11
+ DBCONFIG_mysql=[{"name":"Chinook","connectionColor":"cyan"}]
12
+
11
13
 
12
14
  SINGLE_CONNECTION=mysql
13
15
  SINGLE_DATABASE=Chinook
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dbgate-api",
3
3
  "main": "src/index.js",
4
- "version": "4.7.3-alpha.5",
4
+ "version": "4.7.4-alpha.10",
5
5
  "homepage": "https://dbgate.org/",
6
6
  "repository": {
7
7
  "type": "git",
@@ -25,9 +25,9 @@
25
25
  "compare-versions": "^3.6.0",
26
26
  "cors": "^2.8.5",
27
27
  "cross-env": "^6.0.3",
28
- "dbgate-query-splitter": "^4.7.3-alpha.5",
29
- "dbgate-sqltree": "^4.7.3-alpha.5",
30
- "dbgate-tools": "^4.7.3-alpha.5",
28
+ "dbgate-query-splitter": "^4.7.4-alpha.10",
29
+ "dbgate-sqltree": "^4.7.4-alpha.10",
30
+ "dbgate-tools": "^4.7.4-alpha.10",
31
31
  "diff": "^5.0.0",
32
32
  "diff2html": "^3.4.13",
33
33
  "eslint": "^6.8.0",
@@ -63,7 +63,7 @@
63
63
  "devDependencies": {
64
64
  "@types/fs-extra": "^9.0.11",
65
65
  "@types/lodash": "^4.14.149",
66
- "dbgate-types": "^4.7.3-alpha.5",
66
+ "dbgate-types": "^4.7.4-alpha.10",
67
67
  "env-cmd": "^10.1.0",
68
68
  "node-loader": "^1.0.2",
69
69
  "nodemon": "^2.0.2",
@@ -3,7 +3,7 @@ const os = require('os');
3
3
  const path = require('path');
4
4
  const axios = require('axios');
5
5
  const { datadir } = require('../utility/directories');
6
- const hasPermission = require('../utility/hasPermission');
6
+ const { hasPermission, getLogins } = require('../utility/hasPermission');
7
7
  const socket = require('../utility/socket');
8
8
  const _ = require('lodash');
9
9
  const AsyncLock = require('async-lock');
@@ -26,17 +26,31 @@ module.exports = {
26
26
  // },
27
27
 
28
28
  get_meta: true,
29
- async get() {
30
- const permissions = process.env.PERMISSIONS ? process.env.PERMISSIONS.split(',') : null;
29
+ async get(_params, req) {
30
+ const logins = getLogins();
31
+ const login = logins ? logins.find(x => x.login == (req.auth && req.auth.user)) : null;
32
+ const permissions = login ? login.permissions : null;
31
33
 
32
34
  return {
33
35
  runAsPortal: !!connections.portalConnections,
34
36
  singleDatabase: connections.singleDatabase,
37
+ // hideAppEditor: !!process.env.HIDE_APP_EDITOR,
38
+ allowShellConnection: platformInfo.allowShellConnection,
39
+ allowShellScripting: platformInfo.allowShellConnection,
35
40
  permissions,
41
+ login,
36
42
  ...currentVersion,
37
43
  };
38
44
  },
39
45
 
46
+ logout_meta: {
47
+ method: 'get',
48
+ raw: true,
49
+ },
50
+ logout(req, res) {
51
+ res.status(401).send('Logged out<br><a href="../..">Back to DbGate</a>');
52
+ },
53
+
40
54
  platformInfo_meta: true,
41
55
  async platformInfo() {
42
56
  return platformInfo;
@@ -64,8 +78,8 @@ module.exports = {
64
78
  },
65
79
 
66
80
  updateSettings_meta: true,
67
- async updateSettings(values) {
68
- if (!hasPermission(`settings/change`)) return false;
81
+ async updateSettings(values, req) {
82
+ if (!hasPermission(`settings/change`, req)) return false;
69
83
 
70
84
  const res = await lock.acquire('update', async () => {
71
85
  const currentValue = await this.getSettings();
@@ -5,12 +5,14 @@ const fs = require('fs-extra');
5
5
 
6
6
  const { datadir, filesdir } = require('../utility/directories');
7
7
  const socket = require('../utility/socket');
8
- const { encryptConnection } = require('../utility/crypting');
8
+ const { encryptConnection, maskConnection } = require('../utility/crypting');
9
9
  const { handleProcessCommunication } = require('../utility/processComm');
10
10
  const { pickSafeConnectionInfo } = require('../utility/crypting');
11
11
  const JsonLinesDatabase = require('../utility/JsonLinesDatabase');
12
12
 
13
13
  const processArgs = require('../utility/processArgs');
14
+ const { safeJsonParse } = require('dbgate-tools');
15
+ const platformInfo = require('../utility/platformInfo');
14
16
 
15
17
  function getNamedArgs() {
16
18
  const res = {};
@@ -55,6 +57,8 @@ function getPortalCollections() {
55
57
  (process.env[`FILE_${id}`] ? getDatabaseFileLabel(process.env[`FILE_${id}`]) : null),
56
58
  singleDatabase: !!process.env[`DATABASE_${id}`] || !!process.env[`FILE_${id}`],
57
59
  displayName: process.env[`LABEL_${id}`],
60
+ isReadOnly: process.env[`READONLY_${id}`],
61
+ databases: process.env[`DBCONFIG_${id}`] ? safeJsonParse(process.env[`DBCONFIG_${id}`]) : null,
58
62
 
59
63
  // SSH tunnel
60
64
  useSshTunnel: process.env[`USE_SSH_${id}`],
@@ -162,7 +166,9 @@ module.exports = {
162
166
 
163
167
  list_meta: true,
164
168
  async list() {
165
- return portalConnections || this.datastore.find();
169
+ return portalConnections && !platformInfo.allowShellConnection
170
+ ? portalConnections.map(maskConnection)
171
+ : this.datastore.find();
166
172
  },
167
173
 
168
174
  test_meta: true,
@@ -199,6 +205,9 @@ module.exports = {
199
205
  }
200
206
  socket.emitChanged('connection-list-changed');
201
207
  socket.emitChanged('used-apps-changed');
208
+ if (this._closeAll) {
209
+ this._closeAll(connection._id);
210
+ }
202
211
  // for (const db of connection.databases || []) {
203
212
  // socket.emitChanged(`db-apps-changed-${connection._id}-${db.name}`);
204
213
  // }
@@ -238,13 +247,21 @@ module.exports = {
238
247
  return res;
239
248
  },
240
249
 
241
- get_meta: true,
242
- async get({ conid }) {
243
- if (portalConnections) return portalConnections.find(x => x._id == conid) || null;
250
+ async getCore({ conid, mask = false }) {
251
+ if (!conid) return null;
252
+ if (portalConnections) {
253
+ const res = portalConnections.find(x => x._id == conid) || null;
254
+ return mask && !platformInfo.allowShellConnection ? maskConnection(res) : res;
255
+ }
244
256
  const res = await this.datastore.get(conid);
245
257
  return res || null;
246
258
  },
247
259
 
260
+ get_meta: true,
261
+ async get({ conid }) {
262
+ return this.getCore({ conid, mask: true });
263
+ },
264
+
248
265
  newSqliteDatabase_meta: true,
249
266
  async newSqliteDatabase({ file }) {
250
267
  const sqliteDir = path.join(filesdir(), 'sqlite');
@@ -33,6 +33,10 @@ module.exports = {
33
33
  closed: {},
34
34
  requests: {},
35
35
 
36
+ async _init() {
37
+ connections._closeAll = conid => this.closeAll(conid);
38
+ },
39
+
36
40
  handle_structure(conid, database, { structure }) {
37
41
  const existing = this.opened.find(x => x.conid == conid && x.database == database);
38
42
  if (!existing) return;
@@ -75,7 +79,7 @@ module.exports = {
75
79
  async ensureOpened(conid, database) {
76
80
  const existing = this.opened.find(x => x.conid == conid && x.database == database);
77
81
  if (existing) return existing;
78
- const connection = await connections.get({ conid });
82
+ const connection = await connections.getCore({ conid });
79
83
  const subprocess = fork(global['API_PACKAGE'] || process.argv[1], [
80
84
  '--is-forked-api',
81
85
  '--start-process',
@@ -136,6 +140,13 @@ module.exports = {
136
140
  return res;
137
141
  },
138
142
 
143
+ sqlSelect_meta: true,
144
+ async sqlSelect({ conid, database, select }) {
145
+ const opened = await this.ensureOpened(conid, database);
146
+ const res = await this.sendRequest(opened, { msgtype: 'sqlSelect', select });
147
+ return res;
148
+ },
149
+
139
150
  runScript_meta: true,
140
151
  async runScript({ conid, database, sql }) {
141
152
  console.log(`Processing script, conid=${conid}, database=${database}, sql=${sql}`);
@@ -148,14 +159,64 @@ module.exports = {
148
159
  async collectionData({ conid, database, options }) {
149
160
  const opened = await this.ensureOpened(conid, database);
150
161
  const res = await this.sendRequest(opened, { msgtype: 'collectionData', options });
151
- return res.result;
162
+ return res.result || null;
163
+ },
164
+
165
+ async loadDataCore(msgtype, { conid, database, ...args }) {
166
+ const opened = await this.ensureOpened(conid, database);
167
+ const res = await this.sendRequest(opened, { msgtype, ...args });
168
+ if (res.errorMessage) {
169
+ console.error(res.errorMessage);
170
+
171
+ return {
172
+ errorMessage: res.errorMessage,
173
+ };
174
+ }
175
+ return res.result || null;
176
+ },
177
+
178
+ loadKeys_meta: true,
179
+ async loadKeys({ conid, database, root }) {
180
+ return this.loadDataCore('loadKeys', { conid, database, root });
181
+ },
182
+
183
+ loadKeyInfo_meta: true,
184
+ async loadKeyInfo({ conid, database, key }) {
185
+ return this.loadDataCore('loadKeyInfo', { conid, database, key });
186
+ },
187
+
188
+ loadKeyTableRange_meta: true,
189
+ async loadKeyTableRange({ conid, database, key, cursor, count }) {
190
+ return this.loadDataCore('loadKeyTableRange', { conid, database, key, cursor, count });
191
+ },
192
+
193
+ loadFieldValues_meta: true,
194
+ async loadFieldValues({ conid, database, schemaName, pureName, field, search }) {
195
+ return this.loadDataCore('loadFieldValues', { conid, database, schemaName, pureName, field, search });
196
+ },
197
+
198
+ callMethod_meta: true,
199
+ async callMethod({ conid, database, method, args }) {
200
+ return this.loadDataCore('callMethod', { conid, database, method, args });
201
+
202
+ // const opened = await this.ensureOpened(conid, database);
203
+ // const res = await this.sendRequest(opened, { msgtype: 'callMethod', method, args });
204
+ // if (res.errorMessage) {
205
+ // console.error(res.errorMessage);
206
+ // }
207
+ // return res.result || null;
152
208
  },
153
209
 
154
210
  updateCollection_meta: true,
155
211
  async updateCollection({ conid, database, changeSet }) {
156
212
  const opened = await this.ensureOpened(conid, database);
157
213
  const res = await this.sendRequest(opened, { msgtype: 'updateCollection', changeSet });
158
- return res.result;
214
+ if (res.errorMessage) {
215
+ return {
216
+ errorMessage: res.errorMessage,
217
+ };
218
+ }
219
+ return res.result || null;
159
220
  },
160
221
 
161
222
  status_meta: true,
@@ -228,6 +289,12 @@ module.exports = {
228
289
  }
229
290
  },
230
291
 
292
+ closeAll(conid, kill = true) {
293
+ for (const existing of this.opened.filter(x => x.conid == conid)) {
294
+ this.close(conid, existing.database, kill);
295
+ }
296
+ },
297
+
231
298
  disconnect_meta: true,
232
299
  async disconnect({ conid, database }) {
233
300
  await this.close(conid, database, true);
@@ -325,8 +392,8 @@ module.exports = {
325
392
  const targetDb = generateDbPairingId(
326
393
  extendDatabaseInfo(await this.structure({ conid: targetConid, database: targetDatabase }))
327
394
  );
328
- // const sourceConnection = await connections.get({conid:sourceConid})
329
- const connection = await connections.get({ conid: targetConid });
395
+ // const sourceConnection = await connections.getCore({conid:sourceConid})
396
+ const connection = await connections.getCore({ conid: targetConid });
330
397
  const driver = requireEngineDriver(connection);
331
398
  const targetDbPaired = matchPairedObjects(sourceDb, targetDb, dbDiffOptions);
332
399
  const diffRows = computeDbDiffRows(sourceDb, targetDbPaired, dbDiffOptions, driver);
@@ -3,7 +3,7 @@ const fs = require('fs-extra');
3
3
  const path = require('path');
4
4
  const { filesdir, archivedir, resolveArchiveFolder, uploadsdir, appdir } = require('../utility/directories');
5
5
  const getChartExport = require('../utility/getChartExport');
6
- const hasPermission = require('../utility/hasPermission');
6
+ const { hasPermission } = require('../utility/hasPermission');
7
7
  const socket = require('../utility/socket');
8
8
  const scheduler = require('./scheduler');
9
9
  const getDiagramExport = require('../utility/getDiagramExport');
@@ -23,8 +23,8 @@ function deserialize(format, text) {
23
23
 
24
24
  module.exports = {
25
25
  list_meta: true,
26
- async list({ folder }) {
27
- if (!hasPermission(`files/${folder}/read`)) return [];
26
+ async list({ folder }, req) {
27
+ if (!hasPermission(`files/${folder}/read`, req)) return [];
28
28
  const dir = path.join(filesdir(), folder);
29
29
  if (!(await fs.exists(dir))) return [];
30
30
  const files = (await fs.readdir(dir)).map(file => ({ folder, file }));
@@ -32,11 +32,11 @@ module.exports = {
32
32
  },
33
33
 
34
34
  listAll_meta: true,
35
- async listAll() {
35
+ async listAll(_params, req) {
36
36
  const folders = await fs.readdir(filesdir());
37
37
  const res = [];
38
38
  for (const folder of folders) {
39
- if (!hasPermission(`files/${folder}/read`)) continue;
39
+ if (!hasPermission(`files/${folder}/read`, req)) continue;
40
40
  const dir = path.join(filesdir(), folder);
41
41
  const files = (await fs.readdir(dir)).map(file => ({ folder, file }));
42
42
  res.push(...files);
@@ -45,31 +45,34 @@ module.exports = {
45
45
  },
46
46
 
47
47
  delete_meta: true,
48
- async delete({ folder, file }) {
49
- if (!hasPermission(`files/${folder}/write`)) return;
48
+ async delete({ folder, file }, req) {
49
+ if (!hasPermission(`files/${folder}/write`, req)) return false;
50
50
  await fs.unlink(path.join(filesdir(), folder, file));
51
51
  socket.emitChanged(`files-changed-${folder}`);
52
52
  socket.emitChanged(`all-files-changed`);
53
+ return true;
53
54
  },
54
55
 
55
56
  rename_meta: true,
56
- async rename({ folder, file, newFile }) {
57
- if (!hasPermission(`files/${folder}/write`)) return;
57
+ async rename({ folder, file, newFile }, req) {
58
+ if (!hasPermission(`files/${folder}/write`, req)) return false;
58
59
  await fs.rename(path.join(filesdir(), folder, file), path.join(filesdir(), folder, newFile));
59
60
  socket.emitChanged(`files-changed-${folder}`);
60
61
  socket.emitChanged(`all-files-changed`);
62
+ return true;
61
63
  },
62
64
 
63
65
  copy_meta: true,
64
- async copy({ folder, file, newFile }) {
65
- if (!hasPermission(`files/${folder}/write`)) return;
66
+ async copy({ folder, file, newFile }, req) {
67
+ if (!hasPermission(`files/${folder}/write`, req)) return false;
66
68
  await fs.copyFile(path.join(filesdir(), folder, file), path.join(filesdir(), folder, newFile));
67
69
  socket.emitChanged(`files-changed-${folder}`);
68
70
  socket.emitChanged(`all-files-changed`);
71
+ return true;
69
72
  },
70
73
 
71
74
  load_meta: true,
72
- async load({ folder, file, format }) {
75
+ async load({ folder, file, format }, req) {
73
76
  if (folder.startsWith('archive:')) {
74
77
  const text = await fs.readFile(path.join(resolveArchiveFolder(folder.substring('archive:'.length)), file), {
75
78
  encoding: 'utf-8',
@@ -81,20 +84,22 @@ module.exports = {
81
84
  });
82
85
  return deserialize(format, text);
83
86
  } else {
84
- if (!hasPermission(`files/${folder}/read`)) return null;
87
+ if (!hasPermission(`files/${folder}/read`, req)) return null;
85
88
  const text = await fs.readFile(path.join(filesdir(), folder, file), { encoding: 'utf-8' });
86
89
  return deserialize(format, text);
87
90
  }
88
91
  },
89
92
 
90
93
  save_meta: true,
91
- async save({ folder, file, data, format }) {
94
+ async save({ folder, file, data, format }, req) {
92
95
  if (folder.startsWith('archive:')) {
96
+ if (!hasPermission(`archive/write`, req)) return false;
93
97
  const dir = resolveArchiveFolder(folder.substring('archive:'.length));
94
98
  await fs.writeFile(path.join(dir, file), serialize(format, data));
95
99
  socket.emitChanged(`archive-files-changed-${folder.substring('archive:'.length)}`);
96
100
  return true;
97
101
  } else if (folder.startsWith('app:')) {
102
+ if (!hasPermission(`apps/write`, req)) return false;
98
103
  const app = folder.substring('app:'.length);
99
104
  await fs.writeFile(path.join(appdir(), app, file), serialize(format, data));
100
105
  socket.emitChanged(`app-files-changed-${app}`);
@@ -102,7 +107,7 @@ module.exports = {
102
107
  apps.emitChangedDbApp(folder);
103
108
  return true;
104
109
  } else {
105
- if (!hasPermission(`files/${folder}/write`)) return false;
110
+ if (!hasPermission(`files/${folder}/write`, req)) return false;
106
111
  const dir = path.join(filesdir(), folder);
107
112
  if (!(await fs.exists(dir))) {
108
113
  await fs.mkdir(dir);
@@ -123,8 +128,8 @@ module.exports = {
123
128
  },
124
129
 
125
130
  favorites_meta: true,
126
- async favorites() {
127
- if (!hasPermission(`files/favorites/read`)) return [];
131
+ async favorites(_params, req) {
132
+ if (!hasPermission(`files/favorites/read`, req)) return [];
128
133
  const dir = path.join(filesdir(), 'favorites');
129
134
  if (!(await fs.exists(dir))) return [];
130
135
  const files = await fs.readdir(dir);
@@ -111,18 +111,22 @@ module.exports = {
111
111
  getInfo_meta: true,
112
112
  async getInfo({ jslid }) {
113
113
  const file = getJslFileName(jslid);
114
- const firstLine = await readFirstLine(file);
115
- if (firstLine) {
116
- const parsed = JSON.parse(firstLine);
117
- if (parsed.__isStreamHeader) {
118
- return parsed;
114
+ try {
115
+ const firstLine = await readFirstLine(file);
116
+ if (firstLine) {
117
+ const parsed = JSON.parse(firstLine);
118
+ if (parsed.__isStreamHeader) {
119
+ return parsed;
120
+ }
121
+ return {
122
+ __isStreamHeader: true,
123
+ __isDynamicStructure: true,
124
+ };
119
125
  }
120
- return {
121
- __isStreamHeader: true,
122
- __isDynamicStructure: true,
123
- };
126
+ return null;
127
+ } catch (err) {
128
+ return null;
124
129
  }
125
- return null;
126
130
  },
127
131
 
128
132
  getRows_meta: true,
@@ -7,7 +7,7 @@ const socket = require('../utility/socket');
7
7
  const compareVersions = require('compare-versions');
8
8
  const requirePlugin = require('../shell/requirePlugin');
9
9
  const downloadPackage = require('../utility/downloadPackage');
10
- const hasPermission = require('../utility/hasPermission');
10
+ const { hasPermission } = require('../utility/hasPermission');
11
11
  const _ = require('lodash');
12
12
  const packagedPluginsContent = require('../packagedPluginsContent');
13
13
 
@@ -73,7 +73,7 @@ module.exports = {
73
73
  const res = [];
74
74
  for (const packageName of _.union(files1, files2)) {
75
75
  if (packageName == 'dist') continue;
76
- // if (!/^dbgate-plugin-.*$/.test(packageName)) continue;
76
+ if (!/^dbgate-plugin-.*$/.test(packageName)) continue;
77
77
  try {
78
78
  if (packagedContent && packagedContent[packageName]) {
79
79
  const manifest = {
@@ -115,8 +115,8 @@ module.exports = {
115
115
  // },
116
116
 
117
117
  install_meta: true,
118
- async install({ packageName }) {
119
- if (!hasPermission(`plugins/install`)) return;
118
+ async install({ packageName }, req) {
119
+ if (!hasPermission(`plugins/install`, req)) return;
120
120
  const dir = path.join(pluginsdir(), packageName);
121
121
  // @ts-ignore
122
122
  if (!(await fs.exists(dir))) {
@@ -128,8 +128,8 @@ module.exports = {
128
128
  },
129
129
 
130
130
  uninstall_meta: true,
131
- async uninstall({ packageName }) {
132
- if (!hasPermission(`plugins/install`)) return;
131
+ async uninstall({ packageName }, req) {
132
+ if (!hasPermission(`plugins/install`, req)) return;
133
133
  const dir = path.join(pluginsdir(), packageName);
134
134
  await fs.rmdir(dir, { recursive: true });
135
135
  socket.emitChanged(`installed-plugins-changed`);
@@ -138,8 +138,8 @@ module.exports = {
138
138
  },
139
139
 
140
140
  upgrade_meta: true,
141
- async upgrade({ packageName }) {
142
- if (!hasPermission(`plugins/install`)) return;
141
+ async upgrade({ packageName }, req) {
142
+ if (!hasPermission(`plugins/install`, req)) return;
143
143
  const dir = path.join(pluginsdir(), packageName);
144
144
  // @ts-ignore
145
145
  if (await fs.exists(dir)) {
@@ -6,9 +6,10 @@ const byline = require('byline');
6
6
  const socket = require('../utility/socket');
7
7
  const { fork } = require('child_process');
8
8
  const { rundir, uploadsdir, pluginsdir, getPluginBackendPath, packagedPluginList } = require('../utility/directories');
9
- const { extractShellApiPlugins, extractShellApiFunctionName } = require('dbgate-tools');
9
+ const { extractShellApiPlugins, extractShellApiFunctionName, jsonScriptToJavascript } = require('dbgate-tools');
10
10
  const { handleProcessCommunication } = require('../utility/processComm');
11
11
  const processArgs = require('../utility/processArgs');
12
+ const platformInfo = require('../utility/platformInfo');
12
13
 
13
14
  function extractPlugins(script) {
14
15
  const requireRegex = /\s*\/\/\s*@require\s+([^\s]+)\s*\n/g;
@@ -150,6 +151,16 @@ module.exports = {
150
151
  start_meta: true,
151
152
  async start({ script }) {
152
153
  const runid = uuidv1();
154
+
155
+ if (script.type == 'json') {
156
+ const js = jsonScriptToJavascript(script);
157
+ return this.startCore(runid, scriptTemplate(js, false));
158
+ }
159
+
160
+ if (!platformInfo.allowShellScripting) {
161
+ return { errorMessage: 'Shell scripting is not allowed' };
162
+ }
163
+
153
164
  return this.startCore(runid, scriptTemplate(script, false));
154
165
  },
155
166
 
@@ -3,7 +3,7 @@ const fs = require('fs-extra');
3
3
  const path = require('path');
4
4
  const cron = require('node-cron');
5
5
  const runners = require('./runners');
6
- const hasPermission = require('../utility/hasPermission');
6
+ const { hasPermission } = require('../utility/hasPermission');
7
7
 
8
8
  const scheduleRegex = /\s*\/\/\s*@schedule\s+([^\n]+)\n/;
9
9
 
@@ -26,8 +26,8 @@ module.exports = {
26
26
  this.tasks.push(task);
27
27
  },
28
28
 
29
- async reload() {
30
- if (!hasPermission('files/shell/read')) return;
29
+ async reload(_params, req) {
30
+ if (!hasPermission('files/shell/read', req)) return;
31
31
  const shellDir = path.join(filesdir(), 'shell');
32
32
  await this.unload();
33
33
  if (!(await fs.exists(shellDir))) return;
@@ -37,7 +37,7 @@ module.exports = {
37
37
  const res = await lock.acquire(conid, async () => {
38
38
  const existing = this.opened.find(x => x.conid == conid);
39
39
  if (existing) return existing;
40
- const connection = await connections.get({ conid });
40
+ const connection = await connections.getCore({ conid });
41
41
  const subprocess = fork(global['API_PACKAGE'] || process.argv[1], [
42
42
  '--is-forked-api',
43
43
  '--start-process',
@@ -4,8 +4,10 @@ const connections = require('./connections');
4
4
  const socket = require('../utility/socket');
5
5
  const { fork } = require('child_process');
6
6
  const jsldata = require('./jsldata');
7
+ const path = require('path');
7
8
  const { handleProcessCommunication } = require('../utility/processComm');
8
9
  const processArgs = require('../utility/processArgs');
10
+ const { appdir } = require('../utility/directories');
9
11
 
10
12
  module.exports = {
11
13
  /** @type {import('dbgate-types').OpenedSession[]} */
@@ -46,9 +48,18 @@ module.exports = {
46
48
  this.dispatchMessage(sesid, info);
47
49
  },
48
50
 
49
- handle_done(sesid) {
51
+ handle_done(sesid, props) {
50
52
  socket.emit(`session-done-${sesid}`);
51
- this.dispatchMessage(sesid, 'Query execution finished');
53
+ if (!props.skipFinishedMessage) {
54
+ this.dispatchMessage(sesid, 'Query execution finished');
55
+ }
56
+ const session = this.opened.find(x => x.sesid == sesid);
57
+ if (session.loadingReader_jslid) {
58
+ socket.emit(`session-jslid-done-${session.loadingReader_jslid}`);
59
+ }
60
+ if (session.killOnDone) {
61
+ this.kill({ sesid });
62
+ }
52
63
  },
53
64
 
54
65
  handle_recordset(sesid, props) {
@@ -60,12 +71,17 @@ module.exports = {
60
71
  jsldata.notifyChangedStats(stats);
61
72
  },
62
73
 
74
+ handle_initializeFile(sesid, props) {
75
+ const { jslid } = props;
76
+ socket.emit(`session-initialize-file-${jslid}`);
77
+ },
78
+
63
79
  handle_ping() {},
64
80
 
65
81
  create_meta: true,
66
82
  async create({ conid, database }) {
67
83
  const sesid = uuidv1();
68
- const connection = await connections.get({ conid });
84
+ const connection = await connections.getCore({ conid });
69
85
  const subprocess = fork(global['API_PACKAGE'] || process.argv[1], [
70
86
  '--is-forked-api',
71
87
  '--start-process',
@@ -105,6 +121,29 @@ module.exports = {
105
121
  return { state: 'ok' };
106
122
  },
107
123
 
124
+ executeReader_meta: true,
125
+ async executeReader({ conid, database, sql, queryName, appFolder }) {
126
+ const { sesid } = await this.create({ conid, database });
127
+ const session = this.opened.find(x => x.sesid == sesid);
128
+ session.killOnDone = true;
129
+ const jslid = uuidv1();
130
+ session.loadingReader_jslid = jslid;
131
+ const fileName = queryName && appFolder ? path.join(appdir(), appFolder, `${queryName}.query.sql`) : null;
132
+
133
+ session.subprocess.send({ msgtype: 'executeReader', sql, fileName, jslid });
134
+
135
+ return { jslid };
136
+ },
137
+
138
+ stopLoadingReader_meta: true,
139
+ async stopLoadingReader({ jslid }) {
140
+ const session = this.opened.find(x => x.loadingReader_jslid == jslid);
141
+ if (session) {
142
+ this.kill({ sesid: session.sesid });
143
+ }
144
+ return true;
145
+ },
146
+
108
147
  // cancel_meta: true,
109
148
  // async cancel({ sesid }) {
110
149
  // const session = this.opened.find((x) => x.sesid == sesid);
@@ -1,5 +1,5 @@
1
1
 
2
2
  module.exports = {
3
- version: '4.7.3-alpha.5',
4
- buildTime: '2022-03-14T18:31:03.883Z'
3
+ version: '4.7.4-alpha.10',
4
+ buildTime: '2022-03-21T19:56:58.513Z'
5
5
  };
package/src/main.js CHANGED
@@ -29,6 +29,8 @@ const queryHistory = require('./controllers/queryHistory');
29
29
  const { rundir } = require('./utility/directories');
30
30
  const platformInfo = require('./utility/platformInfo');
31
31
  const getExpressPath = require('./utility/getExpressPath');
32
+ const { getLogins } = require('./utility/hasPermission');
33
+ const _ = require('lodash');
32
34
 
33
35
  function start() {
34
36
  // console.log('process.argv', process.argv);
@@ -37,12 +39,11 @@ function start() {
37
39
 
38
40
  const server = http.createServer(app);
39
41
 
40
- if (process.env.LOGIN && process.env.PASSWORD) {
42
+ const logins = getLogins();
43
+ if (logins) {
41
44
  app.use(
42
45
  basicAuth({
43
- users: {
44
- [process.env.LOGIN]: process.env.PASSWORD,
45
- },
46
+ users: _.fromPairs(logins.map(x => [x.login, x.password])),
46
47
  challenge: true,
47
48
  realm: 'DbGate Web App',
48
49
  })
@@ -85,15 +86,7 @@ function start() {
85
86
  if (platformInfo.isDocker) {
86
87
  // server static files inside docker container
87
88
  app.use(getExpressPath('/'), express.static('/home/dbgate-docker/public'));
88
- } else {
89
- if (!platformInfo.isNpmDist) {
90
- app.get(getExpressPath('/'), (req, res) => {
91
- res.send('DbGate API');
92
- });
93
- }
94
- }
95
-
96
- if (platformInfo.isNpmDist) {
89
+ } else if (platformInfo.isNpmDist) {
97
90
  app.use(getExpressPath('/'), express.static(path.join(__dirname, '../../dbgate-web/public')));
98
91
  getPort({
99
92
  port: parseInt(
@@ -105,7 +98,19 @@ function start() {
105
98
  console.log(`DbGate API listening on port ${port}`);
106
99
  });
107
100
  });
101
+ } else if (process.env.DEVWEB) {
102
+ console.log('__dirname', __dirname);
103
+ console.log(path.join(__dirname, '../../web/public/build'));
104
+ app.use(getExpressPath('/'), express.static(path.join(__dirname, '../../web/public')));
105
+
106
+ const port = process.env.PORT || 3000;
107
+ console.log('DbGate API & web listening on port', port);
108
+ server.listen(port);
108
109
  } else {
110
+ app.get(getExpressPath('/'), (req, res) => {
111
+ res.send('DbGate API');
112
+ });
113
+
109
114
  const port = process.env.PORT || 3000;
110
115
  console.log('DbGate API listening on port', port);
111
116
  server.listen(port);
@@ -20,7 +20,7 @@ function start() {
20
20
  if (handleProcessCommunication(connection)) return;
21
21
  try {
22
22
  const driver = requireEngineDriver(connection);
23
- const conn = await connectUtility(driver, connection);
23
+ const conn = await connectUtility(driver, connection, 'app');
24
24
  const res = await driver.getVersion(conn);
25
25
  process.send({ msgtype: 'connected', ...res });
26
26
  } catch (e) {
@@ -7,6 +7,7 @@ const connectUtility = require('../utility/connectUtility');
7
7
  const { handleProcessCommunication } = require('../utility/processComm');
8
8
  const { SqlGenerator } = require('dbgate-tools');
9
9
  const generateDeploySql = require('../shell/generateDeploySql');
10
+ const { dumpSqlSelect } = require('dbgate-sqltree');
10
11
 
11
12
  let systemConnection;
12
13
  let storedConnection;
@@ -107,7 +108,7 @@ async function handleConnect({ connection, structure, globalSettings }) {
107
108
 
108
109
  if (!structure) setStatusName('pending');
109
110
  const driver = requireEngineDriver(storedConnection);
110
- systemConnection = await checkedAsyncCall(connectUtility(driver, storedConnection));
111
+ systemConnection = await checkedAsyncCall(connectUtility(driver, storedConnection, 'app'));
111
112
  await checkedAsyncCall(readVersion());
112
113
  if (structure) {
113
114
  analysedStructure = structure;
@@ -154,6 +155,7 @@ async function handleRunScript({ msgid, sql }) {
154
155
  await waitConnected();
155
156
  const driver = requireEngineDriver(storedConnection);
156
157
  try {
158
+ ensureExecuteCustomScript(driver);
157
159
  await driver.script(systemConnection, sql);
158
160
  process.send({ msgtype: 'response', msgid });
159
161
  } catch (err) {
@@ -165,6 +167,7 @@ async function handleQueryData({ msgid, sql }) {
165
167
  await waitConnected();
166
168
  const driver = requireEngineDriver(storedConnection);
167
169
  try {
170
+ ensureExecuteCustomScript(driver);
168
171
  const res = await driver.query(systemConnection, sql);
169
172
  process.send({ msgtype: 'response', msgid, ...res });
170
173
  } catch (err) {
@@ -172,21 +175,67 @@ async function handleQueryData({ msgid, sql }) {
172
175
  }
173
176
  }
174
177
 
175
- async function handleCollectionData({ msgid, options }) {
178
+ async function handleSqlSelect({ msgid, select }) {
179
+ const driver = requireEngineDriver(storedConnection);
180
+ const dmp = driver.createDumper();
181
+ dumpSqlSelect(dmp, select);
182
+ return handleQueryData({ msgid, sql: dmp.s });
183
+ }
184
+
185
+ async function handleDriverDataCore(msgid, callMethod) {
176
186
  await waitConnected();
177
187
  const driver = requireEngineDriver(storedConnection);
178
188
  try {
179
- const result = await driver.readCollection(systemConnection, options);
189
+ const result = await callMethod(driver);
180
190
  process.send({ msgtype: 'response', msgid, result });
181
191
  } catch (err) {
182
192
  process.send({ msgtype: 'response', msgid, errorMessage: err.message });
183
193
  }
184
194
  }
185
195
 
196
+ async function handleCollectionData({ msgid, options }) {
197
+ return handleDriverDataCore(msgid, driver => driver.readCollection(systemConnection, options));
198
+ }
199
+
200
+ async function handleLoadKeys({ msgid, root }) {
201
+ return handleDriverDataCore(msgid, driver => driver.loadKeys(systemConnection, root));
202
+ }
203
+
204
+ async function handleLoadKeyInfo({ msgid, key }) {
205
+ return handleDriverDataCore(msgid, driver => driver.loadKeyInfo(systemConnection, key));
206
+ }
207
+
208
+ async function handleCallMethod({ msgid, method, args }) {
209
+ return handleDriverDataCore(msgid, driver => {
210
+ ensureExecuteCustomScript(driver);
211
+ return driver.callMethod(systemConnection, method, args);
212
+ });
213
+ }
214
+
215
+ async function handleLoadKeyTableRange({ msgid, key, cursor, count }) {
216
+ return handleDriverDataCore(msgid, driver => driver.loadKeyTableRange(systemConnection, key, cursor, count));
217
+ }
218
+
219
+ async function handleLoadFieldValues({ msgid, schemaName, pureName, field, search }) {
220
+ return handleDriverDataCore(msgid, driver =>
221
+ driver.loadFieldValues(systemConnection, { schemaName, pureName }, field, search)
222
+ );
223
+ }
224
+
225
+ function ensureExecuteCustomScript(driver) {
226
+ if (driver.readOnlySessions) {
227
+ return;
228
+ }
229
+ if (storedConnection.isReadOnly) {
230
+ throw new Error('Connection is read only');
231
+ }
232
+ }
233
+
186
234
  async function handleUpdateCollection({ msgid, changeSet }) {
187
235
  await waitConnected();
188
236
  const driver = requireEngineDriver(storedConnection);
189
237
  try {
238
+ ensureExecuteCustomScript(driver);
190
239
  const result = await driver.updateCollection(systemConnection, changeSet);
191
240
  process.send({ msgtype: 'response', msgid, result });
192
241
  } catch (err) {
@@ -248,10 +297,16 @@ const messageHandlers = {
248
297
  runScript: handleRunScript,
249
298
  updateCollection: handleUpdateCollection,
250
299
  collectionData: handleCollectionData,
300
+ loadKeys: handleLoadKeys,
301
+ loadKeyInfo: handleLoadKeyInfo,
302
+ callMethod: handleCallMethod,
303
+ loadKeyTableRange: handleLoadKeyTableRange,
251
304
  sqlPreview: handleSqlPreview,
252
305
  ping: handlePing,
253
306
  syncModel: handleSyncModel,
254
307
  generateDeploySql: handleGenerateDeploySql,
308
+ loadFieldValues: handleLoadFieldValues,
309
+ sqlSelect: handleSqlSelect,
255
310
  // runCommand: handleRunCommand,
256
311
  };
257
312
 
@@ -58,11 +58,14 @@ async function handleConnect(connection) {
58
58
 
59
59
  const driver = requireEngineDriver(storedConnection);
60
60
  try {
61
- systemConnection = await connectUtility(driver, storedConnection);
61
+ systemConnection = await connectUtility(driver, storedConnection, 'app');
62
62
  readVersion();
63
63
  handleRefresh();
64
64
  if (extractBoolSettingsValue(globalSettings, 'connection.autoRefresh', false)) {
65
- setInterval(handleRefresh, extractIntSettingsValue(globalSettings, 'connection.autoRefreshInterval', 30, 5, 3600) * 1000);
65
+ setInterval(
66
+ handleRefresh,
67
+ extractIntSettingsValue(globalSettings, 'connection.autoRefreshInterval', 30, 5, 3600) * 1000
68
+ );
66
69
  }
67
70
  } catch (err) {
68
71
  setStatus({
@@ -80,7 +83,7 @@ function handlePing() {
80
83
 
81
84
  async function handleCreateDatabase({ name }) {
82
85
  const driver = requireEngineDriver(storedConnection);
83
- systemConnection = await connectUtility(driver, storedConnection);
86
+ systemConnection = await connectUtility(driver, storedConnection, 'app');
84
87
  console.log(`RUNNING SCRIPT: CREATE DATABASE ${driver.dialect.quoteIdentifier(name)}`);
85
88
  if (driver.createDatabase) {
86
89
  await driver.createDatabase(systemConnection, name);
@@ -17,11 +17,15 @@ let afterConnectCallbacks = [];
17
17
  // let currentHandlers = [];
18
18
 
19
19
  class TableWriter {
20
- constructor(structure, resultIndex) {
21
- this.jslid = uuidv1();
22
- this.currentFile = path.join(jsldir(), `${this.jslid}.jsonl`);
20
+ constructor() {
23
21
  this.currentRowCount = 0;
24
22
  this.currentChangeIndex = 1;
23
+ this.initializedFile = false;
24
+ }
25
+
26
+ initializeFromQuery(structure, resultIndex) {
27
+ this.jslid = uuidv1();
28
+ this.currentFile = path.join(jsldir(), `${this.jslid}.jsonl`);
25
29
  fs.writeFileSync(
26
30
  this.currentFile,
27
31
  JSON.stringify({
@@ -32,13 +36,21 @@ class TableWriter {
32
36
  this.currentStream = fs.createWriteStream(this.currentFile, { flags: 'a' });
33
37
  this.writeCurrentStats(false, false);
34
38
  this.resultIndex = resultIndex;
39
+ this.initializedFile = true;
35
40
  process.send({ msgtype: 'recordset', jslid: this.jslid, resultIndex });
36
41
  }
37
42
 
43
+ initializeFromReader(jslid) {
44
+ this.jslid = jslid;
45
+ this.currentFile = path.join(jsldir(), `${this.jslid}.jsonl`);
46
+ this.writeCurrentStats(false, false);
47
+ }
48
+
38
49
  row(row) {
39
50
  // console.log('ACCEPT ROW', row);
40
51
  this.currentStream.write(JSON.stringify(row) + '\n');
41
52
  this.currentRowCount += 1;
53
+
42
54
  if (!this.plannedStats) {
43
55
  this.plannedStats = true;
44
56
  process.nextTick(() => {
@@ -49,6 +61,21 @@ class TableWriter {
49
61
  }
50
62
  }
51
63
 
64
+ rowFromReader(row) {
65
+ if (!this.initializedFile) {
66
+ process.send({ msgtype: 'initializeFile', jslid: this.jslid });
67
+ this.initializedFile = true;
68
+
69
+ fs.writeFileSync(this.currentFile, JSON.stringify(row) + '\n');
70
+ this.currentStream = fs.createWriteStream(this.currentFile, { flags: 'a' });
71
+ this.writeCurrentStats(false, false);
72
+ this.initializedFile = true;
73
+ return;
74
+ }
75
+
76
+ this.row(row);
77
+ }
78
+
52
79
  writeCurrentStats(isFinished = false, emitEvent = false) {
53
80
  const stats = {
54
81
  rowCount: this.currentRowCount,
@@ -63,10 +90,11 @@ class TableWriter {
63
90
  }
64
91
  }
65
92
 
66
- close() {
93
+ close(afterClose) {
67
94
  if (this.currentStream) {
68
95
  this.currentStream.end(() => {
69
96
  this.writeCurrentStats(true, true);
97
+ if (afterClose) afterClose();
70
98
  });
71
99
  }
72
100
  }
@@ -98,7 +126,11 @@ class StreamHandler {
98
126
 
99
127
  recordset(columns) {
100
128
  this.closeCurrentWriter();
101
- this.currentWriter = new TableWriter(Array.isArray(columns) ? { columns } : columns, this.resultIndexHolder.value);
129
+ this.currentWriter = new TableWriter();
130
+ this.currentWriter.initializeFromQuery(
131
+ Array.isArray(columns) ? { columns } : columns,
132
+ this.resultIndexHolder.value
133
+ );
102
134
  this.resultIndexHolder.value += 1;
103
135
 
104
136
  // this.writeCurrentStats();
@@ -110,7 +142,6 @@ class StreamHandler {
110
142
  // }, 500);
111
143
  }
112
144
  row(row) {
113
- // console.log('ACCEPT ROW', row);
114
145
  if (this.currentWriter) this.currentWriter.row(row);
115
146
  else if (row.message) process.send({ msgtype: 'info', info: { message: row.message } });
116
147
  // this.onRow(this.jslid);
@@ -135,11 +166,22 @@ function handleStream(driver, resultIndexHolder, sql) {
135
166
  });
136
167
  }
137
168
 
169
+ function allowExecuteCustomScript(driver) {
170
+ if (driver.readOnlySessions) {
171
+ return true;
172
+ }
173
+ if (storedConnection.isReadOnly) {
174
+ return false;
175
+ // throw new Error('Connection is read only');
176
+ }
177
+ return true;
178
+ }
179
+
138
180
  async function handleConnect(connection) {
139
181
  storedConnection = connection;
140
182
 
141
183
  const driver = requireEngineDriver(storedConnection);
142
- systemConnection = await connectUtility(driver, storedConnection);
184
+ systemConnection = await connectUtility(driver, storedConnection, 'app');
143
185
  for (const [resolve] of afterConnectCallbacks) {
144
186
  resolve();
145
187
  }
@@ -163,6 +205,19 @@ async function handleExecuteQuery({ sql }) {
163
205
  await waitConnected();
164
206
  const driver = requireEngineDriver(storedConnection);
165
207
 
208
+ if (!allowExecuteCustomScript(driver)) {
209
+ process.send({
210
+ msgtype: 'info',
211
+ info: {
212
+ message: 'Connection without read-only sessions is read only',
213
+ severity: 'error',
214
+ },
215
+ });
216
+ process.send({ msgtype: 'done', skipFinishedMessage: true });
217
+ return;
218
+ //process.send({ msgtype: 'error', error: e.message });
219
+ }
220
+
166
221
  const resultIndexHolder = {
167
222
  value: 0,
168
223
  };
@@ -176,9 +231,39 @@ async function handleExecuteQuery({ sql }) {
176
231
  process.send({ msgtype: 'done' });
177
232
  }
178
233
 
234
+ async function handleExecuteReader({ jslid, sql, fileName }) {
235
+ await waitConnected();
236
+
237
+ const driver = requireEngineDriver(storedConnection);
238
+
239
+ if (fileName) {
240
+ sql = fs.readFileSync(fileName, 'utf-8');
241
+ } else {
242
+ if (!allowExecuteCustomScript(driver)) {
243
+ process.send({ msgtype: 'done' });
244
+ return;
245
+ }
246
+ }
247
+
248
+ const writer = new TableWriter();
249
+ writer.initializeFromReader(jslid);
250
+
251
+ const reader = await driver.readQuery(systemConnection, sql);
252
+
253
+ reader.on('data', data => {
254
+ writer.rowFromReader(data);
255
+ });
256
+ reader.on('end', () => {
257
+ writer.close(() => {
258
+ process.send({ msgtype: 'done' });
259
+ });
260
+ });
261
+ }
262
+
179
263
  const messageHandlers = {
180
264
  connect: handleConnect,
181
265
  executeQuery: handleExecuteQuery,
266
+ executeReader: handleExecuteReader,
182
267
  // cancel: handleCancel,
183
268
  };
184
269
 
@@ -5,7 +5,7 @@ async function executeQuery({ connection = undefined, systemConnection = undefin
5
5
  console.log(`Execute query ${sql}`);
6
6
 
7
7
  if (!driver) driver = requireEngineDriver(connection);
8
- const pool = systemConnection || (await connectUtility(driver, connection));
8
+ const pool = systemConnection || (await connectUtility(driver, connection, 'script'));
9
9
  console.log(`Connected.`);
10
10
 
11
11
  await driver.script(pool, sql);
@@ -21,7 +21,7 @@ async function generateDeploySql({
21
21
  }) {
22
22
  if (!driver) driver = requireEngineDriver(connection);
23
23
 
24
- const pool = systemConnection || (await connectUtility(driver, connection));
24
+ const pool = systemConnection || (await connectUtility(driver, connection, 'read'));
25
25
  if (!analysedStructure) {
26
26
  analysedStructure = await driver.analyseFull(pool);
27
27
  }
@@ -1,14 +1,26 @@
1
1
  const requireEngineDriver = require('../utility/requireEngineDriver');
2
- const { decryptConnection } = require('../utility/crypting');
3
2
  const connectUtility = require('../utility/connectUtility');
4
3
 
5
- async function queryReader({ connection, sql }) {
6
- console.log(`Reading query ${sql}`);
4
+ async function queryReader({
5
+ connection,
6
+ query,
7
+ queryType,
8
+ // obsolete; use query instead
9
+ sql,
10
+ }) {
11
+ // if (sql && json) {
12
+ // throw new Error('Only one of sql or json could be set');
13
+ // }
14
+ // if (!sql && !json) {
15
+ // throw new Error('One of sql or json must be set');
16
+ // }
17
+ console.log(`Reading query ${query || sql}`);
18
+ // else console.log(`Reading query ${JSON.stringify(json)}`);
7
19
 
8
20
  const driver = requireEngineDriver(connection);
9
- const pool = await connectUtility(driver, connection);
21
+ const pool = await connectUtility(driver, connection, queryType == 'json' ? 'read' : 'script');
10
22
  console.log(`Connected.`);
11
- return await driver.readQuery(pool, sql);
23
+ return queryType == 'json' ? await driver.readJsonQuery(pool, query) : await driver.readQuery(pool, query || sql);
12
24
  }
13
25
 
14
26
  module.exports = queryReader;
@@ -4,12 +4,12 @@ const connectUtility = require('../utility/connectUtility');
4
4
 
5
5
  async function tableReader({ connection, pureName, schemaName }) {
6
6
  const driver = requireEngineDriver(connection);
7
- const pool = await connectUtility(driver, connection);
7
+ const pool = await connectUtility(driver, connection, 'read');
8
8
  console.log(`Connected.`);
9
9
 
10
10
  const fullName = { pureName, schemaName };
11
11
 
12
- if (driver.dialect.nosql) {
12
+ if (driver.databaseEngineTypes.includes('document')) {
13
13
  // @ts-ignore
14
14
  console.log(`Reading collection ${fullNameToString(fullName)}`);
15
15
  // @ts-ignore
@@ -8,7 +8,7 @@ async function tableWriter({ connection, schemaName, pureName, driver, systemCon
8
8
  if (!driver) {
9
9
  driver = requireEngineDriver(connection);
10
10
  }
11
- const pool = systemConnection || (await connectUtility(driver, connection));
11
+ const pool = systemConnection || (await connectUtility(driver, connection, 'write'));
12
12
 
13
13
  console.log(`Connected.`);
14
14
  return await driver.writeTable(pool, { schemaName, pureName }, options);
@@ -4,11 +4,47 @@ const fs = require('fs-extra');
4
4
  const { decryptConnection } = require('./crypting');
5
5
  const { getSshTunnel } = require('./sshTunnel');
6
6
  const { getSshTunnelProxy } = require('./sshTunnelProxy');
7
+ const platformInfo = require('../utility/platformInfo');
8
+ const connections = require('../controllers/connections');
9
+
10
+ async function loadConnection(driver, storedConnection, connectionMode) {
11
+ const { allowShellConnection } = platformInfo;
12
+
13
+ if (connectionMode == 'app') {
14
+ return storedConnection;
15
+ }
16
+
17
+ if (storedConnection._id || !allowShellConnection) {
18
+ if (!storedConnection._id) {
19
+ throw new Error('Missing connection _id');
20
+ }
21
+
22
+ await connections._init();
23
+ const loaded = await connections.getCore({ conid: storedConnection._id });
24
+ const loadedWithDb = {
25
+ ...loaded,
26
+ database: storedConnection.database,
27
+ };
28
+
29
+ if (loaded.isReadOnly) {
30
+ if (connectionMode == 'read') return loadedWithDb;
31
+ if (connectionMode == 'write') throw new Error('Cannot write readonly connection');
32
+ if (connectionMode == 'script') {
33
+ if (driver.readOnlySessions) return loadedWithDb;
34
+ throw new Error('Cannot write readonly connection');
35
+ }
36
+ }
37
+ return loadedWithDb;
38
+ }
39
+ return storedConnection;
40
+ }
41
+
42
+ async function connectUtility(driver, storedConnection, connectionMode) {
43
+ const connectionLoaded = await loadConnection(driver, storedConnection, connectionMode);
7
44
 
8
- async function connectUtility(driver, storedConnection) {
9
45
  const connection = {
10
- database: storedConnection.defaultDatabase,
11
- ...decryptConnection(storedConnection),
46
+ database: connectionLoaded.defaultDatabase,
47
+ ...decryptConnection(connectionLoaded),
12
48
  };
13
49
 
14
50
  if (!connection.port && driver.defaultPort) connection.port = driver.defaultPort.toString();
@@ -55,7 +55,7 @@ function encryptPasswordField(connection, field) {
55
55
  [field]: 'crypt:' + getEncryptor().encrypt(connection[field]),
56
56
  };
57
57
  }
58
- return connection;
58
+ return connection;
59
59
  }
60
60
 
61
61
  function decryptPasswordField(connection, field) {
@@ -75,6 +75,11 @@ function encryptConnection(connection) {
75
75
  return connection;
76
76
  }
77
77
 
78
+ function maskConnection(connection) {
79
+ if (!connection) return connection;
80
+ return _.omit(connection, ['password', 'sshPassword', 'sshKeyfilePassword']);
81
+ }
82
+
78
83
  function decryptConnection(connection) {
79
84
  connection = decryptPasswordField(connection, 'password');
80
85
  connection = decryptPasswordField(connection, 'sshPassword');
@@ -95,5 +100,6 @@ module.exports = {
95
100
  loadEncryptionKey,
96
101
  encryptConnection,
97
102
  decryptConnection,
103
+ maskConnection,
98
104
  pickSafeConnectionInfo,
99
105
  };
@@ -1,12 +1,56 @@
1
1
  const { compilePermissions, testPermission } = require('dbgate-tools');
2
+ const _ = require('lodash');
2
3
 
3
- let compiled = undefined;
4
+ const userPermissions = {};
4
5
 
5
- function hasPermission(tested) {
6
- if (compiled === undefined) {
7
- compiled = compilePermissions(process.env.PERMISSIONS);
6
+ function hasPermission(tested, req) {
7
+ const { user } = (req && req.auth) || {};
8
+ const key = user || '';
9
+ const logins = getLogins();
10
+ if (!userPermissions[key] && logins) {
11
+ const login = logins.find(x => x.login == user);
12
+ userPermissions[key] = compilePermissions(login ? login.permissions : null);
8
13
  }
9
- return testPermission(tested, compiled);
14
+ return testPermission(tested, userPermissions[key]);
10
15
  }
11
16
 
12
- module.exports = hasPermission;
17
+ let loginsCache = null;
18
+ let loginsLoaded = false;
19
+
20
+ function getLogins() {
21
+ if (loginsLoaded) {
22
+ return loginsCache;
23
+ }
24
+
25
+ const res = [];
26
+ if (process.env.LOGIN && process.env.PASSWORD) {
27
+ res.push({
28
+ login: process.env.LOGIN,
29
+ password: process.env.PASSWORD,
30
+ permissions: process.env.PERMISSIONS,
31
+ });
32
+ }
33
+ if (process.env.LOGINS) {
34
+ const logins = _.compact(process.env.LOGINS.split(',').map(x => x.trim()));
35
+ for (const login of logins) {
36
+ const password = process.env[`LOGIN_PASSWORD_${login}`];
37
+ const permissions = process.env[`LOGIN_PERMISSIONS_${login}`];
38
+ if (password) {
39
+ res.push({
40
+ login,
41
+ password,
42
+ permissions,
43
+ });
44
+ }
45
+ }
46
+ }
47
+
48
+ loginsCache = res.length > 0 ? res : null;
49
+ loginsLoaded = true;
50
+ return loginsCache;
51
+ }
52
+
53
+ module.exports = {
54
+ hasPermission,
55
+ getLogins,
56
+ };
@@ -39,6 +39,8 @@ const platformInfo = {
39
39
  environment: process.env.NODE_ENV,
40
40
  platform,
41
41
  runningInWebpack: !!process.env.WEBPACK_DEV_SERVER_URL,
42
+ allowShellConnection: !!process.env.SHELL_CONNECTION || !!isElectron(),
43
+ allowShellScripting: !!process.env.SHELL_SCRIPTING || !!isElectron(),
42
44
  defaultKeyfile: path.join(os.homedir(), '.ssh/id_rsa'),
43
45
  };
44
46
 
@@ -62,7 +62,7 @@ module.exports = function useController(app, electron, route, controller) {
62
62
  // controller._init_called = true;
63
63
  // }
64
64
  try {
65
- let params = [{ ...req.body, ...req.query }];
65
+ let params = [{ ...req.body, ...req.query }, req];
66
66
  if (rawParams) params = [req, res];
67
67
  const data = await controller[key](...params);
68
68
  res.json(data);