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 +13 -0
- package/env/singledb/.env +2 -0
- package/package.json +5 -5
- package/src/controllers/config.js +19 -5
- package/src/controllers/connections.js +22 -5
- package/src/controllers/databaseConnections.js +72 -5
- package/src/controllers/files.js +22 -17
- package/src/controllers/jsldata.js +14 -10
- package/src/controllers/plugins.js +8 -8
- package/src/controllers/runners.js +12 -1
- package/src/controllers/scheduler.js +3 -3
- package/src/controllers/serverConnections.js +1 -1
- package/src/controllers/sessions.js +42 -3
- package/src/currentVersion.js +2 -2
- package/src/main.js +18 -13
- package/src/proc/connectProcess.js +1 -1
- package/src/proc/databaseConnectionProcess.js +58 -3
- package/src/proc/serverConnectionProcess.js +6 -3
- package/src/proc/sessionProcess.js +92 -7
- package/src/shell/executeQuery.js +1 -1
- package/src/shell/generateDeploySql.js +1 -1
- package/src/shell/queryReader.js +17 -5
- package/src/shell/tableReader.js +2 -2
- package/src/shell/tableWriter.js +1 -1
- package/src/utility/connectUtility.js +39 -3
- package/src/utility/crypting.js +7 -1
- package/src/utility/hasPermission.js +50 -6
- package/src/utility/platformInfo.js +2 -0
- package/src/utility/useController.js +1 -1
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
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
|
+
"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.
|
|
29
|
-
"dbgate-sqltree": "^4.7.
|
|
30
|
-
"dbgate-tools": "^4.7.
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
242
|
-
|
|
243
|
-
if (portalConnections)
|
|
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.
|
|
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
|
-
|
|
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.
|
|
329
|
-
const connection = await connections.
|
|
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);
|
package/src/controllers/files.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
122
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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);
|
package/src/currentVersion.js
CHANGED
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
|
-
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
}
|
package/src/shell/queryReader.js
CHANGED
|
@@ -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({
|
|
6
|
-
|
|
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;
|
package/src/shell/tableReader.js
CHANGED
|
@@ -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.
|
|
12
|
+
if (driver.databaseEngineTypes.includes('document')) {
|
|
13
13
|
// @ts-ignore
|
|
14
14
|
console.log(`Reading collection ${fullNameToString(fullName)}`);
|
|
15
15
|
// @ts-ignore
|
package/src/shell/tableWriter.js
CHANGED
|
@@ -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:
|
|
11
|
-
...decryptConnection(
|
|
46
|
+
database: connectionLoaded.defaultDatabase,
|
|
47
|
+
...decryptConnection(connectionLoaded),
|
|
12
48
|
};
|
|
13
49
|
|
|
14
50
|
if (!connection.port && driver.defaultPort) connection.port = driver.defaultPort.toString();
|
package/src/utility/crypting.js
CHANGED
|
@@ -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
|
-
|
|
4
|
+
const userPermissions = {};
|
|
4
5
|
|
|
5
|
-
function hasPermission(tested) {
|
|
6
|
-
|
|
7
|
-
|
|
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,
|
|
14
|
+
return testPermission(tested, userPermissions[key]);
|
|
10
15
|
}
|
|
11
16
|
|
|
12
|
-
|
|
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);
|