dbgate-api-premium 7.1.3-alpha.3 → 7.1.3
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 +6 -6
- package/src/auth/storageAuthProvider.js +16 -6
- package/src/controllers/databaseConnections.js +40 -8
- package/src/controllers/files.js +38 -4
- package/src/controllers/sessions.js +13 -0
- package/src/currentVersion.js +2 -2
- package/src/proc/databaseConnectionProcess.js +4 -4
- package/src/proc/restConnectionProcess.js +55 -19
- package/src/proc/sessionProcess.js +33 -0
- package/src/storageModel.js +24 -0
- package/src/utility/connectUtility.js +29 -1
- package/src/utility/crypting.js +1 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dbgate-api-premium",
|
|
3
3
|
"main": "src/index.js",
|
|
4
|
-
"version": "7.1.3
|
|
4
|
+
"version": "7.1.3",
|
|
5
5
|
"homepage": "https://www.dbgate.io/",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -30,11 +30,11 @@
|
|
|
30
30
|
"compare-versions": "^3.6.0",
|
|
31
31
|
"cors": "^2.8.5",
|
|
32
32
|
"cross-env": "^6.0.3",
|
|
33
|
-
"dbgate-datalib": "7.1.3
|
|
33
|
+
"dbgate-datalib": "7.1.3",
|
|
34
34
|
"dbgate-query-splitter": "^4.12.0",
|
|
35
|
-
"dbgate-rest": "7.1.3
|
|
36
|
-
"dbgate-sqltree": "7.1.3
|
|
37
|
-
"dbgate-tools": "7.1.3
|
|
35
|
+
"dbgate-rest": "7.1.3",
|
|
36
|
+
"dbgate-sqltree": "7.1.3",
|
|
37
|
+
"dbgate-tools": "7.1.3",
|
|
38
38
|
"debug": "^4.3.4",
|
|
39
39
|
"diff": "^5.0.0",
|
|
40
40
|
"diff2html": "^3.4.13",
|
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
"devDependencies": {
|
|
89
89
|
"@types/fs-extra": "^9.0.11",
|
|
90
90
|
"@types/lodash": "^4.14.149",
|
|
91
|
-
"dbgate-types": "7.1.3
|
|
91
|
+
"dbgate-types": "7.1.3",
|
|
92
92
|
"env-cmd": "^10.1.0",
|
|
93
93
|
"jsdoc-to-markdown": "^9.0.5",
|
|
94
94
|
"node-loader": "^1.0.2",
|
|
@@ -34,7 +34,16 @@ async function loadPermissionsForUserId(userId) {
|
|
|
34
34
|
const rolePermissions = sortPermissionsFromTheSameLevel(await storageReadUserRolePermissions(userId));
|
|
35
35
|
const userPermissions = await storageReadUserPermissions(userId);
|
|
36
36
|
const loggedUserPermissions = await storageReadRolePermissions(-2);
|
|
37
|
-
|
|
37
|
+
const resp = [
|
|
38
|
+
...getPredefinedPermissions('logged-user'),
|
|
39
|
+
...loggedUserPermissions,
|
|
40
|
+
...rolePermissions,
|
|
41
|
+
...userPermissions,
|
|
42
|
+
];
|
|
43
|
+
if (resp.some(p => p?.startsWith('admin/'))) {
|
|
44
|
+
resp.push('widgets/admin');
|
|
45
|
+
}
|
|
46
|
+
return resp;
|
|
38
47
|
}
|
|
39
48
|
|
|
40
49
|
function getBuiltinRoleIdFromRequest(req) {
|
|
@@ -390,10 +399,11 @@ class OauthProvider extends StorageProviderBase {
|
|
|
390
399
|
const scopeParam = this.config.oauthScope ? `&scope=${this.config.oauthScope}` : '';
|
|
391
400
|
return {
|
|
392
401
|
status: 'ok',
|
|
393
|
-
uri: `${this.config.oauthAuth}?client_id=${
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
402
|
+
uri: `${this.config.oauthAuth}?client_id=${
|
|
403
|
+
this.config.oauthClient
|
|
404
|
+
}&response_type=code&redirect_uri=${encodeURIComponent(redirectUri)}&state=${encodeURIComponent(
|
|
405
|
+
state
|
|
406
|
+
)}${scopeParam}`,
|
|
397
407
|
};
|
|
398
408
|
}
|
|
399
409
|
|
|
@@ -714,5 +724,5 @@ function createStorageAuthProvider(config) {
|
|
|
714
724
|
module.exports = {
|
|
715
725
|
createStorageAuthProvider,
|
|
716
726
|
SuperadminAuthProvider,
|
|
717
|
-
getBuiltinRoleIdFromRequest
|
|
727
|
+
getBuiltinRoleIdFromRequest,
|
|
718
728
|
};
|
|
@@ -95,10 +95,12 @@ module.exports = {
|
|
|
95
95
|
}
|
|
96
96
|
},
|
|
97
97
|
handle_response(conid, database, { msgid, ...response }) {
|
|
98
|
-
const [resolve, reject, additionalData] = this.requests[msgid];
|
|
99
|
-
resolve
|
|
100
|
-
|
|
101
|
-
additionalData?.auditLogger
|
|
98
|
+
const [resolve, reject, additionalData] = this.requests[msgid] || [];
|
|
99
|
+
if (resolve) {
|
|
100
|
+
resolve(response);
|
|
101
|
+
if (additionalData?.auditLogger) {
|
|
102
|
+
additionalData?.auditLogger(response);
|
|
103
|
+
}
|
|
102
104
|
}
|
|
103
105
|
delete this.requests[msgid];
|
|
104
106
|
},
|
|
@@ -239,7 +241,7 @@ module.exports = {
|
|
|
239
241
|
sendRequest(conn, message, additionalData = {}) {
|
|
240
242
|
const msgid = crypto.randomUUID();
|
|
241
243
|
const promise = new Promise((resolve, reject) => {
|
|
242
|
-
this.requests[msgid] = [resolve, reject, additionalData];
|
|
244
|
+
this.requests[msgid] = [resolve, reject, additionalData, conn.conid, conn.database];
|
|
243
245
|
try {
|
|
244
246
|
const serializedMessage = serializeJsTypesForJsonStringify({ msgid, ...message });
|
|
245
247
|
conn.subprocess.send(serializedMessage);
|
|
@@ -264,12 +266,12 @@ module.exports = {
|
|
|
264
266
|
},
|
|
265
267
|
|
|
266
268
|
sqlSelect_meta: true,
|
|
267
|
-
async sqlSelect({ conid, database, select, auditLogSessionGroup }, req) {
|
|
269
|
+
async sqlSelect({ conid, database, select, commandTimeout, auditLogSessionGroup }, req) {
|
|
268
270
|
await testConnectionPermission(conid, req);
|
|
269
271
|
const opened = await this.ensureOpened(conid, database);
|
|
270
272
|
const res = await this.sendRequest(
|
|
271
273
|
opened,
|
|
272
|
-
{ msgtype: 'sqlSelect', select },
|
|
274
|
+
{ msgtype: 'sqlSelect', select, commandTimeout },
|
|
273
275
|
{
|
|
274
276
|
auditLogger:
|
|
275
277
|
auditLogSessionGroup && select?.from?.name?.pureName
|
|
@@ -344,9 +346,12 @@ module.exports = {
|
|
|
344
346
|
},
|
|
345
347
|
|
|
346
348
|
collectionData_meta: true,
|
|
347
|
-
async collectionData({ conid, database, options, auditLogSessionGroup }, req) {
|
|
349
|
+
async collectionData({ conid, database, options, commandTimeout, auditLogSessionGroup }, req) {
|
|
348
350
|
await testConnectionPermission(conid, req);
|
|
349
351
|
const opened = await this.ensureOpened(conid, database);
|
|
352
|
+
if (commandTimeout && options) {
|
|
353
|
+
options.commandTimeout = commandTimeout;
|
|
354
|
+
}
|
|
350
355
|
const res = await this.sendRequest(
|
|
351
356
|
opened,
|
|
352
357
|
{ msgtype: 'collectionData', options },
|
|
@@ -580,6 +585,24 @@ module.exports = {
|
|
|
580
585
|
};
|
|
581
586
|
},
|
|
582
587
|
|
|
588
|
+
pingDatabases_meta: true,
|
|
589
|
+
async pingDatabases({ databases }, req) {
|
|
590
|
+
if (!databases || !Array.isArray(databases)) return { status: 'ok' };
|
|
591
|
+
for (const { conid, database } of databases) {
|
|
592
|
+
if (!conid || !database) continue;
|
|
593
|
+
const existing = this.opened.find(x => x.conid == conid && x.database == database);
|
|
594
|
+
if (existing) {
|
|
595
|
+
try {
|
|
596
|
+
existing.subprocess.send({ msgtype: 'ping' });
|
|
597
|
+
} catch (err) {
|
|
598
|
+
logger.error(extractErrorLogData(err), 'DBGM-00308 Error pinging DB connection');
|
|
599
|
+
this.close(conid, database);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
return { status: 'ok' };
|
|
604
|
+
},
|
|
605
|
+
|
|
583
606
|
refresh_meta: true,
|
|
584
607
|
async refresh({ conid, database, keepOpen }, req) {
|
|
585
608
|
await testConnectionPermission(conid, req);
|
|
@@ -622,6 +645,15 @@ module.exports = {
|
|
|
622
645
|
structure: existing.structure,
|
|
623
646
|
};
|
|
624
647
|
socket.emitChanged(`database-status-changed`, { conid, database });
|
|
648
|
+
|
|
649
|
+
// Reject all pending requests for this connection
|
|
650
|
+
for (const [msgid, entry] of Object.entries(this.requests)) {
|
|
651
|
+
const [resolve, reject, additionalData, reqConid, reqDatabase] = entry;
|
|
652
|
+
if (reqConid === conid && reqDatabase === database) {
|
|
653
|
+
reject('DBGM-00309 Database connection closed');
|
|
654
|
+
delete this.requests[msgid];
|
|
655
|
+
}
|
|
656
|
+
}
|
|
625
657
|
}
|
|
626
658
|
},
|
|
627
659
|
|
package/src/controllers/files.js
CHANGED
|
@@ -15,7 +15,8 @@ const getDiagramExport = require('../utility/getDiagramExport');
|
|
|
15
15
|
const apps = require('./apps');
|
|
16
16
|
const getMapExport = require('../utility/getMapExport');
|
|
17
17
|
const dbgateApi = require('../shell');
|
|
18
|
-
const { getLogger } = require('dbgate-tools');
|
|
18
|
+
const { getLogger, getSqlFrontMatter } = require('dbgate-tools');
|
|
19
|
+
const yaml = require('js-yaml');
|
|
19
20
|
const platformInfo = require('../utility/platformInfo');
|
|
20
21
|
const { checkSecureFilePathsWithoutDirectory, checkSecureDirectories } = require('../utility/security');
|
|
21
22
|
const { copyAppLogsIntoFile, getRecentAppLogRecords } = require('../utility/appLogStore');
|
|
@@ -35,13 +36,46 @@ function deserialize(format, text) {
|
|
|
35
36
|
|
|
36
37
|
module.exports = {
|
|
37
38
|
list_meta: true,
|
|
38
|
-
async list({ folder }, req) {
|
|
39
|
+
async list({ folder, parseFrontMatter }, req) {
|
|
39
40
|
const loadedPermissions = await loadPermissionsFromRequest(req);
|
|
40
41
|
if (!hasPermission(`files/${folder}/read`, loadedPermissions)) return [];
|
|
41
42
|
const dir = path.join(filesdir(), folder);
|
|
42
43
|
if (!(await fs.exists(dir))) return [];
|
|
43
|
-
const
|
|
44
|
-
|
|
44
|
+
const fileNames = await fs.readdir(dir);
|
|
45
|
+
if (!parseFrontMatter) {
|
|
46
|
+
return fileNames.map(file => ({ folder, file }));
|
|
47
|
+
}
|
|
48
|
+
const result = [];
|
|
49
|
+
for (const file of fileNames) {
|
|
50
|
+
const item = { folder, file };
|
|
51
|
+
let fh;
|
|
52
|
+
try {
|
|
53
|
+
fh = await require('fs').promises.open(path.join(dir, file), 'r');
|
|
54
|
+
const buf = new Uint8Array(512);
|
|
55
|
+
const { bytesRead } = await fh.read(buf, 0, 512, 0);
|
|
56
|
+
let text = Buffer.from(buf.buffer, 0, bytesRead).toString('utf-8');
|
|
57
|
+
|
|
58
|
+
if (text.includes('-- >>>') && !text.includes('-- <<<')) {
|
|
59
|
+
const stat = await fh.stat();
|
|
60
|
+
const fullSize = Math.min(stat.size, 4096);
|
|
61
|
+
if (fullSize > 512) {
|
|
62
|
+
const fullBuf = new Uint8Array(fullSize);
|
|
63
|
+
const { bytesRead: fullBytesRead } = await fh.read(fullBuf, 0, fullSize, 0);
|
|
64
|
+
text = Buffer.from(fullBuf.buffer, 0, fullBytesRead).toString('utf-8');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const fm = getSqlFrontMatter(text, yaml);
|
|
69
|
+
if (fm?.connectionId) item.connectionId = fm.connectionId;
|
|
70
|
+
if (fm?.databaseName) item.databaseName = fm.databaseName;
|
|
71
|
+
} catch (e) {
|
|
72
|
+
// ignore read errors for individual files
|
|
73
|
+
} finally {
|
|
74
|
+
if (fh) await fh.close().catch(() => {});
|
|
75
|
+
}
|
|
76
|
+
result.push(item);
|
|
77
|
+
}
|
|
78
|
+
return result;
|
|
45
79
|
},
|
|
46
80
|
|
|
47
81
|
listAll_meta: true,
|
|
@@ -228,6 +228,19 @@ module.exports = {
|
|
|
228
228
|
return { state: 'ok' };
|
|
229
229
|
},
|
|
230
230
|
|
|
231
|
+
setIsolationLevel_meta: true,
|
|
232
|
+
async setIsolationLevel({ sesid, level }) {
|
|
233
|
+
const session = this.opened.find(x => x.sesid == sesid);
|
|
234
|
+
if (!session) {
|
|
235
|
+
throw new Error('Invalid session');
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
logger.info({ sesid, level }, 'DBGM-00315 Setting transaction isolation level');
|
|
239
|
+
session.subprocess.send({ msgtype: 'setIsolationLevel', level });
|
|
240
|
+
|
|
241
|
+
return { state: 'ok' };
|
|
242
|
+
},
|
|
243
|
+
|
|
231
244
|
executeReader_meta: true,
|
|
232
245
|
async executeReader({ conid, database, sql, queryName, appFolder }) {
|
|
233
246
|
const { sesid } = await this.create({ conid, database });
|
package/src/currentVersion.js
CHANGED
|
@@ -234,12 +234,12 @@ async function handleRunOperation({ msgid, operation, useTransaction }, skipRead
|
|
|
234
234
|
}
|
|
235
235
|
}
|
|
236
236
|
|
|
237
|
-
async function handleQueryData({ msgid, sql, range }, skipReadonlyCheck = false) {
|
|
237
|
+
async function handleQueryData({ msgid, sql, range, commandTimeout }, skipReadonlyCheck = false) {
|
|
238
238
|
await waitConnected();
|
|
239
239
|
const driver = requireEngineDriver(storedConnection);
|
|
240
240
|
try {
|
|
241
241
|
if (!skipReadonlyCheck) ensureExecuteCustomScript(driver);
|
|
242
|
-
const res = await driver.query(dbhan, sql, { range });
|
|
242
|
+
const res = await driver.query(dbhan, sql, { range, commandTimeout });
|
|
243
243
|
process.send({ msgtype: 'response', msgid, ...serializeJsTypesForJsonStringify(res) });
|
|
244
244
|
} catch (err) {
|
|
245
245
|
process.send({
|
|
@@ -250,11 +250,11 @@ async function handleQueryData({ msgid, sql, range }, skipReadonlyCheck = false)
|
|
|
250
250
|
}
|
|
251
251
|
}
|
|
252
252
|
|
|
253
|
-
async function handleSqlSelect({ msgid, select }) {
|
|
253
|
+
async function handleSqlSelect({ msgid, select, commandTimeout }) {
|
|
254
254
|
const driver = requireEngineDriver(storedConnection);
|
|
255
255
|
const dmp = driver.createDumper();
|
|
256
256
|
dumpSqlSelect(dmp, select);
|
|
257
|
-
return handleQueryData({ msgid, sql: dmp.s, range: select.range }, true);
|
|
257
|
+
return handleQueryData({ msgid, sql: dmp.s, range: select.range, commandTimeout }, true);
|
|
258
258
|
}
|
|
259
259
|
|
|
260
260
|
async function handleDriverDataCore(msgid, callMethod, { logName }) {
|
|
@@ -11,6 +11,7 @@ const fs = require('fs');
|
|
|
11
11
|
const { Readable, Transform } = require('stream');
|
|
12
12
|
const { pipeline } = require('stream/promises');
|
|
13
13
|
const getJslFileName = require('../utility/getJslFileName');
|
|
14
|
+
const { decryptPasswordString } = require('../utility/crypting');
|
|
14
15
|
const {
|
|
15
16
|
analyseOpenApiDefinition,
|
|
16
17
|
analyseODataDefinition,
|
|
@@ -23,8 +24,37 @@ const {
|
|
|
23
24
|
|
|
24
25
|
const logger = getLogger('restconnProcess');
|
|
25
26
|
|
|
27
|
+
function buildProxyConfig(proxyUrl, proxyUser, proxyPassword) {
|
|
28
|
+
const url = String(proxyUrl ?? '').trim();
|
|
29
|
+
const user = String(proxyUser ?? '').trim();
|
|
30
|
+
const pass = String(proxyPassword ?? '').trim();
|
|
31
|
+
if (!url && (user || pass)) {
|
|
32
|
+
throw new Error('DBGM-00327 Proxy user or password is set but proxy URL is missing');
|
|
33
|
+
}
|
|
34
|
+
if (!url) return undefined;
|
|
35
|
+
try {
|
|
36
|
+
const parsed = new URL(url.includes('://') ? url : `http://${url}`);
|
|
37
|
+
const config = {
|
|
38
|
+
protocol: parsed.protocol.replace(':', ''),
|
|
39
|
+
host: parsed.hostname,
|
|
40
|
+
port: parsed.port ? parseInt(parsed.port, 10) : (parsed.protocol === 'https:' ? 443 : 80),
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const username = proxyUser ?? parsed.username;
|
|
44
|
+
const rawPassword = proxyPassword ?? parsed.password;
|
|
45
|
+
const password = decryptPasswordString(rawPassword);
|
|
46
|
+
if (username) {
|
|
47
|
+
config.auth = { username, password: password ?? '' };
|
|
48
|
+
}
|
|
49
|
+
return config;
|
|
50
|
+
} catch (err) {
|
|
51
|
+
throw new Error(`DBGM-00328 Invalid proxy URL "${url}": ${err && err.message ? err.message : err}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
26
55
|
let restInfo = null;
|
|
27
56
|
let storedConnection = null;
|
|
57
|
+
let axiosInstance = axios.default;
|
|
28
58
|
let lastSwaggerUrl = null;
|
|
29
59
|
let lastApiInfo = null;
|
|
30
60
|
let lastConnectError = null;
|
|
@@ -79,7 +109,7 @@ async function fetchOpenApiStructure() {
|
|
|
79
109
|
|
|
80
110
|
let swaggerData = null;
|
|
81
111
|
try {
|
|
82
|
-
const response = await
|
|
112
|
+
const response = await axiosInstance.get(url, {
|
|
83
113
|
timeout: 10000,
|
|
84
114
|
headers,
|
|
85
115
|
});
|
|
@@ -101,7 +131,7 @@ async function fetchOpenApiStructure() {
|
|
|
101
131
|
|
|
102
132
|
async function fetchODataStructure() {
|
|
103
133
|
if (!storedConnection.apiServerUrl1) {
|
|
104
|
-
throw new Error('DBGM-
|
|
134
|
+
throw new Error('DBGM-00316 OData service URL is not configured');
|
|
105
135
|
}
|
|
106
136
|
|
|
107
137
|
const url = storedConnection.apiServerUrl1;
|
|
@@ -112,25 +142,27 @@ async function fetchODataStructure() {
|
|
|
112
142
|
'OData-Version': '4.0',
|
|
113
143
|
};
|
|
114
144
|
|
|
145
|
+
const proxyConfig = buildProxyConfig(storedConnection?.httpProxyUrl, storedConnection?.httpProxyUser, storedConnection?.httpProxyPassword);
|
|
115
146
|
let serviceDocument = null;
|
|
116
147
|
try {
|
|
117
|
-
const response = await
|
|
148
|
+
const response = await axiosInstance.get(url, {
|
|
118
149
|
timeout: 10000,
|
|
119
150
|
headers: odataHeaders,
|
|
151
|
+
...(proxyConfig && { proxy: proxyConfig }),
|
|
120
152
|
});
|
|
121
153
|
serviceDocument = parseStructuredContent(response.data);
|
|
122
154
|
} catch (err) {
|
|
123
|
-
throw new Error(`DBGM-
|
|
155
|
+
throw new Error(`DBGM-00317 Could not fetch OData service document: ${err.message}`);
|
|
124
156
|
}
|
|
125
157
|
|
|
126
158
|
if (!serviceDocument || typeof serviceDocument !== 'object') {
|
|
127
|
-
throw new Error('DBGM-
|
|
159
|
+
throw new Error('DBGM-00318 OData service document is empty or invalid');
|
|
128
160
|
}
|
|
129
161
|
|
|
130
162
|
const metadataUrl = resolveODataMetadataUrl(serviceDocument['@odata.context'], url);
|
|
131
163
|
let metadataXml = '';
|
|
132
164
|
try {
|
|
133
|
-
const response = await
|
|
165
|
+
const response = await axiosInstance.get(metadataUrl, {
|
|
134
166
|
timeout: 10000,
|
|
135
167
|
headers: {
|
|
136
168
|
...authHeaders,
|
|
@@ -138,14 +170,15 @@ async function fetchODataStructure() {
|
|
|
138
170
|
'OData-Version': '4.0',
|
|
139
171
|
},
|
|
140
172
|
transformResponse: value => value,
|
|
173
|
+
...(proxyConfig && { proxy: proxyConfig }),
|
|
141
174
|
});
|
|
142
175
|
metadataXml = typeof response.data === 'string' ? response.data : String(response.data ?? '');
|
|
143
176
|
} catch (err) {
|
|
144
|
-
throw new Error(`DBGM-
|
|
177
|
+
throw new Error(`DBGM-00319 Could not fetch OData metadata document: ${err.message}`);
|
|
145
178
|
}
|
|
146
179
|
|
|
147
180
|
if (!metadataXml.trim()) {
|
|
148
|
-
throw new Error('DBGM-
|
|
181
|
+
throw new Error('DBGM-00320 OData metadata document is empty');
|
|
149
182
|
}
|
|
150
183
|
|
|
151
184
|
const apiInfo = analyseODataDefinition(serviceDocument, url, metadataXml);
|
|
@@ -184,7 +217,7 @@ async function fetchGraphQLSchemaViaIntrospection() {
|
|
|
184
217
|
storedConnection.apiServerUrl1,
|
|
185
218
|
// @ts-ignore
|
|
186
219
|
buildRestAuthHeaders(storedConnection.restAuth),
|
|
187
|
-
|
|
220
|
+
axiosInstance
|
|
188
221
|
);
|
|
189
222
|
|
|
190
223
|
return {
|
|
@@ -275,6 +308,9 @@ async function handleConnect(connection) {
|
|
|
275
308
|
setStatusName('pending');
|
|
276
309
|
|
|
277
310
|
try {
|
|
311
|
+
const proxyConfig = buildProxyConfig(connection?.httpProxyUrl, connection?.httpProxyUser, connection?.httpProxyPassword);
|
|
312
|
+
axiosInstance = proxyConfig ? axios.default.create({ proxy: proxyConfig }) : axios.default;
|
|
313
|
+
|
|
278
314
|
const isGraphQL = connection.engine === 'graphql@rest';
|
|
279
315
|
const structure = await loadStructureWithProgress('loadStructure', isGraphQL);
|
|
280
316
|
|
|
@@ -287,7 +323,7 @@ async function handleConnect(connection) {
|
|
|
287
323
|
const apiType = isGraphQL ? 'GraphQL' : connection.engine === 'odata@rest' ? 'OData' : 'REST';
|
|
288
324
|
logger.info(
|
|
289
325
|
{ url: storedConnection.apiServerUrl1 },
|
|
290
|
-
`DBGM-
|
|
326
|
+
`DBGM-00321 Connected to ${apiType} API`
|
|
291
327
|
);
|
|
292
328
|
resolveAfterConnect();
|
|
293
329
|
} catch (err) {
|
|
@@ -354,7 +390,7 @@ async function handleExecuteOpenapi({ msgid, endpoint, method, parameters, serve
|
|
|
354
390
|
parameters,
|
|
355
391
|
server,
|
|
356
392
|
auth,
|
|
357
|
-
|
|
393
|
+
axiosInstance
|
|
358
394
|
);
|
|
359
395
|
|
|
360
396
|
process.send({
|
|
@@ -389,7 +425,7 @@ async function handleExecuteOdata({ msgid, endpoint, method, parameters, server,
|
|
|
389
425
|
parameters,
|
|
390
426
|
server,
|
|
391
427
|
auth,
|
|
392
|
-
|
|
428
|
+
axiosInstance
|
|
393
429
|
);
|
|
394
430
|
|
|
395
431
|
process.send({
|
|
@@ -428,7 +464,7 @@ async function handleApiQuery({ msgid, server, query, variables, auth }) {
|
|
|
428
464
|
...buildRestAuthHeaders(auth),
|
|
429
465
|
};
|
|
430
466
|
|
|
431
|
-
const response = await
|
|
467
|
+
const response = await axiosInstance.post(url, { query, variables }, { headers });
|
|
432
468
|
|
|
433
469
|
process.send({
|
|
434
470
|
msgtype: 'response',
|
|
@@ -520,7 +556,7 @@ ${selectionBlock}
|
|
|
520
556
|
function extractGraphQlRows({ body, operationName, projection }) {
|
|
521
557
|
if (body?.errors?.length) {
|
|
522
558
|
const message = body.errors.map(item => item?.message).filter(Boolean).join('; ') || 'GraphQL query error';
|
|
523
|
-
throw new Error(`DBGM-
|
|
559
|
+
throw new Error(`DBGM-00322 ${message}`);
|
|
524
560
|
}
|
|
525
561
|
|
|
526
562
|
const operationData = body?.data?.[operationName];
|
|
@@ -581,7 +617,7 @@ async function* streamGraphQlRows({ url, headers, operationName, projection, que
|
|
|
581
617
|
filterValue,
|
|
582
618
|
});
|
|
583
619
|
|
|
584
|
-
const response = await
|
|
620
|
+
const response = await axiosInstance.post(url, { query }, { headers });
|
|
585
621
|
const { rows, hasNextPage, endCursor } = extractGraphQlRows({
|
|
586
622
|
body: response?.data,
|
|
587
623
|
operationName,
|
|
@@ -640,13 +676,13 @@ async function handleLoadGraphqlConnectionData({
|
|
|
640
676
|
|
|
641
677
|
const url = server || storedConnection?.apiServerUrl1;
|
|
642
678
|
if (!url) {
|
|
643
|
-
throw new Error('DBGM-
|
|
679
|
+
throw new Error('DBGM-00323 GraphQL endpoint URL is not configured');
|
|
644
680
|
}
|
|
645
681
|
if (!operationName) {
|
|
646
|
-
throw new Error('DBGM-
|
|
682
|
+
throw new Error('DBGM-00324 GraphQL operation name is missing');
|
|
647
683
|
}
|
|
648
684
|
if (!projection?.kind) {
|
|
649
|
-
throw new Error('DBGM-
|
|
685
|
+
throw new Error('DBGM-00325 GraphQL projection is missing');
|
|
650
686
|
}
|
|
651
687
|
|
|
652
688
|
const rowsPageSize = pageSize || 100;
|
|
@@ -711,7 +747,7 @@ async function handleLoadGraphqlConnectionData({
|
|
|
711
747
|
}
|
|
712
748
|
}
|
|
713
749
|
|
|
714
|
-
logger.error(extractErrorLogData(err), 'DBGM-
|
|
750
|
+
logger.error(extractErrorLogData(err), 'DBGM-00326 Error loading GraphQL connection data to JSL');
|
|
715
751
|
process.send({
|
|
716
752
|
msgtype: 'response',
|
|
717
753
|
msgid,
|
|
@@ -77,6 +77,38 @@ async function handleStopProfiler({ jslid }) {
|
|
|
77
77
|
currentProfiler = null;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
async function handleSetIsolationLevel({ level }) {
|
|
81
|
+
lastActivity = new Date().getTime();
|
|
82
|
+
|
|
83
|
+
await waitConnected();
|
|
84
|
+
const driver = requireEngineDriver(storedConnection);
|
|
85
|
+
|
|
86
|
+
if (!driver.setTransactionIsolationLevel) {
|
|
87
|
+
process.send({ msgtype: 'done', skipFinishedMessage: true });
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (driver.isolationLevels && level && !driver.isolationLevels.includes(level)) {
|
|
92
|
+
process.send({
|
|
93
|
+
msgtype: 'info',
|
|
94
|
+
info: {
|
|
95
|
+
message: `Isolation level "${level}" is not supported by this driver. Supported levels: ${driver.isolationLevels.join(', ')}`,
|
|
96
|
+
severity: 'error',
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
process.send({ msgtype: 'done', skipFinishedMessage: true });
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
executingScripts++;
|
|
104
|
+
try {
|
|
105
|
+
await driver.setTransactionIsolationLevel(dbhan, level);
|
|
106
|
+
process.send({ msgtype: 'done', controlCommand: 'setIsolationLevel' });
|
|
107
|
+
} finally {
|
|
108
|
+
executingScripts--;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
80
112
|
async function handleExecuteControlCommand({ command }) {
|
|
81
113
|
lastActivity = new Date().getTime();
|
|
82
114
|
|
|
@@ -210,6 +242,7 @@ const messageHandlers = {
|
|
|
210
242
|
connect: handleConnect,
|
|
211
243
|
executeQuery: handleExecuteQuery,
|
|
212
244
|
executeControlCommand: handleExecuteControlCommand,
|
|
245
|
+
setIsolationLevel: handleSetIsolationLevel,
|
|
213
246
|
executeReader: handleExecuteReader,
|
|
214
247
|
startProfiler: handleStartProfiler,
|
|
215
248
|
stopProfiler: handleStopProfiler,
|
package/src/storageModel.js
CHANGED
|
@@ -698,6 +698,30 @@ module.exports = {
|
|
|
698
698
|
"columnName": "id_original",
|
|
699
699
|
"dataType": "varchar(250)",
|
|
700
700
|
"notNull": false
|
|
701
|
+
},
|
|
702
|
+
{
|
|
703
|
+
"pureName": "connections",
|
|
704
|
+
"columnName": "httpProxyUrl",
|
|
705
|
+
"dataType": "varchar(250)",
|
|
706
|
+
"notNull": false
|
|
707
|
+
},
|
|
708
|
+
{
|
|
709
|
+
"pureName": "connections",
|
|
710
|
+
"columnName": "httpProxyUser",
|
|
711
|
+
"dataType": "varchar(250)",
|
|
712
|
+
"notNull": false
|
|
713
|
+
},
|
|
714
|
+
{
|
|
715
|
+
"pureName": "connections",
|
|
716
|
+
"columnName": "httpProxyPassword",
|
|
717
|
+
"dataType": "varchar(250)",
|
|
718
|
+
"notNull": false
|
|
719
|
+
},
|
|
720
|
+
{
|
|
721
|
+
"pureName": "connections",
|
|
722
|
+
"columnName": "defaultIsolationLevel",
|
|
723
|
+
"dataType": "varchar(250)",
|
|
724
|
+
"notNull": false
|
|
701
725
|
}
|
|
702
726
|
],
|
|
703
727
|
"foreignKeys": [
|
|
@@ -132,7 +132,35 @@ async function connectUtility(driver, storedConnection, connectionMode, addition
|
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
connection.ssl = await extractConnectionSslParams(connection);
|
|
135
|
-
|
|
135
|
+
|
|
136
|
+
const proxyUrl = String(connection.httpProxyUrl ?? '').trim();
|
|
137
|
+
const proxyUser = String(connection.httpProxyUser ?? '').trim();
|
|
138
|
+
const proxyPassword = String(connection.httpProxyPassword ?? '').trim();
|
|
139
|
+
if (!proxyUrl && (proxyUser || proxyPassword)) {
|
|
140
|
+
throw new Error('DBGM-00329 Proxy user or password is set but proxy URL is missing');
|
|
141
|
+
}
|
|
142
|
+
if (proxyUrl) {
|
|
143
|
+
let parsedProxy;
|
|
144
|
+
try {
|
|
145
|
+
const parsed = new URL(proxyUrl.includes('://') ? proxyUrl : `http://${proxyUrl}`);
|
|
146
|
+
parsedProxy = {
|
|
147
|
+
protocol: parsed.protocol.replace(':', ''),
|
|
148
|
+
host: parsed.hostname,
|
|
149
|
+
port: parsed.port ? parseInt(parsed.port, 10) : (parsed.protocol === 'https:' ? 443 : 80),
|
|
150
|
+
};
|
|
151
|
+
const username = connection.httpProxyUser ?? parsed.username;
|
|
152
|
+
const rawPassword = connection.httpProxyPassword ?? parsed.password;
|
|
153
|
+
const password = decryptPasswordString(rawPassword);
|
|
154
|
+
if (username) {
|
|
155
|
+
parsedProxy.auth = { username, password: password ?? '' };
|
|
156
|
+
}
|
|
157
|
+
} catch (err) {
|
|
158
|
+
throw new Error(`DBGM-00334 Invalid proxy URL "${proxyUrl}": ${err && err.message ? err.message : err}`);
|
|
159
|
+
}
|
|
160
|
+
connection.axios = axios.default.create({ proxy: parsedProxy });
|
|
161
|
+
} else {
|
|
162
|
+
connection.axios = axios.default;
|
|
163
|
+
}
|
|
136
164
|
|
|
137
165
|
const conn = await driver.connect({ conid: connectionLoaded?._id, ...connection, ...additionalOptions });
|
|
138
166
|
return conn;
|
package/src/utility/crypting.js
CHANGED
|
@@ -101,7 +101,7 @@ function decryptObjectPasswordField(obj, field, encryptor = null) {
|
|
|
101
101
|
return obj;
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
const fieldsToEncrypt = ['password', 'sshPassword', 'sshKeyfilePassword', 'connectionDefinition'];
|
|
104
|
+
const fieldsToEncrypt = ['password', 'sshPassword', 'sshKeyfilePassword', 'connectionDefinition', 'httpProxyPassword'];
|
|
105
105
|
const additionalFieldsToMask = [
|
|
106
106
|
'databaseUrl',
|
|
107
107
|
'server',
|