dbgate-api 6.5.4 → 6.5.6
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/authProvider.js +2 -2
- package/src/controllers/auth.js +89 -18
- package/src/controllers/cloud.js +18 -0
- package/src/controllers/config.js +57 -18
- package/src/controllers/connections.js +5 -5
- package/src/controllers/databaseConnections.js +102 -8
- package/src/controllers/files.js +2 -2
- package/src/controllers/runners.js +24 -1
- package/src/controllers/serverConnections.js +12 -0
- package/src/controllers/sessions.js +14 -1
- package/src/controllers/storage.js +5 -0
- package/src/currentVersion.js +2 -2
- package/src/shell/deployDb.js +1 -1
- package/src/shell/generateDeploySql.js +1 -1
- package/src/shell/importDbFromFolder.js +2 -5
- package/src/storageModel.js +250 -2
- package/src/utility/auditlog.js +9 -0
- package/src/utility/authProxy.js +7 -0
- package/src/utility/cloudIntf.js +46 -3
- package/src/utility/getChartExport.js +11 -3
- package/src/utility/getMapExport.js +1 -1
- package/src/utility/loginchecker.js +18 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dbgate-api",
|
|
3
3
|
"main": "src/index.js",
|
|
4
|
-
"version": "6.5.
|
|
4
|
+
"version": "6.5.6",
|
|
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.5.
|
|
33
|
+
"dbgate-datalib": "^6.5.6",
|
|
34
34
|
"dbgate-query-splitter": "^4.11.5",
|
|
35
|
-
"dbgate-sqltree": "^6.5.
|
|
36
|
-
"dbgate-tools": "^6.5.
|
|
35
|
+
"dbgate-sqltree": "^6.5.6",
|
|
36
|
+
"dbgate-tools": "^6.5.6",
|
|
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.5.
|
|
88
|
+
"dbgate-types": "^6.5.6",
|
|
89
89
|
"env-cmd": "^10.1.0",
|
|
90
90
|
"jsdoc-to-markdown": "^9.0.5",
|
|
91
91
|
"node-loader": "^1.0.2",
|
package/src/auth/authProvider.js
CHANGED
|
@@ -11,7 +11,7 @@ const logger = getLogger('authProvider');
|
|
|
11
11
|
class AuthProviderBase {
|
|
12
12
|
amoid = 'none';
|
|
13
13
|
|
|
14
|
-
async login(login, password, options = undefined) {
|
|
14
|
+
async login(login, password, options = undefined, req = undefined) {
|
|
15
15
|
return {
|
|
16
16
|
accessToken: jwt.sign(
|
|
17
17
|
{
|
|
@@ -23,7 +23,7 @@ class AuthProviderBase {
|
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
oauthToken(params) {
|
|
26
|
+
oauthToken(params, req) {
|
|
27
27
|
return {};
|
|
28
28
|
}
|
|
29
29
|
|
package/src/controllers/auth.js
CHANGED
|
@@ -13,8 +13,21 @@ const {
|
|
|
13
13
|
} = require('../auth/authProvider');
|
|
14
14
|
const storage = require('./storage');
|
|
15
15
|
const { decryptPasswordString } = require('../utility/crypting');
|
|
16
|
-
const {
|
|
16
|
+
const {
|
|
17
|
+
createDbGateIdentitySession,
|
|
18
|
+
startCloudTokenChecking,
|
|
19
|
+
readCloudTokenHolder,
|
|
20
|
+
readCloudTestTokenHolder,
|
|
21
|
+
} = require('../utility/cloudIntf');
|
|
17
22
|
const socket = require('../utility/socket');
|
|
23
|
+
const { sendToAuditLog } = require('../utility/auditlog');
|
|
24
|
+
const {
|
|
25
|
+
isLoginLicensed,
|
|
26
|
+
LOGIN_LIMIT_ERROR,
|
|
27
|
+
markTokenAsLoggedIn,
|
|
28
|
+
markUserAsActive,
|
|
29
|
+
markLoginAsLoggedOut,
|
|
30
|
+
} = require('../utility/loginchecker');
|
|
18
31
|
|
|
19
32
|
const logger = getLogger('auth');
|
|
20
33
|
|
|
@@ -54,6 +67,11 @@ function authMiddleware(req, res, next) {
|
|
|
54
67
|
|
|
55
68
|
// const isAdminPage = req.headers['x-is-admin-page'] == 'true';
|
|
56
69
|
|
|
70
|
+
if (process.env.SKIP_ALL_AUTH) {
|
|
71
|
+
// API is not authorized for basic auth
|
|
72
|
+
return next();
|
|
73
|
+
}
|
|
74
|
+
|
|
57
75
|
if (process.env.BASIC_AUTH) {
|
|
58
76
|
// API is not authorized for basic auth
|
|
59
77
|
return next();
|
|
@@ -72,6 +90,8 @@ function authMiddleware(req, res, next) {
|
|
|
72
90
|
try {
|
|
73
91
|
const decoded = jwt.verify(token, getTokenSecret());
|
|
74
92
|
req.user = decoded;
|
|
93
|
+
markUserAsActive(decoded.licenseUid, token);
|
|
94
|
+
|
|
75
95
|
return next();
|
|
76
96
|
} catch (err) {
|
|
77
97
|
if (skipAuth) {
|
|
@@ -87,12 +107,12 @@ function authMiddleware(req, res, next) {
|
|
|
87
107
|
|
|
88
108
|
module.exports = {
|
|
89
109
|
oauthToken_meta: true,
|
|
90
|
-
async oauthToken(params) {
|
|
110
|
+
async oauthToken(params, req) {
|
|
91
111
|
const { amoid } = params;
|
|
92
|
-
return getAuthProviderById(amoid).oauthToken(params);
|
|
112
|
+
return getAuthProviderById(amoid).oauthToken(params, req);
|
|
93
113
|
},
|
|
94
114
|
login_meta: true,
|
|
95
|
-
async login(params) {
|
|
115
|
+
async login(params, req) {
|
|
96
116
|
const { amoid, login, password, isAdminPage } = params;
|
|
97
117
|
|
|
98
118
|
if (isAdminPage) {
|
|
@@ -102,25 +122,52 @@ module.exports = {
|
|
|
102
122
|
adminPassword = decryptPasswordString(adminConfig?.adminPassword);
|
|
103
123
|
}
|
|
104
124
|
if (adminPassword && adminPassword == password) {
|
|
125
|
+
if (!(await isLoginLicensed(req, `superadmin`))) {
|
|
126
|
+
return { error: LOGIN_LIMIT_ERROR };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
sendToAuditLog(req, {
|
|
130
|
+
category: 'auth',
|
|
131
|
+
component: 'AuthController',
|
|
132
|
+
action: 'login',
|
|
133
|
+
event: 'login.admin',
|
|
134
|
+
severity: 'info',
|
|
135
|
+
message: 'Administration login successful',
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const licenseUid = `superadmin`;
|
|
139
|
+
const accessToken = jwt.sign(
|
|
140
|
+
{
|
|
141
|
+
login: 'superadmin',
|
|
142
|
+
permissions: await storage.loadSuperadminPermissions(),
|
|
143
|
+
roleId: -3,
|
|
144
|
+
licenseUid,
|
|
145
|
+
},
|
|
146
|
+
getTokenSecret(),
|
|
147
|
+
{
|
|
148
|
+
expiresIn: getTokenLifetime(),
|
|
149
|
+
}
|
|
150
|
+
);
|
|
151
|
+
markTokenAsLoggedIn(licenseUid, accessToken);
|
|
152
|
+
|
|
105
153
|
return {
|
|
106
|
-
accessToken
|
|
107
|
-
{
|
|
108
|
-
login: 'superadmin',
|
|
109
|
-
permissions: await storage.loadSuperadminPermissions(),
|
|
110
|
-
roleId: -3,
|
|
111
|
-
},
|
|
112
|
-
getTokenSecret(),
|
|
113
|
-
{
|
|
114
|
-
expiresIn: getTokenLifetime(),
|
|
115
|
-
}
|
|
116
|
-
),
|
|
154
|
+
accessToken,
|
|
117
155
|
};
|
|
118
156
|
}
|
|
119
157
|
|
|
158
|
+
sendToAuditLog(req, {
|
|
159
|
+
category: 'auth',
|
|
160
|
+
component: 'AuthController',
|
|
161
|
+
action: 'loginFail',
|
|
162
|
+
event: 'login.adminFailed',
|
|
163
|
+
severity: 'warn',
|
|
164
|
+
message: 'Administraton login failed',
|
|
165
|
+
});
|
|
166
|
+
|
|
120
167
|
return { error: 'Login failed' };
|
|
121
168
|
}
|
|
122
169
|
|
|
123
|
-
return getAuthProviderById(amoid).login(login, password);
|
|
170
|
+
return getAuthProviderById(amoid).login(login, password, undefined, req);
|
|
124
171
|
},
|
|
125
172
|
|
|
126
173
|
getProviders_meta: true,
|
|
@@ -138,8 +185,8 @@ module.exports = {
|
|
|
138
185
|
},
|
|
139
186
|
|
|
140
187
|
createCloudLoginSession_meta: true,
|
|
141
|
-
async createCloudLoginSession({ client }) {
|
|
142
|
-
const res = await createDbGateIdentitySession(client);
|
|
188
|
+
async createCloudLoginSession({ client, redirectUri }) {
|
|
189
|
+
const res = await createDbGateIdentitySession(client, redirectUri);
|
|
143
190
|
startCloudTokenChecking(res.sid, tokenHolder => {
|
|
144
191
|
socket.emit('got-cloud-token', tokenHolder);
|
|
145
192
|
socket.emitChanged('cloud-content-changed');
|
|
@@ -148,5 +195,29 @@ module.exports = {
|
|
|
148
195
|
return res;
|
|
149
196
|
},
|
|
150
197
|
|
|
198
|
+
cloudLoginRedirected_meta: true,
|
|
199
|
+
async cloudLoginRedirected({ sid }) {
|
|
200
|
+
const tokenHolder = await readCloudTokenHolder(sid);
|
|
201
|
+
return tokenHolder;
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
cloudTestLogin_meta: true,
|
|
205
|
+
async cloudTestLogin({ email }) {
|
|
206
|
+
const tokenHolder = await readCloudTestTokenHolder(email);
|
|
207
|
+
return tokenHolder;
|
|
208
|
+
},
|
|
209
|
+
|
|
210
|
+
logoutAdmin_meta: true,
|
|
211
|
+
async logoutAdmin() {
|
|
212
|
+
await markLoginAsLoggedOut('superadmin');
|
|
213
|
+
return true;
|
|
214
|
+
},
|
|
215
|
+
|
|
216
|
+
logoutUser_meta: true,
|
|
217
|
+
async logoutUser({}, req) {
|
|
218
|
+
await markLoginAsLoggedOut(req?.user?.licenseUid);
|
|
219
|
+
return true;
|
|
220
|
+
},
|
|
221
|
+
|
|
151
222
|
authMiddleware,
|
|
152
223
|
};
|
package/src/controllers/cloud.js
CHANGED
|
@@ -258,4 +258,22 @@ module.exports = {
|
|
|
258
258
|
await fs.writeFile(filePath, content);
|
|
259
259
|
return true;
|
|
260
260
|
},
|
|
261
|
+
|
|
262
|
+
folderUsers_meta: true,
|
|
263
|
+
async folderUsers({ folid }) {
|
|
264
|
+
const resp = await callCloudApiGet(`content-folders/users/${folid}`);
|
|
265
|
+
return resp;
|
|
266
|
+
},
|
|
267
|
+
|
|
268
|
+
setFolderUserRole_meta: true,
|
|
269
|
+
async setFolderUserRole({ folid, email, role }) {
|
|
270
|
+
const resp = await callCloudApiPost(`content-folders/set-user-role/${folid}`, { email, role });
|
|
271
|
+
return resp;
|
|
272
|
+
},
|
|
273
|
+
|
|
274
|
+
removeFolderUser_meta: true,
|
|
275
|
+
async removeFolderUser({ folid, email }) {
|
|
276
|
+
const resp = await callCloudApiPost(`content-folders/remove-user/${folid}`, { email });
|
|
277
|
+
return resp;
|
|
278
|
+
},
|
|
261
279
|
};
|
|
@@ -16,7 +16,7 @@ const connections = require('../controllers/connections');
|
|
|
16
16
|
const { getAuthProviderFromReq } = require('../auth/authProvider');
|
|
17
17
|
const { checkLicense, checkLicenseKey } = require('../utility/checkLicense');
|
|
18
18
|
const storage = require('./storage');
|
|
19
|
-
const { getAuthProxyUrl } = require('../utility/authProxy');
|
|
19
|
+
const { getAuthProxyUrl, tryToGetRefreshedLicense } = require('../utility/authProxy');
|
|
20
20
|
const { getPublicHardwareFingerprint } = require('../utility/hardwareFingerprint');
|
|
21
21
|
const { extractErrorMessage } = require('dbgate-tools');
|
|
22
22
|
const {
|
|
@@ -29,6 +29,7 @@ const {
|
|
|
29
29
|
} = require('../utility/crypting');
|
|
30
30
|
|
|
31
31
|
const lock = new AsyncLock();
|
|
32
|
+
let cachedSettingsValue = null;
|
|
32
33
|
|
|
33
34
|
module.exports = {
|
|
34
35
|
// settingsValue: {},
|
|
@@ -108,6 +109,7 @@ module.exports = {
|
|
|
108
109
|
),
|
|
109
110
|
isAdminPasswordMissing,
|
|
110
111
|
isInvalidToken: req?.isInvalidToken,
|
|
112
|
+
skipAllAuth: !!process.env.SKIP_ALL_AUTH,
|
|
111
113
|
adminPasswordState: adminConfig?.adminPasswordState,
|
|
112
114
|
storageDatabase: process.env.STORAGE_DATABASE,
|
|
113
115
|
logsFilePath: getLogsFilePath(),
|
|
@@ -118,6 +120,7 @@ module.exports = {
|
|
|
118
120
|
supportCloudAutoUpgrade: !!process.env.CLOUD_UPGRADE_FILE,
|
|
119
121
|
allowPrivateCloud: platformInfo.isElectron || !!process.env.ALLOW_DBGATE_PRIVATE_CLOUD,
|
|
120
122
|
...currentVersion,
|
|
123
|
+
redirectToDbGateCloudLogin: !!process.env.REDIRECT_TO_DBGATE_CLOUD_LOGIN,
|
|
121
124
|
};
|
|
122
125
|
|
|
123
126
|
return configResult;
|
|
@@ -144,6 +147,13 @@ module.exports = {
|
|
|
144
147
|
return res;
|
|
145
148
|
},
|
|
146
149
|
|
|
150
|
+
async getCachedSettings() {
|
|
151
|
+
if (!cachedSettingsValue) {
|
|
152
|
+
cachedSettingsValue = await this.loadSettings();
|
|
153
|
+
}
|
|
154
|
+
return cachedSettingsValue;
|
|
155
|
+
},
|
|
156
|
+
|
|
147
157
|
deleteSettings_meta: true,
|
|
148
158
|
async deleteSettings() {
|
|
149
159
|
await fs.unlink(path.join(datadir(), processArgs.runE2eTests ? 'settings-e2etests.json' : 'settings.json'));
|
|
@@ -182,6 +192,7 @@ module.exports = {
|
|
|
182
192
|
return {
|
|
183
193
|
...this.fillMissingSettings(JSON.parse(settingsText)),
|
|
184
194
|
'other.licenseKey': platformInfo.isElectron ? await this.loadLicenseKey() : undefined,
|
|
195
|
+
// 'other.licenseKey': await this.loadLicenseKey(),
|
|
185
196
|
};
|
|
186
197
|
}
|
|
187
198
|
} catch (err) {
|
|
@@ -199,21 +210,34 @@ module.exports = {
|
|
|
199
210
|
},
|
|
200
211
|
|
|
201
212
|
saveLicenseKey_meta: true,
|
|
202
|
-
async saveLicenseKey({ licenseKey }) {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
213
|
+
async saveLicenseKey({ licenseKey, forceSave = false, tryToRenew = false }) {
|
|
214
|
+
if (!forceSave) {
|
|
215
|
+
const decoded = jwt.decode(licenseKey?.trim());
|
|
216
|
+
if (!decoded) {
|
|
217
|
+
return {
|
|
218
|
+
status: 'error',
|
|
219
|
+
errorMessage: 'Invalid license key',
|
|
220
|
+
};
|
|
221
|
+
}
|
|
210
222
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
223
|
+
const { exp } = decoded;
|
|
224
|
+
if (exp * 1000 < Date.now()) {
|
|
225
|
+
let renewed = false;
|
|
226
|
+
if (tryToRenew) {
|
|
227
|
+
const newLicenseKey = await tryToGetRefreshedLicense(licenseKey);
|
|
228
|
+
if (newLicenseKey.status == 'ok') {
|
|
229
|
+
licenseKey = newLicenseKey.token;
|
|
230
|
+
renewed = true;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (!renewed) {
|
|
235
|
+
return {
|
|
236
|
+
status: 'error',
|
|
237
|
+
errorMessage: 'License key is expired',
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
}
|
|
217
241
|
}
|
|
218
242
|
|
|
219
243
|
try {
|
|
@@ -257,6 +281,7 @@ module.exports = {
|
|
|
257
281
|
updateSettings_meta: true,
|
|
258
282
|
async updateSettings(values, req) {
|
|
259
283
|
if (!hasPermission(`settings/change`, req)) return false;
|
|
284
|
+
cachedSettingsValue = null;
|
|
260
285
|
|
|
261
286
|
const res = await lock.acquire('settings', async () => {
|
|
262
287
|
const currentValue = await this.loadSettings();
|
|
@@ -265,7 +290,11 @@ module.exports = {
|
|
|
265
290
|
if (process.env.STORAGE_DATABASE) {
|
|
266
291
|
updated = {
|
|
267
292
|
...currentValue,
|
|
268
|
-
...values,
|
|
293
|
+
..._.mapValues(values, v => {
|
|
294
|
+
if (v === true) return 'true';
|
|
295
|
+
if (v === false) return 'false';
|
|
296
|
+
return v;
|
|
297
|
+
}),
|
|
269
298
|
};
|
|
270
299
|
await storage.writeConfig({
|
|
271
300
|
group: 'settings',
|
|
@@ -283,7 +312,7 @@ module.exports = {
|
|
|
283
312
|
// this.settingsValue = updated;
|
|
284
313
|
|
|
285
314
|
if (currentValue['other.licenseKey'] != values['other.licenseKey']) {
|
|
286
|
-
await this.saveLicenseKey({ licenseKey: values['other.licenseKey'] });
|
|
315
|
+
await this.saveLicenseKey({ licenseKey: values['other.licenseKey'], forceSave: true });
|
|
287
316
|
socket.emitChanged(`config-changed`);
|
|
288
317
|
}
|
|
289
318
|
}
|
|
@@ -303,7 +332,7 @@ module.exports = {
|
|
|
303
332
|
const resp = await axios.default.get('https://raw.githubusercontent.com/dbgate/dbgate/master/CHANGELOG.md');
|
|
304
333
|
return resp.data;
|
|
305
334
|
} catch (err) {
|
|
306
|
-
return ''
|
|
335
|
+
return '';
|
|
307
336
|
}
|
|
308
337
|
},
|
|
309
338
|
|
|
@@ -313,6 +342,16 @@ module.exports = {
|
|
|
313
342
|
return resp;
|
|
314
343
|
},
|
|
315
344
|
|
|
345
|
+
getNewLicense_meta: true,
|
|
346
|
+
async getNewLicense({ oldLicenseKey }) {
|
|
347
|
+
const newLicenseKey = await tryToGetRefreshedLicense(oldLicenseKey);
|
|
348
|
+
const res = await checkLicenseKey(newLicenseKey.token);
|
|
349
|
+
if (res.status == 'ok') {
|
|
350
|
+
res.licenseKey = newLicenseKey.token;
|
|
351
|
+
}
|
|
352
|
+
return res;
|
|
353
|
+
},
|
|
354
|
+
|
|
316
355
|
recryptDatabaseForExport(db) {
|
|
317
356
|
const encryptionKey = generateTransportEncryptionKey();
|
|
318
357
|
const transportEncryptor = createTransportEncryptor(encryptionKey);
|
|
@@ -536,14 +536,14 @@ module.exports = {
|
|
|
536
536
|
},
|
|
537
537
|
|
|
538
538
|
dbloginAuthToken_meta: true,
|
|
539
|
-
async dbloginAuthToken({ amoid, code, conid, redirectUri, sid }) {
|
|
539
|
+
async dbloginAuthToken({ amoid, code, conid, redirectUri, sid }, req) {
|
|
540
540
|
try {
|
|
541
541
|
const connection = await this.getCore({ conid });
|
|
542
542
|
const driver = requireEngineDriver(connection);
|
|
543
543
|
const accessToken = await driver.getAuthTokenFromCode(connection, { code, redirectUri, sid });
|
|
544
544
|
const volatile = await this.saveVolatile({ conid, accessToken });
|
|
545
545
|
const authProvider = getAuthProviderById(amoid);
|
|
546
|
-
const resp = await authProvider.login(null, null, { conid: volatile._id });
|
|
546
|
+
const resp = await authProvider.login(null, null, { conid: volatile._id }, req);
|
|
547
547
|
return resp;
|
|
548
548
|
} catch (err) {
|
|
549
549
|
logger.error(extractErrorLogData(err), 'Error getting DB token');
|
|
@@ -552,18 +552,18 @@ module.exports = {
|
|
|
552
552
|
},
|
|
553
553
|
|
|
554
554
|
dbloginAuth_meta: true,
|
|
555
|
-
async dbloginAuth({ amoid, conid, user, password }) {
|
|
555
|
+
async dbloginAuth({ amoid, conid, user, password }, req) {
|
|
556
556
|
if (user || password) {
|
|
557
557
|
const saveResp = await this.saveVolatile({ conid, user, password, test: true });
|
|
558
558
|
if (saveResp.msgtype == 'connected') {
|
|
559
|
-
const loginResp = await getAuthProviderById(amoid).login(user, password, { conid: saveResp._id });
|
|
559
|
+
const loginResp = await getAuthProviderById(amoid).login(user, password, { conid: saveResp._id }, req);
|
|
560
560
|
return loginResp;
|
|
561
561
|
}
|
|
562
562
|
return saveResp;
|
|
563
563
|
}
|
|
564
564
|
|
|
565
565
|
// user and password is stored in connection, volatile connection is not needed
|
|
566
|
-
const loginResp = await getAuthProviderById(amoid).login(null, null, { conid });
|
|
566
|
+
const loginResp = await getAuthProviderById(amoid).login(null, null, { conid }, req);
|
|
567
567
|
return loginResp;
|
|
568
568
|
},
|
|
569
569
|
|
|
@@ -41,6 +41,7 @@ const { decryptConnection } = require('../utility/crypting');
|
|
|
41
41
|
const { getSshTunnel } = require('../utility/sshTunnel');
|
|
42
42
|
const sessions = require('./sessions');
|
|
43
43
|
const jsldata = require('./jsldata');
|
|
44
|
+
const { sendToAuditLog } = require('../utility/auditlog');
|
|
44
45
|
|
|
45
46
|
const logger = getLogger('databaseConnections');
|
|
46
47
|
|
|
@@ -83,8 +84,11 @@ module.exports = {
|
|
|
83
84
|
}
|
|
84
85
|
},
|
|
85
86
|
handle_response(conid, database, { msgid, ...response }) {
|
|
86
|
-
const [resolve, reject] = this.requests[msgid];
|
|
87
|
+
const [resolve, reject, additionalData] = this.requests[msgid];
|
|
87
88
|
resolve(response);
|
|
89
|
+
if (additionalData?.auditLogger) {
|
|
90
|
+
additionalData?.auditLogger(response);
|
|
91
|
+
}
|
|
88
92
|
delete this.requests[msgid];
|
|
89
93
|
},
|
|
90
94
|
handle_status(conid, database, { status }) {
|
|
@@ -215,10 +219,10 @@ module.exports = {
|
|
|
215
219
|
},
|
|
216
220
|
|
|
217
221
|
/** @param {import('dbgate-types').OpenedDatabaseConnection} conn */
|
|
218
|
-
sendRequest(conn, message) {
|
|
222
|
+
sendRequest(conn, message, additionalData = {}) {
|
|
219
223
|
const msgid = crypto.randomUUID();
|
|
220
224
|
const promise = new Promise((resolve, reject) => {
|
|
221
|
-
this.requests[msgid] = [resolve, reject];
|
|
225
|
+
this.requests[msgid] = [resolve, reject, additionalData];
|
|
222
226
|
try {
|
|
223
227
|
conn.subprocess.send({ msgid, ...message });
|
|
224
228
|
} catch (err) {
|
|
@@ -242,18 +246,57 @@ module.exports = {
|
|
|
242
246
|
},
|
|
243
247
|
|
|
244
248
|
sqlSelect_meta: true,
|
|
245
|
-
async sqlSelect({ conid, database, select }, req) {
|
|
249
|
+
async sqlSelect({ conid, database, select, auditLogSessionGroup }, req) {
|
|
246
250
|
testConnectionPermission(conid, req);
|
|
247
251
|
const opened = await this.ensureOpened(conid, database);
|
|
248
|
-
const res = await this.sendRequest(
|
|
252
|
+
const res = await this.sendRequest(
|
|
253
|
+
opened,
|
|
254
|
+
{ msgtype: 'sqlSelect', select },
|
|
255
|
+
{
|
|
256
|
+
auditLogger:
|
|
257
|
+
auditLogSessionGroup && select?.from?.name?.pureName
|
|
258
|
+
? response => {
|
|
259
|
+
sendToAuditLog(req, {
|
|
260
|
+
category: 'dbop',
|
|
261
|
+
component: 'DatabaseConnectionsController',
|
|
262
|
+
event: 'sql.select',
|
|
263
|
+
action: 'select',
|
|
264
|
+
severity: 'info',
|
|
265
|
+
conid,
|
|
266
|
+
database,
|
|
267
|
+
schemaName: select?.from?.name?.schemaName,
|
|
268
|
+
pureName: select?.from?.name?.pureName,
|
|
269
|
+
sumint1: response?.rows?.length,
|
|
270
|
+
sessionParam: `${conid}::${database}::${select?.from?.name?.schemaName || '0'}::${
|
|
271
|
+
select?.from?.name?.pureName
|
|
272
|
+
}`,
|
|
273
|
+
sessionGroup: auditLogSessionGroup,
|
|
274
|
+
message: `Loaded table data from ${select?.from?.name?.pureName}`,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
: null,
|
|
278
|
+
}
|
|
279
|
+
);
|
|
249
280
|
return res;
|
|
250
281
|
},
|
|
251
282
|
|
|
252
283
|
runScript_meta: true,
|
|
253
|
-
async runScript({ conid, database, sql, useTransaction }, req) {
|
|
284
|
+
async runScript({ conid, database, sql, useTransaction, logMessage }, req) {
|
|
254
285
|
testConnectionPermission(conid, req);
|
|
255
286
|
logger.info({ conid, database, sql }, 'Processing script');
|
|
256
287
|
const opened = await this.ensureOpened(conid, database);
|
|
288
|
+
sendToAuditLog(req, {
|
|
289
|
+
category: 'dbop',
|
|
290
|
+
component: 'DatabaseConnectionsController',
|
|
291
|
+
event: 'sql.runscript',
|
|
292
|
+
action: 'runscript',
|
|
293
|
+
severity: 'info',
|
|
294
|
+
conid,
|
|
295
|
+
database,
|
|
296
|
+
detail: sql,
|
|
297
|
+
message: logMessage || `Running SQL script`,
|
|
298
|
+
});
|
|
299
|
+
|
|
257
300
|
const res = await this.sendRequest(opened, { msgtype: 'runScript', sql, useTransaction });
|
|
258
301
|
return res;
|
|
259
302
|
},
|
|
@@ -262,16 +305,53 @@ module.exports = {
|
|
|
262
305
|
async runOperation({ conid, database, operation, useTransaction }, req) {
|
|
263
306
|
testConnectionPermission(conid, req);
|
|
264
307
|
logger.info({ conid, database, operation }, 'Processing operation');
|
|
308
|
+
|
|
309
|
+
sendToAuditLog(req, {
|
|
310
|
+
category: 'dbop',
|
|
311
|
+
component: 'DatabaseConnectionsController',
|
|
312
|
+
event: 'sql.runoperation',
|
|
313
|
+
action: operation.type,
|
|
314
|
+
severity: 'info',
|
|
315
|
+
conid,
|
|
316
|
+
database,
|
|
317
|
+
detail: operation,
|
|
318
|
+
message: `Running DB operation: ${operation.type}`,
|
|
319
|
+
});
|
|
320
|
+
|
|
265
321
|
const opened = await this.ensureOpened(conid, database);
|
|
266
322
|
const res = await this.sendRequest(opened, { msgtype: 'runOperation', operation, useTransaction });
|
|
267
323
|
return res;
|
|
268
324
|
},
|
|
269
325
|
|
|
270
326
|
collectionData_meta: true,
|
|
271
|
-
async collectionData({ conid, database, options }, req) {
|
|
327
|
+
async collectionData({ conid, database, options, auditLogSessionGroup }, req) {
|
|
272
328
|
testConnectionPermission(conid, req);
|
|
273
329
|
const opened = await this.ensureOpened(conid, database);
|
|
274
|
-
const res = await this.sendRequest(
|
|
330
|
+
const res = await this.sendRequest(
|
|
331
|
+
opened,
|
|
332
|
+
{ msgtype: 'collectionData', options },
|
|
333
|
+
{
|
|
334
|
+
auditLogger:
|
|
335
|
+
auditLogSessionGroup && options?.pureName
|
|
336
|
+
? response => {
|
|
337
|
+
sendToAuditLog(req, {
|
|
338
|
+
category: 'dbop',
|
|
339
|
+
component: 'DatabaseConnectionsController',
|
|
340
|
+
event: 'nosql.collectionData',
|
|
341
|
+
action: 'select',
|
|
342
|
+
severity: 'info',
|
|
343
|
+
conid,
|
|
344
|
+
database,
|
|
345
|
+
pureName: options?.pureName,
|
|
346
|
+
sumint1: response?.result?.rows?.length,
|
|
347
|
+
sessionParam: `${conid}::${database}::${options?.pureName}`,
|
|
348
|
+
sessionGroup: auditLogSessionGroup,
|
|
349
|
+
message: `Loaded collection data ${options?.pureName}`,
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
: null,
|
|
353
|
+
}
|
|
354
|
+
);
|
|
275
355
|
return res.result || null;
|
|
276
356
|
},
|
|
277
357
|
|
|
@@ -492,6 +572,20 @@ module.exports = {
|
|
|
492
572
|
}
|
|
493
573
|
|
|
494
574
|
const opened = await this.ensureOpened(conid, database);
|
|
575
|
+
|
|
576
|
+
sendToAuditLog(req, {
|
|
577
|
+
category: 'dbop',
|
|
578
|
+
component: 'DatabaseConnectionsController',
|
|
579
|
+
action: 'structure',
|
|
580
|
+
event: 'dbStructure.get',
|
|
581
|
+
severity: 'info',
|
|
582
|
+
conid,
|
|
583
|
+
database,
|
|
584
|
+
sessionParam: `${conid}::${database}`,
|
|
585
|
+
sessionGroup: 'getStructure',
|
|
586
|
+
message: `Loaded database structure for ${database}`,
|
|
587
|
+
});
|
|
588
|
+
|
|
495
589
|
return opened.structure;
|
|
496
590
|
// const existing = this.opened.find((x) => x.conid == conid && x.database == database);
|
|
497
591
|
// if (existing) return existing.status;
|
package/src/controllers/files.js
CHANGED
|
@@ -203,10 +203,10 @@ module.exports = {
|
|
|
203
203
|
},
|
|
204
204
|
|
|
205
205
|
exportChart_meta: true,
|
|
206
|
-
async exportChart({ filePath, title, config, image }) {
|
|
206
|
+
async exportChart({ filePath, title, config, image, plugins }) {
|
|
207
207
|
const fileName = path.parse(filePath).base;
|
|
208
208
|
const imageFile = fileName.replace('.html', '-preview.png');
|
|
209
|
-
const html = getChartExport(title, config, imageFile);
|
|
209
|
+
const html = getChartExport(title, config, imageFile, plugins);
|
|
210
210
|
await fs.writeFile(filePath, html);
|
|
211
211
|
if (image) {
|
|
212
212
|
const index = image.indexOf('base64,');
|
|
@@ -20,6 +20,7 @@ const { handleProcessCommunication } = require('../utility/processComm');
|
|
|
20
20
|
const processArgs = require('../utility/processArgs');
|
|
21
21
|
const platformInfo = require('../utility/platformInfo');
|
|
22
22
|
const { checkSecureDirectories, checkSecureDirectoriesInScript } = require('../utility/security');
|
|
23
|
+
const { sendToAuditLog, logJsonRunnerScript } = require('../utility/auditlog');
|
|
23
24
|
const logger = getLogger('runners');
|
|
24
25
|
|
|
25
26
|
function extractPlugins(script) {
|
|
@@ -270,7 +271,7 @@ module.exports = {
|
|
|
270
271
|
},
|
|
271
272
|
|
|
272
273
|
start_meta: true,
|
|
273
|
-
async start({ script }) {
|
|
274
|
+
async start({ script }, req) {
|
|
274
275
|
const runid = crypto.randomUUID();
|
|
275
276
|
|
|
276
277
|
if (script.type == 'json') {
|
|
@@ -280,14 +281,36 @@ module.exports = {
|
|
|
280
281
|
}
|
|
281
282
|
}
|
|
282
283
|
|
|
284
|
+
logJsonRunnerScript(req, script);
|
|
285
|
+
|
|
283
286
|
const js = await jsonScriptToJavascript(script);
|
|
284
287
|
return this.startCore(runid, scriptTemplate(js, false));
|
|
285
288
|
}
|
|
286
289
|
|
|
287
290
|
if (!platformInfo.allowShellScripting) {
|
|
291
|
+
sendToAuditLog(req, {
|
|
292
|
+
category: 'shell',
|
|
293
|
+
component: 'RunnersController',
|
|
294
|
+
event: 'script.runFailed',
|
|
295
|
+
action: 'script',
|
|
296
|
+
severity: 'warn',
|
|
297
|
+
detail: script,
|
|
298
|
+
message: 'Scripts are not allowed',
|
|
299
|
+
});
|
|
300
|
+
|
|
288
301
|
return { errorMessage: 'Shell scripting is not allowed' };
|
|
289
302
|
}
|
|
290
303
|
|
|
304
|
+
sendToAuditLog(req, {
|
|
305
|
+
category: 'shell',
|
|
306
|
+
component: 'RunnersController',
|
|
307
|
+
event: 'script.run.shell',
|
|
308
|
+
action: 'script',
|
|
309
|
+
severity: 'info',
|
|
310
|
+
detail: script,
|
|
311
|
+
message: 'Running JS script',
|
|
312
|
+
});
|
|
313
|
+
|
|
291
314
|
return this.startCore(runid, scriptTemplate(script, false));
|
|
292
315
|
},
|
|
293
316
|
|