dbgate-api-premium 6.4.3-alpha.1 → 6.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -5
- package/src/controllers/auth.js +11 -0
- package/src/controllers/cloud.js +261 -0
- package/src/controllers/config.js +2 -1
- package/src/controllers/connections.js +20 -0
- package/src/controllers/databaseConnections.js +3 -0
- package/src/controllers/files.js +33 -0
- package/src/controllers/jsldata.js +26 -0
- package/src/controllers/runners.js +12 -0
- package/src/controllers/serverConnections.js +1 -1
- package/src/controllers/sessions.js +7 -2
- package/src/controllers/storage.js +9 -0
- package/src/controllers/uploads.js +4 -0
- package/src/currentVersion.js +2 -2
- package/src/main.js +5 -0
- package/src/proc/connectProcess.js +1 -8
- package/src/proc/sessionProcess.js +2 -2
- package/src/shell/deployDb.js +10 -1
- package/src/shell/executeQuery.js +3 -1
- package/src/utility/authProxy.js +12 -8
- package/src/utility/checkLicense.js +11 -13
- package/src/utility/cloudIntf.js +399 -0
- package/src/utility/crypting.js +6 -6
- package/src/utility/handleQueryStream.js +64 -5
- package/src/utility/hardwareFingerprint.js +1 -0
- package/src/utility/security.js +52 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dbgate-api-premium",
|
|
3
3
|
"main": "src/index.js",
|
|
4
|
-
"version": "6.
|
|
4
|
+
"version": "6.5.1",
|
|
5
5
|
"homepage": "https://dbgate.org/",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -30,10 +30,10 @@
|
|
|
30
30
|
"compare-versions": "^3.6.0",
|
|
31
31
|
"cors": "^2.8.5",
|
|
32
32
|
"cross-env": "^6.0.3",
|
|
33
|
-
"dbgate-datalib": "^6.
|
|
33
|
+
"dbgate-datalib": "^6.5.1",
|
|
34
34
|
"dbgate-query-splitter": "^4.11.5",
|
|
35
|
-
"dbgate-sqltree": "^6.
|
|
36
|
-
"dbgate-tools": "^6.
|
|
35
|
+
"dbgate-sqltree": "^6.5.1",
|
|
36
|
+
"dbgate-tools": "^6.5.1",
|
|
37
37
|
"debug": "^4.3.4",
|
|
38
38
|
"diff": "^5.0.0",
|
|
39
39
|
"diff2html": "^3.4.13",
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
"devDependencies": {
|
|
86
86
|
"@types/fs-extra": "^9.0.11",
|
|
87
87
|
"@types/lodash": "^4.14.149",
|
|
88
|
-
"dbgate-types": "^6.
|
|
88
|
+
"dbgate-types": "^6.5.1",
|
|
89
89
|
"env-cmd": "^10.1.0",
|
|
90
90
|
"jsdoc-to-markdown": "^9.0.5",
|
|
91
91
|
"node-loader": "^1.0.2",
|
package/src/controllers/auth.js
CHANGED
|
@@ -13,6 +13,8 @@ const {
|
|
|
13
13
|
} = require('../auth/authProvider');
|
|
14
14
|
const storage = require('./storage');
|
|
15
15
|
const { decryptPasswordString } = require('../utility/crypting');
|
|
16
|
+
const { createDbGateIdentitySession, startCloudTokenChecking } = require('../utility/cloudIntf');
|
|
17
|
+
const socket = require('../utility/socket');
|
|
16
18
|
|
|
17
19
|
const logger = getLogger('auth');
|
|
18
20
|
|
|
@@ -135,5 +137,14 @@ module.exports = {
|
|
|
135
137
|
return getAuthProviderById(amoid).redirect(params);
|
|
136
138
|
},
|
|
137
139
|
|
|
140
|
+
createCloudLoginSession_meta: true,
|
|
141
|
+
async createCloudLoginSession({ client }) {
|
|
142
|
+
const res = await createDbGateIdentitySession(client);
|
|
143
|
+
startCloudTokenChecking(res.sid, tokenHolder => {
|
|
144
|
+
socket.emit('got-cloud-token', tokenHolder);
|
|
145
|
+
});
|
|
146
|
+
return res;
|
|
147
|
+
},
|
|
148
|
+
|
|
138
149
|
authMiddleware,
|
|
139
150
|
};
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
const {
|
|
2
|
+
getPublicCloudFiles,
|
|
3
|
+
getPublicFileData,
|
|
4
|
+
refreshPublicFiles,
|
|
5
|
+
callCloudApiGet,
|
|
6
|
+
callCloudApiPost,
|
|
7
|
+
getCloudFolderEncryptor,
|
|
8
|
+
getCloudContent,
|
|
9
|
+
putCloudContent,
|
|
10
|
+
removeCloudCachedConnection,
|
|
11
|
+
} = require('../utility/cloudIntf');
|
|
12
|
+
const connections = require('./connections');
|
|
13
|
+
const socket = require('../utility/socket');
|
|
14
|
+
const { recryptConnection, getInternalEncryptor, encryptConnection } = require('../utility/crypting');
|
|
15
|
+
const { getConnectionLabel, getLogger, extractErrorLogData } = require('dbgate-tools');
|
|
16
|
+
const logger = getLogger('cloud');
|
|
17
|
+
const _ = require('lodash');
|
|
18
|
+
const fs = require('fs-extra');
|
|
19
|
+
|
|
20
|
+
module.exports = {
|
|
21
|
+
publicFiles_meta: true,
|
|
22
|
+
async publicFiles() {
|
|
23
|
+
const res = await getPublicCloudFiles();
|
|
24
|
+
return res;
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
publicFileData_meta: true,
|
|
28
|
+
async publicFileData({ path }) {
|
|
29
|
+
const res = getPublicFileData(path);
|
|
30
|
+
return res;
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
refreshPublicFiles_meta: true,
|
|
34
|
+
async refreshPublicFiles({ isRefresh }) {
|
|
35
|
+
await refreshPublicFiles(isRefresh);
|
|
36
|
+
return {
|
|
37
|
+
status: 'ok',
|
|
38
|
+
};
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
contentList_meta: true,
|
|
42
|
+
async contentList() {
|
|
43
|
+
try {
|
|
44
|
+
const resp = await callCloudApiGet('content-list');
|
|
45
|
+
return resp;
|
|
46
|
+
} catch (err) {
|
|
47
|
+
logger.error(extractErrorLogData(err), 'Error getting cloud content list');
|
|
48
|
+
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
getContent_meta: true,
|
|
54
|
+
async getContent({ folid, cntid }) {
|
|
55
|
+
const resp = await getCloudContent(folid, cntid);
|
|
56
|
+
return resp;
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
putContent_meta: true,
|
|
60
|
+
async putContent({ folid, cntid, content, name, type }) {
|
|
61
|
+
const resp = await putCloudContent(folid, cntid, content, name, type, {});
|
|
62
|
+
socket.emitChanged('cloud-content-changed');
|
|
63
|
+
socket.emit('cloud-content-updated');
|
|
64
|
+
return resp;
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
createFolder_meta: true,
|
|
68
|
+
async createFolder({ name }) {
|
|
69
|
+
const resp = await callCloudApiPost(`folders/create`, { name });
|
|
70
|
+
socket.emitChanged('cloud-content-changed');
|
|
71
|
+
socket.emit('cloud-content-updated');
|
|
72
|
+
return resp;
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
grantFolder_meta: true,
|
|
76
|
+
async grantFolder({ inviteLink }) {
|
|
77
|
+
const m = inviteLink.match(/^dbgate\:\/\/folder\/v1\/([a-zA-Z0-9]+)\?mode=(read|write|admin)$/);
|
|
78
|
+
if (!m) {
|
|
79
|
+
throw new Error('Invalid invite link format');
|
|
80
|
+
}
|
|
81
|
+
const invite = m[1];
|
|
82
|
+
const mode = m[2];
|
|
83
|
+
|
|
84
|
+
const resp = await callCloudApiPost(`folders/grant/${mode}`, { invite });
|
|
85
|
+
socket.emitChanged('cloud-content-changed');
|
|
86
|
+
socket.emit('cloud-content-updated');
|
|
87
|
+
return resp;
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
renameFolder_meta: true,
|
|
91
|
+
async renameFolder({ folid, name }) {
|
|
92
|
+
const resp = await callCloudApiPost(`folders/rename`, { folid, name });
|
|
93
|
+
socket.emitChanged('cloud-content-changed');
|
|
94
|
+
socket.emit('cloud-content-updated');
|
|
95
|
+
return resp;
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
deleteFolder_meta: true,
|
|
99
|
+
async deleteFolder({ folid }) {
|
|
100
|
+
const resp = await callCloudApiPost(`folders/delete`, { folid });
|
|
101
|
+
socket.emitChanged('cloud-content-changed');
|
|
102
|
+
socket.emit('cloud-content-updated');
|
|
103
|
+
return resp;
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
getInviteToken_meta: true,
|
|
107
|
+
async getInviteToken({ folid, role }) {
|
|
108
|
+
const resp = await callCloudApiGet(`invite-token/${folid}/${role}`);
|
|
109
|
+
return resp;
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
refreshContent_meta: true,
|
|
113
|
+
async refreshContent() {
|
|
114
|
+
socket.emitChanged('cloud-content-changed');
|
|
115
|
+
socket.emit('cloud-content-updated');
|
|
116
|
+
return {
|
|
117
|
+
status: 'ok',
|
|
118
|
+
};
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
copyConnectionCloud_meta: true,
|
|
122
|
+
async copyConnectionCloud({ conid, folid }) {
|
|
123
|
+
const conn = await connections.getCore({ conid });
|
|
124
|
+
const folderEncryptor = await getCloudFolderEncryptor(folid);
|
|
125
|
+
const recryptedConn = recryptConnection(conn, getInternalEncryptor(), folderEncryptor);
|
|
126
|
+
const connToSend = _.omit(recryptedConn, ['_id']);
|
|
127
|
+
const resp = await putCloudContent(
|
|
128
|
+
folid,
|
|
129
|
+
undefined,
|
|
130
|
+
JSON.stringify(connToSend),
|
|
131
|
+
getConnectionLabel(conn),
|
|
132
|
+
'connection',
|
|
133
|
+
{
|
|
134
|
+
connectionColor: conn.connectionColor,
|
|
135
|
+
connectionEngine: conn.engine,
|
|
136
|
+
}
|
|
137
|
+
);
|
|
138
|
+
return resp;
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
saveConnection_meta: true,
|
|
142
|
+
async saveConnection({ folid, connection }) {
|
|
143
|
+
let cntid = undefined;
|
|
144
|
+
if (connection._id) {
|
|
145
|
+
const m = connection._id.match(/^cloud\:\/\/(.+)\/(.+)$/);
|
|
146
|
+
if (!m) {
|
|
147
|
+
throw new Error('Invalid cloud connection ID format');
|
|
148
|
+
}
|
|
149
|
+
folid = m[1];
|
|
150
|
+
cntid = m[2];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (!folid) {
|
|
154
|
+
throw new Error('Missing cloud folder ID');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const folderEncryptor = await getCloudFolderEncryptor(folid);
|
|
158
|
+
const recryptedConn = encryptConnection(connection, folderEncryptor);
|
|
159
|
+
const resp = await putCloudContent(
|
|
160
|
+
folid,
|
|
161
|
+
cntid,
|
|
162
|
+
JSON.stringify(recryptedConn),
|
|
163
|
+
getConnectionLabel(recryptedConn),
|
|
164
|
+
'connection',
|
|
165
|
+
{
|
|
166
|
+
connectionColor: connection.connectionColor,
|
|
167
|
+
connectionEngine: connection.engine,
|
|
168
|
+
}
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
if (resp.apiErrorMessage) {
|
|
172
|
+
return resp;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
removeCloudCachedConnection(folid, resp.cntid);
|
|
176
|
+
cntid = resp.cntid;
|
|
177
|
+
socket.emitChanged('cloud-content-changed');
|
|
178
|
+
socket.emit('cloud-content-updated');
|
|
179
|
+
return {
|
|
180
|
+
...recryptedConn,
|
|
181
|
+
_id: `cloud://${folid}/${cntid}`,
|
|
182
|
+
};
|
|
183
|
+
},
|
|
184
|
+
|
|
185
|
+
duplicateConnection_meta: true,
|
|
186
|
+
async duplicateConnection({ conid }) {
|
|
187
|
+
const m = conid.match(/^cloud\:\/\/(.+)\/(.+)$/);
|
|
188
|
+
if (!m) {
|
|
189
|
+
throw new Error('Invalid cloud connection ID format');
|
|
190
|
+
}
|
|
191
|
+
const folid = m[1];
|
|
192
|
+
const cntid = m[2];
|
|
193
|
+
const respGet = await getCloudContent(folid, cntid);
|
|
194
|
+
const conn = JSON.parse(respGet.content);
|
|
195
|
+
const conn2 = {
|
|
196
|
+
...conn,
|
|
197
|
+
displayName: getConnectionLabel(conn) + ' - copy',
|
|
198
|
+
};
|
|
199
|
+
const respPut = await putCloudContent(folid, undefined, JSON.stringify(conn2), conn2.displayName, 'connection', {
|
|
200
|
+
connectionColor: conn.connectionColor,
|
|
201
|
+
connectionEngine: conn.engine,
|
|
202
|
+
});
|
|
203
|
+
return respPut;
|
|
204
|
+
},
|
|
205
|
+
|
|
206
|
+
deleteConnection_meta: true,
|
|
207
|
+
async deleteConnection({ conid }) {
|
|
208
|
+
const m = conid.match(/^cloud\:\/\/(.+)\/(.+)$/);
|
|
209
|
+
if (!m) {
|
|
210
|
+
throw new Error('Invalid cloud connection ID format');
|
|
211
|
+
}
|
|
212
|
+
const folid = m[1];
|
|
213
|
+
const cntid = m[2];
|
|
214
|
+
const resp = await callCloudApiPost(`content/delete/${folid}/${cntid}`);
|
|
215
|
+
socket.emitChanged('cloud-content-changed');
|
|
216
|
+
socket.emit('cloud-content-updated');
|
|
217
|
+
return resp;
|
|
218
|
+
},
|
|
219
|
+
|
|
220
|
+
deleteContent_meta: true,
|
|
221
|
+
async deleteContent({ folid, cntid }) {
|
|
222
|
+
const resp = await callCloudApiPost(`content/delete/${folid}/${cntid}`);
|
|
223
|
+
socket.emitChanged('cloud-content-changed');
|
|
224
|
+
socket.emit('cloud-content-updated');
|
|
225
|
+
return resp;
|
|
226
|
+
},
|
|
227
|
+
|
|
228
|
+
renameContent_meta: true,
|
|
229
|
+
async renameContent({ folid, cntid, name }) {
|
|
230
|
+
const resp = await callCloudApiPost(`content/rename/${folid}/${cntid}`, { name });
|
|
231
|
+
socket.emitChanged('cloud-content-changed');
|
|
232
|
+
socket.emit('cloud-content-updated');
|
|
233
|
+
return resp;
|
|
234
|
+
},
|
|
235
|
+
|
|
236
|
+
saveFile_meta: true,
|
|
237
|
+
async saveFile({ folid, cntid, fileName, data, contentFolder, format }) {
|
|
238
|
+
const resp = await putCloudContent(folid, cntid, data, fileName, 'file', { contentFolder, contentType: format });
|
|
239
|
+
socket.emitChanged('cloud-content-changed');
|
|
240
|
+
socket.emit('cloud-content-updated');
|
|
241
|
+
return resp;
|
|
242
|
+
},
|
|
243
|
+
|
|
244
|
+
copyFile_meta: true,
|
|
245
|
+
async copyFile({ folid, cntid, name }) {
|
|
246
|
+
const resp = await callCloudApiPost(`content/duplicate/${folid}/${cntid}`, { name });
|
|
247
|
+
socket.emitChanged('cloud-content-changed');
|
|
248
|
+
socket.emit('cloud-content-updated');
|
|
249
|
+
return resp;
|
|
250
|
+
},
|
|
251
|
+
|
|
252
|
+
exportFile_meta: true,
|
|
253
|
+
async exportFile({ folid, cntid, filePath }, req) {
|
|
254
|
+
const { content } = await getCloudContent(folid, cntid);
|
|
255
|
+
if (!content) {
|
|
256
|
+
throw new Error('File not found');
|
|
257
|
+
}
|
|
258
|
+
await fs.writeFile(filePath, content);
|
|
259
|
+
return true;
|
|
260
|
+
},
|
|
261
|
+
};
|
|
@@ -116,6 +116,7 @@ module.exports = {
|
|
|
116
116
|
processArgs.runE2eTests ? 'connections-e2etests.jsonl' : 'connections.jsonl'
|
|
117
117
|
),
|
|
118
118
|
supportCloudAutoUpgrade: !!process.env.CLOUD_UPGRADE_FILE,
|
|
119
|
+
allowPrivateCloud: platformInfo.isElectron || !!process.env.ALLOW_DBGATE_PRIVATE_CLOUD,
|
|
119
120
|
...currentVersion,
|
|
120
121
|
};
|
|
121
122
|
|
|
@@ -199,7 +200,7 @@ module.exports = {
|
|
|
199
200
|
|
|
200
201
|
saveLicenseKey_meta: true,
|
|
201
202
|
async saveLicenseKey({ licenseKey }) {
|
|
202
|
-
const decoded = jwt.decode(licenseKey);
|
|
203
|
+
const decoded = jwt.decode(licenseKey?.trim());
|
|
203
204
|
if (!decoded) {
|
|
204
205
|
return {
|
|
205
206
|
status: 'error',
|
|
@@ -239,6 +239,19 @@ module.exports = {
|
|
|
239
239
|
return (await this.datastore.find()).filter(x => connectionHasPermission(x, req));
|
|
240
240
|
},
|
|
241
241
|
|
|
242
|
+
async getUsedEngines() {
|
|
243
|
+
const storage = require('./storage');
|
|
244
|
+
|
|
245
|
+
const storageEngines = await storage.getUsedEngines();
|
|
246
|
+
if (storageEngines) {
|
|
247
|
+
return storageEngines;
|
|
248
|
+
}
|
|
249
|
+
if (portalConnections) {
|
|
250
|
+
return _.uniq(_.compact(portalConnections.map(x => x.engine)));
|
|
251
|
+
}
|
|
252
|
+
return _.uniq((await this.datastore.find()).map(x => x.engine));
|
|
253
|
+
},
|
|
254
|
+
|
|
242
255
|
test_meta: true,
|
|
243
256
|
test({ connection, requestDbList = false }) {
|
|
244
257
|
const subprocess = fork(
|
|
@@ -410,6 +423,13 @@ module.exports = {
|
|
|
410
423
|
return volatile;
|
|
411
424
|
}
|
|
412
425
|
|
|
426
|
+
const cloudMatch = conid.match(/^cloud\:\/\/(.+)\/(.+)$/);
|
|
427
|
+
if (cloudMatch) {
|
|
428
|
+
const { loadCachedCloudConnection } = require('../utility/cloudIntf');
|
|
429
|
+
const conn = await loadCachedCloudConnection(cloudMatch[1], cloudMatch[2]);
|
|
430
|
+
return conn;
|
|
431
|
+
}
|
|
432
|
+
|
|
413
433
|
const storage = require('./storage');
|
|
414
434
|
|
|
415
435
|
const storageConnection = await storage.getConnection({ conid });
|
|
@@ -148,6 +148,9 @@ module.exports = {
|
|
|
148
148
|
const existing = this.opened.find(x => x.conid == conid && x.database == database);
|
|
149
149
|
if (existing) return existing;
|
|
150
150
|
const connection = await connections.getCore({ conid });
|
|
151
|
+
if (!connection) {
|
|
152
|
+
throw new Error(`databaseConnections: Connection with conid="${conid}" not found`);
|
|
153
|
+
}
|
|
151
154
|
if (connection.passwordMode == 'askPassword' || connection.passwordMode == 'askUser') {
|
|
152
155
|
throw new MissingCredentialsError({ conid, passwordMode: connection.passwordMode });
|
|
153
156
|
}
|
package/src/controllers/files.js
CHANGED
|
@@ -11,6 +11,8 @@ const apps = require('./apps');
|
|
|
11
11
|
const getMapExport = require('../utility/getMapExport');
|
|
12
12
|
const dbgateApi = require('../shell');
|
|
13
13
|
const { getLogger } = require('dbgate-tools');
|
|
14
|
+
const platformInfo = require('../utility/platformInfo');
|
|
15
|
+
const { checkSecureFilePathsWithoutDirectory, checkSecureDirectories } = require('../utility/security');
|
|
14
16
|
const logger = getLogger('files');
|
|
15
17
|
|
|
16
18
|
function serialize(format, data) {
|
|
@@ -51,6 +53,9 @@ module.exports = {
|
|
|
51
53
|
delete_meta: true,
|
|
52
54
|
async delete({ folder, file }, req) {
|
|
53
55
|
if (!hasPermission(`files/${folder}/write`, req)) return false;
|
|
56
|
+
if (!checkSecureFilePathsWithoutDirectory(folder, file)) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
54
59
|
await fs.unlink(path.join(filesdir(), folder, file));
|
|
55
60
|
socket.emitChanged(`files-changed`, { folder });
|
|
56
61
|
socket.emitChanged(`all-files-changed`);
|
|
@@ -60,6 +65,9 @@ module.exports = {
|
|
|
60
65
|
rename_meta: true,
|
|
61
66
|
async rename({ folder, file, newFile }, req) {
|
|
62
67
|
if (!hasPermission(`files/${folder}/write`, req)) return false;
|
|
68
|
+
if (!checkSecureFilePathsWithoutDirectory(folder, file, newFile)) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
63
71
|
await fs.rename(path.join(filesdir(), folder, file), path.join(filesdir(), folder, newFile));
|
|
64
72
|
socket.emitChanged(`files-changed`, { folder });
|
|
65
73
|
socket.emitChanged(`all-files-changed`);
|
|
@@ -77,6 +85,9 @@ module.exports = {
|
|
|
77
85
|
|
|
78
86
|
copy_meta: true,
|
|
79
87
|
async copy({ folder, file, newFile }, req) {
|
|
88
|
+
if (!checkSecureFilePathsWithoutDirectory(folder, file, newFile)) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
80
91
|
if (!hasPermission(`files/${folder}/write`, req)) return false;
|
|
81
92
|
await fs.copyFile(path.join(filesdir(), folder, file), path.join(filesdir(), folder, newFile));
|
|
82
93
|
socket.emitChanged(`files-changed`, { folder });
|
|
@@ -86,6 +97,10 @@ module.exports = {
|
|
|
86
97
|
|
|
87
98
|
load_meta: true,
|
|
88
99
|
async load({ folder, file, format }, req) {
|
|
100
|
+
if (!checkSecureFilePathsWithoutDirectory(folder, file)) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
|
|
89
104
|
if (folder.startsWith('archive:')) {
|
|
90
105
|
const text = await fs.readFile(path.join(resolveArchiveFolder(folder.substring('archive:'.length)), file), {
|
|
91
106
|
encoding: 'utf-8',
|
|
@@ -105,12 +120,20 @@ module.exports = {
|
|
|
105
120
|
|
|
106
121
|
loadFrom_meta: true,
|
|
107
122
|
async loadFrom({ filePath, format }, req) {
|
|
123
|
+
if (!platformInfo.isElectron) {
|
|
124
|
+
// this is available only in electron app
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
108
127
|
const text = await fs.readFile(filePath, { encoding: 'utf-8' });
|
|
109
128
|
return deserialize(format, text);
|
|
110
129
|
},
|
|
111
130
|
|
|
112
131
|
save_meta: true,
|
|
113
132
|
async save({ folder, file, data, format }, req) {
|
|
133
|
+
if (!checkSecureFilePathsWithoutDirectory(folder, file)) {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
|
|
114
137
|
if (folder.startsWith('archive:')) {
|
|
115
138
|
if (!hasPermission(`archive/write`, req)) return false;
|
|
116
139
|
const dir = resolveArchiveFolder(folder.substring('archive:'.length));
|
|
@@ -143,6 +166,11 @@ module.exports = {
|
|
|
143
166
|
|
|
144
167
|
saveAs_meta: true,
|
|
145
168
|
async saveAs({ filePath, data, format }) {
|
|
169
|
+
if (!platformInfo.isElectron) {
|
|
170
|
+
// this is available only in electron app
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
|
|
146
174
|
await fs.writeFile(filePath, serialize(format, data));
|
|
147
175
|
},
|
|
148
176
|
|
|
@@ -275,6 +303,11 @@ module.exports = {
|
|
|
275
303
|
|
|
276
304
|
simpleCopy_meta: true,
|
|
277
305
|
async simpleCopy({ sourceFilePath, targetFilePath }, req) {
|
|
306
|
+
if (!platformInfo.isElectron) {
|
|
307
|
+
if (!checkSecureDirectories(sourceFilePath, targetFilePath)) {
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
278
311
|
await fs.copyFile(sourceFilePath, targetFilePath);
|
|
279
312
|
return true;
|
|
280
313
|
},
|
|
@@ -10,6 +10,7 @@ const requirePluginFunction = require('../utility/requirePluginFunction');
|
|
|
10
10
|
const socket = require('../utility/socket');
|
|
11
11
|
const crypto = require('crypto');
|
|
12
12
|
const dbgateApi = require('../shell');
|
|
13
|
+
const { ChartProcessor } = require('dbgate-datalib');
|
|
13
14
|
|
|
14
15
|
function readFirstLine(file) {
|
|
15
16
|
return new Promise((resolve, reject) => {
|
|
@@ -302,4 +303,29 @@ module.exports = {
|
|
|
302
303
|
await dbgateApi.download(uri, { targetFile: getJslFileName(jslid) });
|
|
303
304
|
return { jslid };
|
|
304
305
|
},
|
|
306
|
+
|
|
307
|
+
buildChart_meta: true,
|
|
308
|
+
async buildChart({ jslid, definition }) {
|
|
309
|
+
const datastore = new JsonLinesDatastore(getJslFileName(jslid));
|
|
310
|
+
const processor = new ChartProcessor(definition ? [definition] : undefined);
|
|
311
|
+
await datastore.enumRows(row => {
|
|
312
|
+
processor.addRow(row);
|
|
313
|
+
return true;
|
|
314
|
+
});
|
|
315
|
+
processor.finalize();
|
|
316
|
+
return processor.charts;
|
|
317
|
+
},
|
|
318
|
+
|
|
319
|
+
detectChartColumns_meta: true,
|
|
320
|
+
async detectChartColumns({ jslid }) {
|
|
321
|
+
const datastore = new JsonLinesDatastore(getJslFileName(jslid));
|
|
322
|
+
const processor = new ChartProcessor();
|
|
323
|
+
processor.autoDetectCharts = false;
|
|
324
|
+
await datastore.enumRows(row => {
|
|
325
|
+
processor.addRow(row);
|
|
326
|
+
return true;
|
|
327
|
+
});
|
|
328
|
+
processor.finalize();
|
|
329
|
+
return processor.availableColumns;
|
|
330
|
+
},
|
|
305
331
|
};
|
|
@@ -19,6 +19,7 @@ const {
|
|
|
19
19
|
const { handleProcessCommunication } = require('../utility/processComm');
|
|
20
20
|
const processArgs = require('../utility/processArgs');
|
|
21
21
|
const platformInfo = require('../utility/platformInfo');
|
|
22
|
+
const { checkSecureDirectories, checkSecureDirectoriesInScript } = require('../utility/security');
|
|
22
23
|
const logger = getLogger('runners');
|
|
23
24
|
|
|
24
25
|
function extractPlugins(script) {
|
|
@@ -273,6 +274,12 @@ module.exports = {
|
|
|
273
274
|
const runid = crypto.randomUUID();
|
|
274
275
|
|
|
275
276
|
if (script.type == 'json') {
|
|
277
|
+
if (!platformInfo.isElectron) {
|
|
278
|
+
if (!checkSecureDirectoriesInScript(script)) {
|
|
279
|
+
return { errorMessage: 'Unallowed directories in script' };
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
276
283
|
const js = await jsonScriptToJavascript(script);
|
|
277
284
|
return this.startCore(runid, scriptTemplate(js, false));
|
|
278
285
|
}
|
|
@@ -317,6 +324,11 @@ module.exports = {
|
|
|
317
324
|
|
|
318
325
|
loadReader_meta: true,
|
|
319
326
|
async loadReader({ functionName, props }) {
|
|
327
|
+
if (!platformInfo.isElectron) {
|
|
328
|
+
if (props?.fileName && !checkSecureDirectories(props.fileName)) {
|
|
329
|
+
return { errorMessage: 'Unallowed file' };
|
|
330
|
+
}
|
|
331
|
+
}
|
|
320
332
|
const prefix = extractShellApiPlugins(functionName)
|
|
321
333
|
.map(packageName => `// @require ${packageName}\n`)
|
|
322
334
|
.join('');
|
|
@@ -52,7 +52,7 @@ module.exports = {
|
|
|
52
52
|
if (existing) return existing;
|
|
53
53
|
const connection = await connections.getCore({ conid });
|
|
54
54
|
if (!connection) {
|
|
55
|
-
throw new Error(`Connection with conid="${conid}" not found`);
|
|
55
|
+
throw new Error(`serverConnections: Connection with conid="${conid}" not found`);
|
|
56
56
|
}
|
|
57
57
|
if (connection.singleDatabase) {
|
|
58
58
|
return null;
|
|
@@ -83,6 +83,11 @@ module.exports = {
|
|
|
83
83
|
jsldata.notifyChangedStats(stats);
|
|
84
84
|
},
|
|
85
85
|
|
|
86
|
+
handle_charts(sesid, props) {
|
|
87
|
+
const { jslid, charts, resultIndex } = props;
|
|
88
|
+
socket.emit(`session-charts-${sesid}`, { jslid, resultIndex, charts });
|
|
89
|
+
},
|
|
90
|
+
|
|
86
91
|
handle_initializeFile(sesid, props) {
|
|
87
92
|
const { jslid } = props;
|
|
88
93
|
socket.emit(`session-initialize-file-${jslid}`);
|
|
@@ -141,7 +146,7 @@ module.exports = {
|
|
|
141
146
|
},
|
|
142
147
|
|
|
143
148
|
executeQuery_meta: true,
|
|
144
|
-
async executeQuery({ sesid, sql, autoCommit, limitRows }) {
|
|
149
|
+
async executeQuery({ sesid, sql, autoCommit, limitRows, frontMatter }) {
|
|
145
150
|
const session = this.opened.find(x => x.sesid == sesid);
|
|
146
151
|
if (!session) {
|
|
147
152
|
throw new Error('Invalid session');
|
|
@@ -149,7 +154,7 @@ module.exports = {
|
|
|
149
154
|
|
|
150
155
|
logger.info({ sesid, sql }, 'Processing query');
|
|
151
156
|
this.dispatchMessage(sesid, 'Query execution started');
|
|
152
|
-
session.subprocess.send({ msgtype: 'executeQuery', sql, autoCommit, limitRows });
|
|
157
|
+
session.subprocess.send({ msgtype: 'executeQuery', sql, autoCommit, limitRows, frontMatter });
|
|
153
158
|
|
|
154
159
|
return { state: 'ok' };
|
|
155
160
|
},
|
|
@@ -742,4 +742,13 @@ module.exports = {
|
|
|
742
742
|
});
|
|
743
743
|
socket.emitChanged('connection-list-changed');
|
|
744
744
|
},
|
|
745
|
+
|
|
746
|
+
async getUsedEngines() {
|
|
747
|
+
if (!process.env.STORAGE_DATABASE) {
|
|
748
|
+
return null;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
const resp = await storageSelectFmt(`select distinct ~engine from ~connections`);
|
|
752
|
+
return resp.map(x => x.engine);
|
|
753
|
+
},
|
|
745
754
|
};
|
|
@@ -44,6 +44,10 @@ module.exports = {
|
|
|
44
44
|
raw: true,
|
|
45
45
|
},
|
|
46
46
|
get(req, res) {
|
|
47
|
+
if (req.query.file.includes('..') || req.query.file.includes('/') || req.query.file.includes('\\')) {
|
|
48
|
+
res.status(400).send('Invalid file path');
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
47
51
|
res.sendFile(path.join(uploadsdir(), req.query.file));
|
|
48
52
|
},
|
|
49
53
|
|
package/src/currentVersion.js
CHANGED
package/src/main.js
CHANGED
|
@@ -27,6 +27,7 @@ const plugins = require('./controllers/plugins');
|
|
|
27
27
|
const files = require('./controllers/files');
|
|
28
28
|
const scheduler = require('./controllers/scheduler');
|
|
29
29
|
const queryHistory = require('./controllers/queryHistory');
|
|
30
|
+
const cloud = require('./controllers/cloud');
|
|
30
31
|
const onFinished = require('on-finished');
|
|
31
32
|
const processArgs = require('./utility/processArgs');
|
|
32
33
|
|
|
@@ -39,6 +40,7 @@ const { getDefaultAuthProvider } = require('./auth/authProvider');
|
|
|
39
40
|
const startCloudUpgradeTimer = require('./utility/cloudUpgrade');
|
|
40
41
|
const { isProApp } = require('./utility/checkLicense');
|
|
41
42
|
const { getHealthStatus, getHealthStatusSprinx } = require('./utility/healthStatus');
|
|
43
|
+
const { startCloudFiles } = require('./utility/cloudIntf');
|
|
42
44
|
|
|
43
45
|
const logger = getLogger('main');
|
|
44
46
|
|
|
@@ -200,6 +202,8 @@ function start() {
|
|
|
200
202
|
if (process.env.CLOUD_UPGRADE_FILE) {
|
|
201
203
|
startCloudUpgradeTimer();
|
|
202
204
|
}
|
|
205
|
+
|
|
206
|
+
startCloudFiles();
|
|
203
207
|
}
|
|
204
208
|
|
|
205
209
|
function useAllControllers(app, electron) {
|
|
@@ -220,6 +224,7 @@ function useAllControllers(app, electron) {
|
|
|
220
224
|
useController(app, electron, '/query-history', queryHistory);
|
|
221
225
|
useController(app, electron, '/apps', apps);
|
|
222
226
|
useController(app, electron, '/auth', auth);
|
|
227
|
+
useController(app, electron, '/cloud', cloud);
|
|
223
228
|
}
|
|
224
229
|
|
|
225
230
|
function setElectronSender(electronSender) {
|
|
@@ -28,14 +28,7 @@ function start() {
|
|
|
28
28
|
let version = {
|
|
29
29
|
version: 'Unknown',
|
|
30
30
|
};
|
|
31
|
-
|
|
32
|
-
version = await driver.getVersion(dbhan);
|
|
33
|
-
} catch (err) {
|
|
34
|
-
logger.error(extractErrorLogData(err), 'Error getting DB server version');
|
|
35
|
-
version = {
|
|
36
|
-
version: 'Unknown',
|
|
37
|
-
};
|
|
38
|
-
}
|
|
31
|
+
version = await driver.getVersion(dbhan);
|
|
39
32
|
let databases = undefined;
|
|
40
33
|
if (requestDbList) {
|
|
41
34
|
databases = await driver.listDatabases(dbhan);
|
|
@@ -117,7 +117,7 @@ async function handleExecuteControlCommand({ command }) {
|
|
|
117
117
|
}
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
async function handleExecuteQuery({ sql, autoCommit, limitRows }) {
|
|
120
|
+
async function handleExecuteQuery({ sql, autoCommit, limitRows, frontMatter }) {
|
|
121
121
|
lastActivity = new Date().getTime();
|
|
122
122
|
|
|
123
123
|
await waitConnected();
|
|
@@ -146,7 +146,7 @@ async function handleExecuteQuery({ sql, autoCommit, limitRows }) {
|
|
|
146
146
|
...driver.getQuerySplitterOptions('stream'),
|
|
147
147
|
returnRichInfo: true,
|
|
148
148
|
})) {
|
|
149
|
-
await handleQueryStream(dbhan, driver, queryStreamInfoHolder, sqlItem, undefined, limitRows);
|
|
149
|
+
await handleQueryStream(dbhan, driver, queryStreamInfoHolder, sqlItem, undefined, limitRows, frontMatter);
|
|
150
150
|
// const handler = new StreamHandler(resultIndex);
|
|
151
151
|
// const stream = await driver.stream(systemConnection, sqlItem, handler);
|
|
152
152
|
// handler.stream = stream;
|