dbgate-api 4.7.4-alpha.1 → 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,3 +1,14 @@
1
1
  DEVMODE=1
2
+ # PERMISSIONS=~widgets/app,~widgets/plugins
2
3
  # DISABLE_SHELL=1
3
- # HIDE_APP_EDITOR=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.4-alpha.1",
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.4-alpha.1",
29
- "dbgate-sqltree": "^4.7.4-alpha.1",
30
- "dbgate-tools": "^4.7.4-alpha.1",
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.4-alpha.1",
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,18 +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,
35
- hideAppEditor: !!process.env.HIDE_APP_EDITOR,
37
+ // hideAppEditor: !!process.env.HIDE_APP_EDITOR,
38
+ allowShellConnection: platformInfo.allowShellConnection,
39
+ allowShellScripting: platformInfo.allowShellConnection,
36
40
  permissions,
41
+ login,
37
42
  ...currentVersion,
38
43
  };
39
44
  },
40
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
+
41
54
  platformInfo_meta: true,
42
55
  async platformInfo() {
43
56
  return platformInfo;
@@ -65,8 +78,8 @@ module.exports = {
65
78
  },
66
79
 
67
80
  updateSettings_meta: true,
68
- async updateSettings(values) {
69
- if (!hasPermission(`settings/change`)) return false;
81
+ async updateSettings(values, req) {
82
+ if (!hasPermission(`settings/change`, req)) return false;
70
83
 
71
84
  const res = await lock.acquire('update', async () => {
72
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 = {};
@@ -56,6 +58,7 @@ function getPortalCollections() {
56
58
  singleDatabase: !!process.env[`DATABASE_${id}`] || !!process.env[`FILE_${id}`],
57
59
  displayName: process.env[`LABEL_${id}`],
58
60
  isReadOnly: process.env[`READONLY_${id}`],
61
+ databases: process.env[`DBCONFIG_${id}`] ? safeJsonParse(process.env[`DBCONFIG_${id}`]) : null,
59
62
 
60
63
  // SSH tunnel
61
64
  useSshTunnel: process.env[`USE_SSH_${id}`],
@@ -163,7 +166,9 @@ module.exports = {
163
166
 
164
167
  list_meta: true,
165
168
  async list() {
166
- return portalConnections || this.datastore.find();
169
+ return portalConnections && !platformInfo.allowShellConnection
170
+ ? portalConnections.map(maskConnection)
171
+ : this.datastore.find();
167
172
  },
168
173
 
169
174
  test_meta: true,
@@ -242,14 +247,21 @@ module.exports = {
242
247
  return res;
243
248
  },
244
249
 
245
- get_meta: true,
246
- async get({ conid }) {
250
+ async getCore({ conid, mask = false }) {
247
251
  if (!conid) return null;
248
- if (portalConnections) return portalConnections.find(x => x._id == conid) || null;
252
+ if (portalConnections) {
253
+ const res = portalConnections.find(x => x._id == conid) || null;
254
+ return mask && !platformInfo.allowShellConnection ? maskConnection(res) : res;
255
+ }
249
256
  const res = await this.datastore.get(conid);
250
257
  return res || null;
251
258
  },
252
259
 
260
+ get_meta: true,
261
+ async get({ conid }) {
262
+ return this.getCore({ conid, mask: true });
263
+ },
264
+
253
265
  newSqliteDatabase_meta: true,
254
266
  async newSqliteDatabase({ file }) {
255
267
  const sqliteDir = path.join(filesdir(), 'sqlite');
@@ -79,7 +79,7 @@ module.exports = {
79
79
  async ensureOpened(conid, database) {
80
80
  const existing = this.opened.find(x => x.conid == conid && x.database == database);
81
81
  if (existing) return existing;
82
- const connection = await connections.get({ conid });
82
+ const connection = await connections.getCore({ conid });
83
83
  const subprocess = fork(global['API_PACKAGE'] || process.argv[1], [
84
84
  '--is-forked-api',
85
85
  '--start-process',
@@ -392,8 +392,8 @@ module.exports = {
392
392
  const targetDb = generateDbPairingId(
393
393
  extendDatabaseInfo(await this.structure({ conid: targetConid, database: targetDatabase }))
394
394
  );
395
- // const sourceConnection = await connections.get({conid:sourceConid})
396
- const connection = await connections.get({ conid: targetConid });
395
+ // const sourceConnection = await connections.getCore({conid:sourceConid})
396
+ const connection = await connections.getCore({ conid: targetConid });
397
397
  const driver = requireEngineDriver(connection);
398
398
  const targetDbPaired = matchPairedObjects(sourceDb, targetDb, dbDiffOptions);
399
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);
@@ -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;
@@ -149,11 +150,17 @@ module.exports = {
149
150
 
150
151
  start_meta: true,
151
152
  async start({ script }) {
152
- if (process.env.DISABLE_SHELL) {
153
- return { errorMessage: 'Shell is disabled' };
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' };
154
162
  }
155
163
 
156
- const runid = uuidv1();
157
164
  return this.startCore(runid, scriptTemplate(script, false));
158
165
  },
159
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',
@@ -54,6 +54,9 @@ module.exports = {
54
54
  this.dispatchMessage(sesid, 'Query execution finished');
55
55
  }
56
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
+ }
57
60
  if (session.killOnDone) {
58
61
  this.kill({ sesid });
59
62
  }
@@ -78,7 +81,7 @@ module.exports = {
78
81
  create_meta: true,
79
82
  async create({ conid, database }) {
80
83
  const sesid = uuidv1();
81
- const connection = await connections.get({ conid });
84
+ const connection = await connections.getCore({ conid });
82
85
  const subprocess = fork(global['API_PACKAGE'] || process.argv[1], [
83
86
  '--is-forked-api',
84
87
  '--start-process',
@@ -124,6 +127,7 @@ module.exports = {
124
127
  const session = this.opened.find(x => x.sesid == sesid);
125
128
  session.killOnDone = true;
126
129
  const jslid = uuidv1();
130
+ session.loadingReader_jslid = jslid;
127
131
  const fileName = queryName && appFolder ? path.join(appdir(), appFolder, `${queryName}.query.sql`) : null;
128
132
 
129
133
  session.subprocess.send({ msgtype: 'executeReader', sql, fileName, jslid });
@@ -131,6 +135,15 @@ module.exports = {
131
135
  return { jslid };
132
136
  },
133
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
+
134
147
  // cancel_meta: true,
135
148
  // async cancel({ sesid }) {
136
149
  // const session = this.opened.find((x) => x.sesid == sesid);
@@ -1,5 +1,5 @@
1
1
 
2
2
  module.exports = {
3
- version: '4.7.4-alpha.1',
4
- buildTime: '2022-03-17T18:49:14.355Z'
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) {
@@ -108,7 +108,7 @@ async function handleConnect({ connection, structure, globalSettings }) {
108
108
 
109
109
  if (!structure) setStatusName('pending');
110
110
  const driver = requireEngineDriver(storedConnection);
111
- systemConnection = await checkedAsyncCall(connectUtility(driver, storedConnection));
111
+ systemConnection = await checkedAsyncCall(connectUtility(driver, storedConnection, 'app'));
112
112
  await checkedAsyncCall(readVersion());
113
113
  if (structure) {
114
114
  analysedStructure = structure;
@@ -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);
@@ -181,7 +181,7 @@ async function handleConnect(connection) {
181
181
  storedConnection = connection;
182
182
 
183
183
  const driver = requireEngineDriver(storedConnection);
184
- systemConnection = await connectUtility(driver, storedConnection);
184
+ systemConnection = await connectUtility(driver, storedConnection, 'app');
185
185
  for (const [resolve] of afterConnectCallbacks) {
186
186
  resolve();
187
187
  }
@@ -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,7 +4,7 @@ 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 };
@@ -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);