dbgate-api-premium 6.3.2 → 6.3.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 +5 -5
- package/src/auth/storageAuthProvider.js +2 -1
- package/src/controllers/auth.js +3 -1
- package/src/controllers/databaseConnections.js +25 -1
- package/src/controllers/serverConnections.js +5 -0
- package/src/controllers/storage.js +291 -8
- package/src/controllers/storageDb.js +59 -1
- package/src/currentVersion.js +2 -2
- package/src/main.js +7 -1
- package/src/utility/DatastoreProxy.js +4 -0
- package/src/utility/connectUtility.js +3 -1
- package/src/utility/crypting.js +85 -21
- package/src/utility/getMapExport.js +2 -0
- package/src/utility/healthStatus.js +12 -1
- package/src/utility/processArgs.js +5 -0
- package/src/utility/sshTunnel.js +13 -2
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.3.
|
|
4
|
+
"version": "6.3.3",
|
|
5
5
|
"homepage": "https://dbgate.org/",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -29,10 +29,10 @@
|
|
|
29
29
|
"compare-versions": "^3.6.0",
|
|
30
30
|
"cors": "^2.8.5",
|
|
31
31
|
"cross-env": "^6.0.3",
|
|
32
|
-
"dbgate-datalib": "^6.3.
|
|
32
|
+
"dbgate-datalib": "^6.3.3",
|
|
33
33
|
"dbgate-query-splitter": "^4.11.3",
|
|
34
|
-
"dbgate-sqltree": "^6.3.
|
|
35
|
-
"dbgate-tools": "^6.3.
|
|
34
|
+
"dbgate-sqltree": "^6.3.3",
|
|
35
|
+
"dbgate-tools": "^6.3.3",
|
|
36
36
|
"debug": "^4.3.4",
|
|
37
37
|
"diff": "^5.0.0",
|
|
38
38
|
"diff2html": "^3.4.13",
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
"devDependencies": {
|
|
84
84
|
"@types/fs-extra": "^9.0.11",
|
|
85
85
|
"@types/lodash": "^4.14.149",
|
|
86
|
-
"dbgate-types": "^6.3.
|
|
86
|
+
"dbgate-types": "^6.3.3",
|
|
87
87
|
"env-cmd": "^10.1.0",
|
|
88
88
|
"jsdoc-to-markdown": "^9.0.5",
|
|
89
89
|
"node-loader": "^1.0.2",
|
|
@@ -20,6 +20,7 @@ const logger = getLogger('storageAuthProvider');
|
|
|
20
20
|
const axios = require('axios');
|
|
21
21
|
const _ = require('lodash');
|
|
22
22
|
const { authProxyGetTokenFromCode, authProxyGetRedirectUrl } = require('../utility/authProxy');
|
|
23
|
+
const { decryptUser } = require('../utility/crypting');
|
|
23
24
|
|
|
24
25
|
async function loadPermissionsForUserId(userId) {
|
|
25
26
|
const rolePermissions = sortPermissionsFromTheSameLevel(await storageReadUserRolePermissions(userId));
|
|
@@ -77,7 +78,7 @@ class LocalAuthProvider extends StorageProviderBase {
|
|
|
77
78
|
if (rows.length == 0) {
|
|
78
79
|
return { error: 'Login not allowed' };
|
|
79
80
|
}
|
|
80
|
-
const row = rows[0];
|
|
81
|
+
const row = decryptUser(rows[0]);
|
|
81
82
|
if (row.password == password) {
|
|
82
83
|
const userId = row.id;
|
|
83
84
|
const permissions = await loadPermissionsForUserId(userId);
|
package/src/controllers/auth.js
CHANGED
|
@@ -12,6 +12,7 @@ const {
|
|
|
12
12
|
getAuthProviderById,
|
|
13
13
|
} = require('../auth/authProvider');
|
|
14
14
|
const storage = require('./storage');
|
|
15
|
+
const { decryptPasswordString } = require('../utility/crypting');
|
|
15
16
|
|
|
16
17
|
const logger = getLogger('auth');
|
|
17
18
|
|
|
@@ -44,6 +45,7 @@ function authMiddleware(req, res, next) {
|
|
|
44
45
|
'/connections/dblogin-auth',
|
|
45
46
|
'/connections/dblogin-auth-token',
|
|
46
47
|
'/health',
|
|
48
|
+
'/__health',
|
|
47
49
|
];
|
|
48
50
|
|
|
49
51
|
// console.log('********************* getAuthProvider()', getAuthProvider());
|
|
@@ -95,7 +97,7 @@ module.exports = {
|
|
|
95
97
|
let adminPassword = process.env.ADMIN_PASSWORD;
|
|
96
98
|
if (!adminPassword) {
|
|
97
99
|
const adminConfig = await storage.readConfig({ group: 'admin' });
|
|
98
|
-
adminPassword = adminConfig?.adminPassword;
|
|
100
|
+
adminPassword = decryptPasswordString(adminConfig?.adminPassword);
|
|
99
101
|
}
|
|
100
102
|
if (adminPassword && adminPassword == password) {
|
|
101
103
|
return {
|
|
@@ -37,6 +37,8 @@ const loadModelTransform = require('../utility/loadModelTransform');
|
|
|
37
37
|
const exportDbModelSql = require('../utility/exportDbModelSql');
|
|
38
38
|
const axios = require('axios');
|
|
39
39
|
const { callTextToSqlApi, callCompleteOnCursorApi, callRefactorSqlQueryApi } = require('../utility/authProxy');
|
|
40
|
+
const { decryptConnection } = require('../utility/crypting');
|
|
41
|
+
const { getSshTunnel } = require('../utility/sshTunnel');
|
|
40
42
|
|
|
41
43
|
const logger = getLogger('databaseConnections');
|
|
42
44
|
|
|
@@ -140,6 +142,11 @@ module.exports = {
|
|
|
140
142
|
if (newOpened.disconnected) return;
|
|
141
143
|
this.close(conid, database, false);
|
|
142
144
|
});
|
|
145
|
+
subprocess.on('error', err => {
|
|
146
|
+
logger.error(extractErrorLogData(err), 'Error in database connection subprocess');
|
|
147
|
+
if (newOpened.disconnected) return;
|
|
148
|
+
this.close(conid, database, false);
|
|
149
|
+
});
|
|
143
150
|
|
|
144
151
|
subprocess.send({
|
|
145
152
|
msgtype: 'connect',
|
|
@@ -619,9 +626,26 @@ module.exports = {
|
|
|
619
626
|
command,
|
|
620
627
|
{ conid, database, outputFile, inputFile, options, selectedTables, skippedTables, argsFormat }
|
|
621
628
|
) {
|
|
622
|
-
const
|
|
629
|
+
const sourceConnection = await connections.getCore({ conid });
|
|
630
|
+
const connection = {
|
|
631
|
+
...decryptConnection(sourceConnection),
|
|
632
|
+
};
|
|
623
633
|
const driver = requireEngineDriver(connection);
|
|
624
634
|
|
|
635
|
+
if (!connection.port && driver.defaultPort) {
|
|
636
|
+
connection.port = driver.defaultPort.toString();
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
if (connection.useSshTunnel) {
|
|
640
|
+
const tunnel = await getSshTunnel(connection);
|
|
641
|
+
if (tunnel.state == 'error') {
|
|
642
|
+
throw new Error(tunnel.message);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
connection.server = tunnel.localHost;
|
|
646
|
+
connection.port = tunnel.localPort;
|
|
647
|
+
}
|
|
648
|
+
|
|
625
649
|
const settingsValue = await config.getSettings();
|
|
626
650
|
|
|
627
651
|
const externalTools = {};
|
|
@@ -98,6 +98,11 @@ module.exports = {
|
|
|
98
98
|
if (newOpened.disconnected) return;
|
|
99
99
|
this.close(conid, false);
|
|
100
100
|
});
|
|
101
|
+
subprocess.on('error', err => {
|
|
102
|
+
logger.error(extractErrorLogData(err), 'Error in server connection subprocess');
|
|
103
|
+
if (newOpened.disconnected) return;
|
|
104
|
+
this.close(conid, false);
|
|
105
|
+
});
|
|
101
106
|
subprocess.send({ msgtype: 'connect', ...connection, globalSettings: await config.getSettings() });
|
|
102
107
|
return newOpened;
|
|
103
108
|
});
|
|
@@ -12,15 +12,30 @@ const {
|
|
|
12
12
|
storageReadConfig,
|
|
13
13
|
storageWriteConfig,
|
|
14
14
|
getStorageConnectionError,
|
|
15
|
+
storageSaveRelationDiff,
|
|
16
|
+
selectStorageIdentity,
|
|
15
17
|
} = require('./storageDb');
|
|
16
18
|
const { hasPermission } = require('../utility/hasPermission');
|
|
17
19
|
const { changeSetToSql, removeSchemaFromChangeSet } = require('dbgate-datalib');
|
|
18
20
|
const storageModel = require('../storageModel');
|
|
19
21
|
const { dumpSqlCommand } = require('dbgate-sqltree');
|
|
20
|
-
const {
|
|
22
|
+
const {
|
|
23
|
+
runCommandOnDriver,
|
|
24
|
+
getLogger,
|
|
25
|
+
runQueryFmt,
|
|
26
|
+
getPredefinedPermissions,
|
|
27
|
+
runQueryOnDriver,
|
|
28
|
+
} = require('dbgate-tools');
|
|
21
29
|
const socket = require('../utility/socket');
|
|
22
30
|
const { obtainRefreshedLicense } = require('../utility/authProxy');
|
|
23
31
|
const { datadir } = require('../utility/directories');
|
|
32
|
+
const {
|
|
33
|
+
loadEncryptionKeyFromExternal,
|
|
34
|
+
encryptUser,
|
|
35
|
+
encryptConnection,
|
|
36
|
+
encryptPasswordString,
|
|
37
|
+
} = require('../utility/crypting');
|
|
38
|
+
const crypto = require('crypto');
|
|
24
39
|
|
|
25
40
|
const logger = getLogger('storage');
|
|
26
41
|
|
|
@@ -31,7 +46,6 @@ function mapConnection(connnection) {
|
|
|
31
46
|
};
|
|
32
47
|
}
|
|
33
48
|
|
|
34
|
-
|
|
35
49
|
let refreshLicenseStarted = false;
|
|
36
50
|
|
|
37
51
|
module.exports = {
|
|
@@ -48,6 +62,22 @@ module.exports = {
|
|
|
48
62
|
}
|
|
49
63
|
|
|
50
64
|
setAuthProviders(providers, providers[defIndex]);
|
|
65
|
+
|
|
66
|
+
const keyResult = await storageSelectFmt(
|
|
67
|
+
"select ~value from ~config where ~group = 'admin' and ~key = 'encryptionKey'"
|
|
68
|
+
);
|
|
69
|
+
const [conn, driver] = await getStorageConnection();
|
|
70
|
+
|
|
71
|
+
await loadEncryptionKeyFromExternal(keyResult[0]?.value, async key => {
|
|
72
|
+
await runQueryFmt(
|
|
73
|
+
driver,
|
|
74
|
+
conn,
|
|
75
|
+
'insert into ~config (~group, ~key, ~value) values (%v, %v, %v)',
|
|
76
|
+
'admin',
|
|
77
|
+
'encryptionKey',
|
|
78
|
+
key
|
|
79
|
+
);
|
|
80
|
+
});
|
|
51
81
|
},
|
|
52
82
|
|
|
53
83
|
async startRefreshLicense() {
|
|
@@ -135,7 +165,7 @@ module.exports = {
|
|
|
135
165
|
displayName: 'Internal storage',
|
|
136
166
|
defaultDatabase: process.env.STORAGE_DATABASE,
|
|
137
167
|
singleDatabase: true,
|
|
138
|
-
...await getDbConnectionParams(),
|
|
168
|
+
...(await getDbConnectionParams()),
|
|
139
169
|
};
|
|
140
170
|
}
|
|
141
171
|
|
|
@@ -183,8 +213,8 @@ module.exports = {
|
|
|
183
213
|
return null;
|
|
184
214
|
}
|
|
185
215
|
|
|
186
|
-
await runQueryFmt(driver, conn, 'delete from auth_methods_config');
|
|
187
|
-
await runQueryFmt(driver, conn, 'delete from auth_methods where id > 0');
|
|
216
|
+
await runQueryFmt(driver, conn, 'delete from ~auth_methods_config');
|
|
217
|
+
await runQueryFmt(driver, conn, 'delete from ~auth_methods where ~id > 0');
|
|
188
218
|
|
|
189
219
|
let id = 1;
|
|
190
220
|
for (const method of authMethods) {
|
|
@@ -192,7 +222,7 @@ module.exports = {
|
|
|
192
222
|
await runQueryFmt(
|
|
193
223
|
driver,
|
|
194
224
|
conn,
|
|
195
|
-
'update auth_methods set name=%v, is_disabled=%v, is_default = %v, is_collapsed = %v where id = %v',
|
|
225
|
+
'update ~auth_methods set ~name=%v, ~is_disabled=%v, ~is_default = %v, ~is_collapsed = %v where ~id = %v',
|
|
196
226
|
method.name,
|
|
197
227
|
method.isDisabled,
|
|
198
228
|
method.isDefault,
|
|
@@ -228,7 +258,7 @@ module.exports = {
|
|
|
228
258
|
await runQueryFmt(
|
|
229
259
|
driver,
|
|
230
260
|
conn,
|
|
231
|
-
'insert into auth_methods_config (%i, %i, %i) values (%v, %v, %v)',
|
|
261
|
+
'insert into ~auth_methods_config (%i, %i, %i) values (%v, %v, %v)',
|
|
232
262
|
'auth_method_id',
|
|
233
263
|
'key',
|
|
234
264
|
'value',
|
|
@@ -400,7 +430,7 @@ module.exports = {
|
|
|
400
430
|
driver,
|
|
401
431
|
conn,
|
|
402
432
|
"insert into ~config (~group, ~key, ~value) values ('admin', 'adminPassword', %v)",
|
|
403
|
-
newPassword
|
|
433
|
+
encryptPasswordString(newPassword)
|
|
404
434
|
);
|
|
405
435
|
|
|
406
436
|
return { status: 'ok' };
|
|
@@ -409,4 +439,257 @@ module.exports = {
|
|
|
409
439
|
getStorageConnectionError() {
|
|
410
440
|
return getStorageConnectionError();
|
|
411
441
|
},
|
|
442
|
+
|
|
443
|
+
getUserList_meta: true,
|
|
444
|
+
async getUserList() {
|
|
445
|
+
const resp = await storageSelectFmt(`select ~users.~id,~users.~login,~users.~password,~users.~email from ~users`);
|
|
446
|
+
const usedRoles = await storageSelectFmt(`select ~roles.~name, ~user_roles.~user_id from
|
|
447
|
+
~user_roles inner join ~roles ON ~user_roles.~role_id = ~roles.~id`);
|
|
448
|
+
const usedPermissions = await storageSelectFmt(`select * from ~user_permissions`);
|
|
449
|
+
return resp.map(x => ({
|
|
450
|
+
...x,
|
|
451
|
+
password: x.password?.startsWith('crypt:') ? 'crypted' : x.password ? 'plain' : 'no',
|
|
452
|
+
usedRoles: usedRoles.filter(y => y.user_id == x.id).map(y => y.name),
|
|
453
|
+
usedPermissions: usedPermissions.filter(y => y.user_id == x.id).map(y => y.permission),
|
|
454
|
+
}));
|
|
455
|
+
},
|
|
456
|
+
|
|
457
|
+
getUserDetail_meta: true,
|
|
458
|
+
async getUserDetail({ id }) {
|
|
459
|
+
const resp =
|
|
460
|
+
id == 'new'
|
|
461
|
+
? {}
|
|
462
|
+
: await storageSelectFmt(
|
|
463
|
+
`select ~users.~id,~users.~login,~users.password, ~users.~email from ~users where ~users.~id = %v`,
|
|
464
|
+
id
|
|
465
|
+
);
|
|
466
|
+
|
|
467
|
+
const rolePermissions = id == 'new' ? [] : await storageReadUserRolePermissions(id);
|
|
468
|
+
const basePermissions = [...getPredefinedPermissions('logged-user'), ...rolePermissions];
|
|
469
|
+
const permissions =
|
|
470
|
+
id == 'new'
|
|
471
|
+
? []
|
|
472
|
+
: (await storageSelectFmt('select * from ~user_permissions where ~user_id = %v', id)).map(x => x.permission);
|
|
473
|
+
const allRoles = await storageSelectFmt('select * from ~roles');
|
|
474
|
+
const usedRoles =
|
|
475
|
+
id == 'new'
|
|
476
|
+
? []
|
|
477
|
+
: (
|
|
478
|
+
await storageSelectFmt(`select ~user_roles.~role_id from ~user_roles where ~user_roles.~user_id = %v`, id)
|
|
479
|
+
).map(x => x.role_id);
|
|
480
|
+
const allConnections = await storageSelectFmt('select * from ~connections');
|
|
481
|
+
const usedConnections =
|
|
482
|
+
id == 'new'
|
|
483
|
+
? []
|
|
484
|
+
: (await storageSelectFmt('select ~connection_id from ~user_connections where ~user_id = %v', id)).map(
|
|
485
|
+
x => x.connection_id
|
|
486
|
+
);
|
|
487
|
+
|
|
488
|
+
return {
|
|
489
|
+
...resp[0],
|
|
490
|
+
basePermissions,
|
|
491
|
+
permissions,
|
|
492
|
+
allRoles,
|
|
493
|
+
usedRoles,
|
|
494
|
+
allConnections,
|
|
495
|
+
usedConnections,
|
|
496
|
+
encryptPassword: !!(resp[0]?.password?.startsWith('crypt:') || id == 'new'),
|
|
497
|
+
};
|
|
498
|
+
},
|
|
499
|
+
|
|
500
|
+
saveUserDetail_meta: true,
|
|
501
|
+
async saveUserDetail(user) {
|
|
502
|
+
const [conn, driver] = await getStorageConnection();
|
|
503
|
+
|
|
504
|
+
const { login, password, email, usedRoles, usedConnections, permissions } = encryptUser(user);
|
|
505
|
+
let id = user.id;
|
|
506
|
+
|
|
507
|
+
if (id == null) {
|
|
508
|
+
await runQueryFmt(
|
|
509
|
+
driver,
|
|
510
|
+
conn,
|
|
511
|
+
'insert into ~users (~login, ~password, ~email) values (%v, %v, %v)',
|
|
512
|
+
login,
|
|
513
|
+
password,
|
|
514
|
+
email
|
|
515
|
+
);
|
|
516
|
+
id = await selectStorageIdentity('users');
|
|
517
|
+
} else {
|
|
518
|
+
await runQueryFmt(
|
|
519
|
+
driver,
|
|
520
|
+
conn,
|
|
521
|
+
'update ~users set ~login=%v, ~password=%v, ~email=%v where ~id = %v',
|
|
522
|
+
login,
|
|
523
|
+
password,
|
|
524
|
+
email,
|
|
525
|
+
id
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
await storageSaveRelationDiff('user_permissions', 'user_id', 'permission', id, permissions);
|
|
530
|
+
await storageSaveRelationDiff('user_roles', 'user_id', 'role_id', id, usedRoles);
|
|
531
|
+
await storageSaveRelationDiff('user_connections', 'user_id', 'connection_id', id, usedConnections);
|
|
532
|
+
|
|
533
|
+
return true;
|
|
534
|
+
},
|
|
535
|
+
|
|
536
|
+
deleteUser_meta: true,
|
|
537
|
+
async deleteUser({ id }) {
|
|
538
|
+
const [conn, driver] = await getStorageConnection();
|
|
539
|
+
|
|
540
|
+
await runQueryFmt(driver, conn, 'delete from ~users where ~id = %v', id);
|
|
541
|
+
|
|
542
|
+
return true;
|
|
543
|
+
},
|
|
544
|
+
|
|
545
|
+
getConnectionList_meta: true,
|
|
546
|
+
async getConnectionList() {
|
|
547
|
+
const resp = await storageSelectFmt(
|
|
548
|
+
`select ~connections.~id,~connections.~engine,~connections.~displayName,~connections.~server,~connections.~user from ~connections`
|
|
549
|
+
);
|
|
550
|
+
return resp;
|
|
551
|
+
},
|
|
552
|
+
|
|
553
|
+
getConnectionDetail_meta: true,
|
|
554
|
+
async getConnectionDetail({ id }) {
|
|
555
|
+
if (id == 'new') {
|
|
556
|
+
return {
|
|
557
|
+
conid: crypto.randomUUID(),
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
const resp = await storageSelectFmt(`select * from ~connections where ~id = %v`, id);
|
|
562
|
+
return resp[0];
|
|
563
|
+
},
|
|
564
|
+
|
|
565
|
+
saveConnectionDetail_meta: true,
|
|
566
|
+
async saveConnectionDetail(savedConnection) {
|
|
567
|
+
const [conn, driver] = await getStorageConnection();
|
|
568
|
+
|
|
569
|
+
const connection = encryptConnection(savedConnection);
|
|
570
|
+
|
|
571
|
+
const tableConnections = storageModel.tables.find(x => x.pureName == 'connections');
|
|
572
|
+
const id = connection.id;
|
|
573
|
+
|
|
574
|
+
const usedColumns = _.difference(
|
|
575
|
+
_.intersection(
|
|
576
|
+
tableConnections.columns.map(x => x.columnName),
|
|
577
|
+
Object.keys(connection)
|
|
578
|
+
),
|
|
579
|
+
['id']
|
|
580
|
+
);
|
|
581
|
+
|
|
582
|
+
if (id) {
|
|
583
|
+
await runQueryOnDriver(conn, driver, dmp => {
|
|
584
|
+
dmp.put(`update ~connections set`);
|
|
585
|
+
dmp.putCollection(',', usedColumns, col => {
|
|
586
|
+
dmp.put(`%i = %v`, col, connection[col]);
|
|
587
|
+
});
|
|
588
|
+
dmp.put(` where ~id = %v`, id);
|
|
589
|
+
});
|
|
590
|
+
} else {
|
|
591
|
+
await runQueryFmt(
|
|
592
|
+
driver,
|
|
593
|
+
conn,
|
|
594
|
+
`insert into ~connections (%,i) values (%,v)`,
|
|
595
|
+
usedColumns,
|
|
596
|
+
usedColumns.map(x => connection[x])
|
|
597
|
+
);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
return true;
|
|
601
|
+
},
|
|
602
|
+
|
|
603
|
+
deleteConnection_meta: true,
|
|
604
|
+
async deleteConnection({ id }) {
|
|
605
|
+
const [conn, driver] = await getStorageConnection();
|
|
606
|
+
if (!conn) {
|
|
607
|
+
return null;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
await runQueryFmt(driver, conn, 'delete from ~connections where ~id = %v', id);
|
|
611
|
+
|
|
612
|
+
return true;
|
|
613
|
+
},
|
|
614
|
+
|
|
615
|
+
getRoleList_meta: true,
|
|
616
|
+
async getRoleList() {
|
|
617
|
+
const resp = await storageSelectFmt(`select ~roles.~id,~roles.~name from ~roles`);
|
|
618
|
+
const usedPermissions = await storageSelectFmt(`select * from ~role_permissions`);
|
|
619
|
+
return resp.map(x => ({
|
|
620
|
+
...x,
|
|
621
|
+
usedPermissions: usedPermissions.filter(y => y.role_id == x.id).map(y => y.permission),
|
|
622
|
+
}));
|
|
623
|
+
},
|
|
624
|
+
|
|
625
|
+
getRoleDetail_meta: true,
|
|
626
|
+
async getRoleDetail({ id }) {
|
|
627
|
+
const resp =
|
|
628
|
+
id == 'new' ? {} : await storageSelectFmt(`select ~roles.~id,~roles.~name from ~roles where ~roles.~id = %v`, id);
|
|
629
|
+
|
|
630
|
+
const basePermissions = getPredefinedPermissions(resp?.name ?? 'logged-user');
|
|
631
|
+
const permissions =
|
|
632
|
+
id == 'new'
|
|
633
|
+
? []
|
|
634
|
+
: (await storageSelectFmt('select * from ~role_permissions where ~role_id = %v', id)).map(x => x.permission);
|
|
635
|
+
const allUsers = await storageSelectFmt('select * from ~users');
|
|
636
|
+
const usedUsers =
|
|
637
|
+
id == 'new'
|
|
638
|
+
? []
|
|
639
|
+
: (
|
|
640
|
+
await storageSelectFmt(`select ~user_roles.~user_id from ~user_roles where ~user_roles.~role_id = %v`, id)
|
|
641
|
+
).map(x => x.user_id);
|
|
642
|
+
const allConnections = await storageSelectFmt('select * from ~connections');
|
|
643
|
+
const usedConnections =
|
|
644
|
+
id == 'new'
|
|
645
|
+
? []
|
|
646
|
+
: (await storageSelectFmt('select ~connection_id from ~role_connections where ~role_id = %v', id)).map(
|
|
647
|
+
x => x.connection_id
|
|
648
|
+
);
|
|
649
|
+
|
|
650
|
+
return {
|
|
651
|
+
...resp[0],
|
|
652
|
+
basePermissions,
|
|
653
|
+
permissions,
|
|
654
|
+
allUsers,
|
|
655
|
+
usedUsers,
|
|
656
|
+
allConnections,
|
|
657
|
+
usedConnections,
|
|
658
|
+
};
|
|
659
|
+
},
|
|
660
|
+
|
|
661
|
+
deleteRole_meta: true,
|
|
662
|
+
async deleteRole({ id }) {
|
|
663
|
+
const [conn, driver] = await getStorageConnection();
|
|
664
|
+
if (!conn) {
|
|
665
|
+
return null;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
await runQueryFmt(driver, conn, 'delete from ~roles where ~id = %v', id);
|
|
669
|
+
|
|
670
|
+
return true;
|
|
671
|
+
},
|
|
672
|
+
|
|
673
|
+
saveRoleDetail_meta: true,
|
|
674
|
+
async saveRoleDetail(role) {
|
|
675
|
+
const [conn, driver] = await getStorageConnection();
|
|
676
|
+
|
|
677
|
+
const { name, usedConnections, usedUsers, permissions } = encryptUser(role);
|
|
678
|
+
let id = role.id;
|
|
679
|
+
|
|
680
|
+
if (id == null) {
|
|
681
|
+
await runQueryFmt(driver, conn, 'insert into ~roles (~name) values (%v)', name);
|
|
682
|
+
id = await selectStorageIdentity('roles');
|
|
683
|
+
} else {
|
|
684
|
+
if (id > 0) {
|
|
685
|
+
await runQueryFmt(driver, conn, 'update ~roles set ~name=%v where ~id = %v', name, id);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
await storageSaveRelationDiff('role_permissions', 'role_id', 'permission', id, permissions);
|
|
690
|
+
await storageSaveRelationDiff('user_roles', 'role_id', 'user_id', id, usedUsers);
|
|
691
|
+
await storageSaveRelationDiff('role_connections', 'role_id', 'connection_id', id, usedConnections);
|
|
692
|
+
|
|
693
|
+
return true;
|
|
694
|
+
},
|
|
412
695
|
};
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
const requireEngineDriver = require('../utility/requireEngineDriver');
|
|
2
2
|
const storageModel = require('../storageModel');
|
|
3
3
|
const dbgateApi = require('../shell');
|
|
4
|
-
const {
|
|
4
|
+
const {
|
|
5
|
+
getPredefinedPermissions,
|
|
6
|
+
getLogger,
|
|
7
|
+
extractErrorLogData,
|
|
8
|
+
runQueryFmt,
|
|
9
|
+
runQueryOnDriver,
|
|
10
|
+
} = require('dbgate-tools');
|
|
5
11
|
const _ = require('lodash');
|
|
6
12
|
const logger = getLogger('storageDb');
|
|
7
13
|
const { extractConnectionSslParams } = require('../utility/connectUtility');
|
|
@@ -238,10 +244,60 @@ async function storageWriteConfig(group, config) {
|
|
|
238
244
|
}
|
|
239
245
|
}
|
|
240
246
|
|
|
247
|
+
async function storageSaveRelationDiff(table, idColumn, valueColumn, idValue, newValueSet) {
|
|
248
|
+
const [conn, driver] = await getStorageConnection();
|
|
249
|
+
if (!conn) {
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const oldValueSet = await storageSelectFmt(`select %i from %i where %i = %v`, valueColumn, table, idColumn, idValue);
|
|
254
|
+
|
|
255
|
+
const oldValues = oldValueSet.map(x => x[valueColumn]);
|
|
256
|
+
|
|
257
|
+
for (const added of _.difference(newValueSet, oldValues)) {
|
|
258
|
+
await runQueryFmt(
|
|
259
|
+
driver,
|
|
260
|
+
conn,
|
|
261
|
+
'insert into %i (%i, %i) values (%v, %v)',
|
|
262
|
+
table,
|
|
263
|
+
idColumn,
|
|
264
|
+
valueColumn,
|
|
265
|
+
idValue,
|
|
266
|
+
added
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
for (const removed of _.difference(oldValues, newValueSet)) {
|
|
271
|
+
await runQueryFmt(
|
|
272
|
+
driver,
|
|
273
|
+
conn,
|
|
274
|
+
'delete from %i where %i = %v and %i = %v',
|
|
275
|
+
table,
|
|
276
|
+
idColumn,
|
|
277
|
+
idValue,
|
|
278
|
+
valueColumn,
|
|
279
|
+
removed
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
241
284
|
function getStorageConnectionError() {
|
|
242
285
|
return storageConnectionError;
|
|
243
286
|
}
|
|
244
287
|
|
|
288
|
+
async function selectStorageIdentity(tableName) {
|
|
289
|
+
const [conn, driver] = await getStorageConnection();
|
|
290
|
+
|
|
291
|
+
const resp = await runQueryOnDriver(conn, driver, dmp =>
|
|
292
|
+
dmp.selectScopeIdentity(
|
|
293
|
+
// @ts-ignore
|
|
294
|
+
{ pureName: tableName }
|
|
295
|
+
)
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
return Object.entries(resp.rows[0])[0][1];
|
|
299
|
+
}
|
|
300
|
+
|
|
245
301
|
module.exports = {
|
|
246
302
|
getStorageConnection,
|
|
247
303
|
storageSelectFmt,
|
|
@@ -254,4 +310,6 @@ module.exports = {
|
|
|
254
310
|
storageWriteConfig,
|
|
255
311
|
getStorageConnectionError,
|
|
256
312
|
storageSqlCommandFmt,
|
|
313
|
+
storageSaveRelationDiff,
|
|
314
|
+
selectStorageIdentity,
|
|
257
315
|
};
|
package/src/currentVersion.js
CHANGED
package/src/main.js
CHANGED
|
@@ -38,7 +38,7 @@ const { getLogger } = require('dbgate-tools');
|
|
|
38
38
|
const { getDefaultAuthProvider } = require('./auth/authProvider');
|
|
39
39
|
const startCloudUpgradeTimer = require('./utility/cloudUpgrade');
|
|
40
40
|
const { isProApp } = require('./utility/checkLicense');
|
|
41
|
-
const getHealthStatus = require('./utility/healthStatus');
|
|
41
|
+
const { getHealthStatus, getHealthStatusSprinx } = require('./utility/healthStatus');
|
|
42
42
|
|
|
43
43
|
const logger = getLogger('main');
|
|
44
44
|
|
|
@@ -124,6 +124,12 @@ function start() {
|
|
|
124
124
|
res.end(JSON.stringify(health, null, 2));
|
|
125
125
|
});
|
|
126
126
|
|
|
127
|
+
app.get(getExpressPath('/__health'), async function (req, res) {
|
|
128
|
+
res.setHeader('Content-Type', 'application/json');
|
|
129
|
+
const health = await getHealthStatusSprinx();
|
|
130
|
+
res.end(JSON.stringify(health, null, 2));
|
|
131
|
+
});
|
|
132
|
+
|
|
127
133
|
app.use(bodyParser.json({ limit: '50mb' }));
|
|
128
134
|
|
|
129
135
|
app.use(
|
|
@@ -60,6 +60,10 @@ class DatastoreProxy {
|
|
|
60
60
|
// if (this.disconnected) return;
|
|
61
61
|
this.subprocess = null;
|
|
62
62
|
});
|
|
63
|
+
this.subprocess.on('error', err => {
|
|
64
|
+
logger.error(extractErrorLogData(err), 'Error in data store subprocess');
|
|
65
|
+
this.subprocess = null;
|
|
66
|
+
});
|
|
63
67
|
this.subprocess.send({ msgtype: 'open', file: this.file });
|
|
64
68
|
}
|
|
65
69
|
return this.subprocess;
|
|
@@ -96,7 +96,9 @@ async function connectUtility(driver, storedConnection, connectionMode, addition
|
|
|
96
96
|
...decryptConnection(connectionLoaded),
|
|
97
97
|
};
|
|
98
98
|
|
|
99
|
-
if (!connection.port && driver.defaultPort)
|
|
99
|
+
if (!connection.port && driver.defaultPort) {
|
|
100
|
+
connection.port = driver.defaultPort.toString();
|
|
101
|
+
}
|
|
100
102
|
|
|
101
103
|
if (connection.useSshTunnel) {
|
|
102
104
|
const tunnel = await getSshTunnelProxy(connection);
|
package/src/utility/crypting.js
CHANGED
|
@@ -5,12 +5,16 @@ const path = require('path');
|
|
|
5
5
|
const _ = require('lodash');
|
|
6
6
|
|
|
7
7
|
const { datadir } = require('./directories');
|
|
8
|
+
const { encryptionKeyArg } = require('./processArgs');
|
|
8
9
|
|
|
9
10
|
const defaultEncryptionKey = 'mQAUaXhavRGJDxDTXSCg7Ej0xMmGCrx6OKA07DIMBiDcYYkvkaXjTAzPUEHEHEf9';
|
|
10
11
|
|
|
11
12
|
let _encryptionKey = null;
|
|
12
13
|
|
|
13
14
|
function loadEncryptionKey() {
|
|
15
|
+
if (encryptionKeyArg) {
|
|
16
|
+
return encryptionKeyArg;
|
|
17
|
+
}
|
|
14
18
|
if (_encryptionKey) {
|
|
15
19
|
return _encryptionKey;
|
|
16
20
|
}
|
|
@@ -33,6 +37,26 @@ function loadEncryptionKey() {
|
|
|
33
37
|
return _encryptionKey;
|
|
34
38
|
}
|
|
35
39
|
|
|
40
|
+
async function loadEncryptionKeyFromExternal(storedValue, setStoredValue) {
|
|
41
|
+
const encryptor = simpleEncryptor.createEncryptor(defaultEncryptionKey);
|
|
42
|
+
|
|
43
|
+
if (!storedValue) {
|
|
44
|
+
const generatedKey = crypto.randomBytes(32);
|
|
45
|
+
const newKey = generatedKey.toString('hex');
|
|
46
|
+
const result = {
|
|
47
|
+
encryptionKey: newKey,
|
|
48
|
+
};
|
|
49
|
+
await setStoredValue(encryptor.encrypt(result));
|
|
50
|
+
|
|
51
|
+
setEncryptionKey(newKey);
|
|
52
|
+
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const data = encryptor.decrypt(storedValue);
|
|
57
|
+
setEncryptionKey(data['encryptionKey']);
|
|
58
|
+
}
|
|
59
|
+
|
|
36
60
|
let _encryptor = null;
|
|
37
61
|
|
|
38
62
|
function getEncryptor() {
|
|
@@ -43,35 +67,46 @@ function getEncryptor() {
|
|
|
43
67
|
return _encryptor;
|
|
44
68
|
}
|
|
45
69
|
|
|
46
|
-
function
|
|
47
|
-
if (
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
70
|
+
function encryptPasswordString(password) {
|
|
71
|
+
if (password && !password.startsWith('crypt:')) {
|
|
72
|
+
return 'crypt:' + getEncryptor().encrypt(password);
|
|
73
|
+
}
|
|
74
|
+
return password;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function decryptPasswordString(password) {
|
|
78
|
+
if (password && password.startsWith('crypt:')) {
|
|
79
|
+
return getEncryptor().decrypt(password.substring('crypt:'.length));
|
|
80
|
+
}
|
|
81
|
+
return password;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function encryptObjectPasswordField(obj, field) {
|
|
85
|
+
if (obj && obj[field] && !obj[field].startsWith('crypt:')) {
|
|
53
86
|
return {
|
|
54
|
-
...
|
|
55
|
-
[field]: 'crypt:' + getEncryptor().encrypt(
|
|
87
|
+
...obj,
|
|
88
|
+
[field]: 'crypt:' + getEncryptor().encrypt(obj[field]),
|
|
56
89
|
};
|
|
57
90
|
}
|
|
58
|
-
return
|
|
91
|
+
return obj;
|
|
59
92
|
}
|
|
60
93
|
|
|
61
|
-
function
|
|
62
|
-
if (
|
|
94
|
+
function decryptObjectPasswordField(obj, field) {
|
|
95
|
+
if (obj && obj[field] && obj[field].startsWith('crypt:')) {
|
|
63
96
|
return {
|
|
64
|
-
...
|
|
65
|
-
[field]: getEncryptor().decrypt(
|
|
97
|
+
...obj,
|
|
98
|
+
[field]: getEncryptor().decrypt(obj[field].substring('crypt:'.length)),
|
|
66
99
|
};
|
|
67
100
|
}
|
|
68
|
-
return
|
|
101
|
+
return obj;
|
|
69
102
|
}
|
|
70
103
|
|
|
71
104
|
function encryptConnection(connection) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
105
|
+
if (connection.passwordMode != 'saveRaw') {
|
|
106
|
+
connection = encryptObjectPasswordField(connection, 'password');
|
|
107
|
+
connection = encryptObjectPasswordField(connection, 'sshPassword');
|
|
108
|
+
connection = encryptObjectPasswordField(connection, 'sshKeyfilePassword');
|
|
109
|
+
}
|
|
75
110
|
return connection;
|
|
76
111
|
}
|
|
77
112
|
|
|
@@ -81,12 +116,24 @@ function maskConnection(connection) {
|
|
|
81
116
|
}
|
|
82
117
|
|
|
83
118
|
function decryptConnection(connection) {
|
|
84
|
-
connection =
|
|
85
|
-
connection =
|
|
86
|
-
connection =
|
|
119
|
+
connection = decryptObjectPasswordField(connection, 'password');
|
|
120
|
+
connection = decryptObjectPasswordField(connection, 'sshPassword');
|
|
121
|
+
connection = decryptObjectPasswordField(connection, 'sshKeyfilePassword');
|
|
87
122
|
return connection;
|
|
88
123
|
}
|
|
89
124
|
|
|
125
|
+
function encryptUser(user) {
|
|
126
|
+
if (user.encryptPassword) {
|
|
127
|
+
user = encryptObjectPasswordField(user, 'password');
|
|
128
|
+
}
|
|
129
|
+
return user;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function decryptUser(user) {
|
|
133
|
+
user = decryptObjectPasswordField(user, 'password');
|
|
134
|
+
return user;
|
|
135
|
+
}
|
|
136
|
+
|
|
90
137
|
function pickSafeConnectionInfo(connection) {
|
|
91
138
|
if (process.env.LOG_CONNECTION_SENSITIVE_VALUES) {
|
|
92
139
|
return connection;
|
|
@@ -99,10 +146,27 @@ function pickSafeConnectionInfo(connection) {
|
|
|
99
146
|
});
|
|
100
147
|
}
|
|
101
148
|
|
|
149
|
+
function setEncryptionKey(encryptionKey) {
|
|
150
|
+
_encryptionKey = encryptionKey;
|
|
151
|
+
_encryptor = null;
|
|
152
|
+
global.ENCRYPTION_KEY = encryptionKey;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function getEncryptionKey() {
|
|
156
|
+
return _encryptionKey;
|
|
157
|
+
}
|
|
158
|
+
|
|
102
159
|
module.exports = {
|
|
103
160
|
loadEncryptionKey,
|
|
104
161
|
encryptConnection,
|
|
162
|
+
encryptUser,
|
|
163
|
+
decryptUser,
|
|
105
164
|
decryptConnection,
|
|
106
165
|
maskConnection,
|
|
107
166
|
pickSafeConnectionInfo,
|
|
167
|
+
loadEncryptionKeyFromExternal,
|
|
168
|
+
getEncryptionKey,
|
|
169
|
+
setEncryptionKey,
|
|
170
|
+
encryptPasswordString,
|
|
171
|
+
decryptPasswordString,
|
|
108
172
|
};
|
|
@@ -24,4 +24,15 @@ async function getHealthStatus() {
|
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
async function getHealthStatusSprinx() {
|
|
28
|
+
return {
|
|
29
|
+
overallStatus: 'OK',
|
|
30
|
+
timeStamp: new Date().toISOString(),
|
|
31
|
+
timeStampUnix: Math.floor(Date.now() / 1000),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = {
|
|
36
|
+
getHealthStatus,
|
|
37
|
+
getHealthStatusSprinx,
|
|
38
|
+
};
|
|
@@ -17,6 +17,7 @@ const processDisplayName = getNamedArg('--process-display-name');
|
|
|
17
17
|
const listenApi = process.argv.includes('--listen-api');
|
|
18
18
|
const listenApiChild = process.argv.includes('--listen-api-child') || listenApi;
|
|
19
19
|
const runE2eTests = process.argv.includes('--run-e2e-tests');
|
|
20
|
+
const encryptionKeyArg = getNamedArg('--encryption-key');
|
|
20
21
|
|
|
21
22
|
function getPassArgs() {
|
|
22
23
|
const res = [];
|
|
@@ -31,6 +32,9 @@ function getPassArgs() {
|
|
|
31
32
|
if (runE2eTests) {
|
|
32
33
|
res.push('--run-e2e-tests');
|
|
33
34
|
}
|
|
35
|
+
if (global['ENCRYPTION_KEY']) {
|
|
36
|
+
res.push('--encryption-key', global['ENCRYPTION_KEY']);
|
|
37
|
+
}
|
|
34
38
|
return res;
|
|
35
39
|
}
|
|
36
40
|
|
|
@@ -45,4 +49,5 @@ module.exports = {
|
|
|
45
49
|
listenApiChild,
|
|
46
50
|
processDisplayName,
|
|
47
51
|
runE2eTests,
|
|
52
|
+
encryptionKeyArg,
|
|
48
53
|
};
|
package/src/utility/sshTunnel.js
CHANGED
|
@@ -57,10 +57,21 @@ function callForwardProcess(connection, tunnelConfig, tunnelCacheKey) {
|
|
|
57
57
|
}
|
|
58
58
|
});
|
|
59
59
|
subprocess.on('exit', code => {
|
|
60
|
-
logger.info(
|
|
60
|
+
logger.info(`SSH forward process exited with code ${code}`);
|
|
61
61
|
delete sshTunnelCache[tunnelCacheKey];
|
|
62
62
|
if (!promiseHandled) {
|
|
63
|
-
reject(
|
|
63
|
+
reject(
|
|
64
|
+
new Error(
|
|
65
|
+
'SSH forward process exited, try to change "Local host address for SSH connections" in Settings/Connections'
|
|
66
|
+
)
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
subprocess.on('error', error => {
|
|
71
|
+
logger.error(extractErrorLogData(error), 'SSH forward process error');
|
|
72
|
+
delete sshTunnelCache[tunnelCacheKey];
|
|
73
|
+
if (!promiseHandled) {
|
|
74
|
+
reject(error);
|
|
64
75
|
}
|
|
65
76
|
});
|
|
66
77
|
});
|