dbgate-api 5.3.4 → 5.4.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 +9 -7
- package/src/auth/authCommon.js +16 -0
- package/src/auth/authProvider.js +322 -0
- package/src/controllers/auth.js +67 -107
- package/src/controllers/config.js +130 -14
- package/src/controllers/connections.js +113 -1
- package/src/controllers/databaseConnections.js +12 -0
- package/src/controllers/plugins.js +4 -2
- package/src/controllers/serverConnections.js +20 -9
- package/src/controllers/storage.js +20 -0
- package/src/currentVersion.js +2 -2
- package/src/index.js +6 -2
- package/src/main.js +19 -7
- package/src/nativeModulesContent.js +2 -0
- package/src/proc/databaseConnectionProcess.js +13 -0
- package/src/proc/serverConnectionProcess.js +29 -12
- package/src/proc/sshForwardProcess.js +4 -2
- package/src/shell/dbModelToJson.js +16 -0
- package/src/shell/index.js +4 -0
- package/src/shell/jsonToDbModel.js +9 -0
- package/src/shell/modifyJsonLinesReader.js +7 -4
- package/src/shell/requirePlugin.js +3 -0
- package/src/utility/authProxy.js +25 -0
- package/src/utility/checkLicense.js +10 -0
- package/src/utility/hardwareFingerprint.js +76 -0
- package/src/utility/hasPermission.js +62 -54
- package/src/utility/socket.js +4 -1
- package/src/utility/useController.js +1 -1
- package/webpack.config.js +2 -0
|
@@ -3,14 +3,20 @@ const os = require('os');
|
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const axios = require('axios');
|
|
5
5
|
const { datadir, getLogsFilePath } = require('../utility/directories');
|
|
6
|
-
const { hasPermission
|
|
6
|
+
const { hasPermission } = require('../utility/hasPermission');
|
|
7
7
|
const socket = require('../utility/socket');
|
|
8
8
|
const _ = require('lodash');
|
|
9
9
|
const AsyncLock = require('async-lock');
|
|
10
|
+
const jwt = require('jsonwebtoken');
|
|
10
11
|
|
|
11
12
|
const currentVersion = require('../currentVersion');
|
|
12
13
|
const platformInfo = require('../utility/platformInfo');
|
|
13
14
|
const connections = require('../controllers/connections');
|
|
15
|
+
const { getAuthProviderFromReq } = require('../auth/authProvider');
|
|
16
|
+
const { checkLicense, checkLicenseKey } = require('../utility/checkLicense');
|
|
17
|
+
const storage = require('./storage');
|
|
18
|
+
const { getAuthProxyUrl } = require('../utility/authProxy');
|
|
19
|
+
const { getPublicHardwareFingerprint } = require('../utility/hardwareFingerprint');
|
|
14
20
|
|
|
15
21
|
const lock = new AsyncLock();
|
|
16
22
|
|
|
@@ -27,27 +33,51 @@ module.exports = {
|
|
|
27
33
|
|
|
28
34
|
get_meta: true,
|
|
29
35
|
async get(_params, req) {
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
|
|
36
|
+
const authProvider = getAuthProviderFromReq(req);
|
|
37
|
+
const login = authProvider.getCurrentLogin(req);
|
|
38
|
+
const permissions = authProvider.getCurrentPermissions(req);
|
|
39
|
+
const isUserLoggedIn = authProvider.isUserLoggedIn(req);
|
|
40
|
+
|
|
41
|
+
const singleConid = authProvider.getSingleConnectionId(req);
|
|
42
|
+
|
|
43
|
+
const singleConnection = singleConid
|
|
44
|
+
? await connections.getCore({ conid: singleConid })
|
|
45
|
+
: connections.singleConnection;
|
|
46
|
+
|
|
47
|
+
let configurationError = null;
|
|
48
|
+
if (process.env.STORAGE_DATABASE && process.env.BASIC_AUTH) {
|
|
49
|
+
configurationError =
|
|
50
|
+
'Basic authentization is not allowed, when using storage. Cannot use both STORAGE_DATABASE and BASIC_AUTH';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const checkedLicense = await checkLicense();
|
|
54
|
+
const isLicenseValid = checkedLicense?.status == 'ok';
|
|
35
55
|
|
|
36
56
|
return {
|
|
37
57
|
runAsPortal: !!connections.portalConnections,
|
|
38
58
|
singleDbConnection: connections.singleDbConnection,
|
|
39
|
-
singleConnection:
|
|
59
|
+
singleConnection: singleConnection,
|
|
60
|
+
isUserLoggedIn,
|
|
40
61
|
// hideAppEditor: !!process.env.HIDE_APP_EDITOR,
|
|
41
62
|
allowShellConnection: platformInfo.allowShellConnection,
|
|
42
63
|
allowShellScripting: platformInfo.allowShellScripting,
|
|
43
64
|
isDocker: platformInfo.isDocker,
|
|
65
|
+
isElectron: platformInfo.isElectron,
|
|
66
|
+
isLicenseValid,
|
|
67
|
+
checkedLicense,
|
|
68
|
+
configurationError,
|
|
69
|
+
logoutUrl: await authProvider.getLogoutUrl(),
|
|
44
70
|
permissions,
|
|
45
71
|
login,
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
72
|
+
// ...additionalConfigProps,
|
|
73
|
+
isBasicAuth: !!process.env.BASIC_AUTH,
|
|
74
|
+
isAdminLoginForm: !!(
|
|
75
|
+
process.env.STORAGE_DATABASE &&
|
|
76
|
+
process.env.ADMIN_PASSWORD &&
|
|
77
|
+
!process.env.BASIC_AUTH &&
|
|
78
|
+
checkedLicense?.type == 'premium'
|
|
79
|
+
),
|
|
80
|
+
storageDatabase: process.env.STORAGE_DATABASE,
|
|
51
81
|
logsFilePath: getLogsFilePath(),
|
|
52
82
|
connectionsFilePath: path.join(datadir(), 'connections.jsonl'),
|
|
53
83
|
...currentVersion,
|
|
@@ -75,6 +105,12 @@ module.exports = {
|
|
|
75
105
|
return res;
|
|
76
106
|
},
|
|
77
107
|
|
|
108
|
+
deleteSettings_meta: true,
|
|
109
|
+
async deleteSettings() {
|
|
110
|
+
await fs.unlink(path.join(datadir(), 'settings.json'));
|
|
111
|
+
return true;
|
|
112
|
+
},
|
|
113
|
+
|
|
78
114
|
fillMissingSettings(value) {
|
|
79
115
|
const res = {
|
|
80
116
|
...value,
|
|
@@ -97,12 +133,80 @@ module.exports = {
|
|
|
97
133
|
async loadSettings() {
|
|
98
134
|
try {
|
|
99
135
|
const settingsText = await fs.readFile(path.join(datadir(), 'settings.json'), { encoding: 'utf-8' });
|
|
100
|
-
return
|
|
136
|
+
return {
|
|
137
|
+
...this.fillMissingSettings(JSON.parse(settingsText)),
|
|
138
|
+
'other.licenseKey': platformInfo.isElectron ? await this.loadLicenseKey() : undefined,
|
|
139
|
+
};
|
|
101
140
|
} catch (err) {
|
|
102
141
|
return this.fillMissingSettings({});
|
|
103
142
|
}
|
|
104
143
|
},
|
|
105
144
|
|
|
145
|
+
async loadLicenseKey() {
|
|
146
|
+
try {
|
|
147
|
+
const licenseKey = await fs.readFile(path.join(datadir(), 'license.key'), { encoding: 'utf-8' });
|
|
148
|
+
return licenseKey;
|
|
149
|
+
} catch (err) {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
saveLicenseKey_meta: true,
|
|
155
|
+
async saveLicenseKey({ licenseKey }) {
|
|
156
|
+
const decoded = jwt.decode(licenseKey);
|
|
157
|
+
if (!decoded) {
|
|
158
|
+
return {
|
|
159
|
+
status: 'error',
|
|
160
|
+
errorMessage: 'Invalid license key',
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const { exp } = decoded;
|
|
165
|
+
if (exp * 1000 < Date.now()) {
|
|
166
|
+
return {
|
|
167
|
+
status: 'error',
|
|
168
|
+
errorMessage: 'License key is expired',
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
if (process.env.STORAGE_DATABASE) {
|
|
174
|
+
await storage.writeConfig({ group: 'license', config: { licenseKey } });
|
|
175
|
+
// await storageWriteConfig('license', { licenseKey });
|
|
176
|
+
} else {
|
|
177
|
+
await fs.writeFile(path.join(datadir(), 'license.key'), licenseKey);
|
|
178
|
+
}
|
|
179
|
+
socket.emitChanged(`config-changed`);
|
|
180
|
+
return { status: 'ok' };
|
|
181
|
+
} catch (err) {
|
|
182
|
+
return {
|
|
183
|
+
status: 'error',
|
|
184
|
+
errorMessage: err.message,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
|
|
189
|
+
startTrial_meta: true,
|
|
190
|
+
async startTrial() {
|
|
191
|
+
try {
|
|
192
|
+
const fingerprint = await getPublicHardwareFingerprint();
|
|
193
|
+
|
|
194
|
+
const resp = await axios.default.post(`${getAuthProxyUrl()}/trial-license`, {
|
|
195
|
+
type: 'premium-trial',
|
|
196
|
+
days: 30,
|
|
197
|
+
fingerprint,
|
|
198
|
+
});
|
|
199
|
+
const { token } = resp.data;
|
|
200
|
+
|
|
201
|
+
return await this.saveLicenseKey({ licenseKey: token });
|
|
202
|
+
} catch (err) {
|
|
203
|
+
return {
|
|
204
|
+
status: 'error',
|
|
205
|
+
errorMessage: err.message,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
|
|
106
210
|
updateSettings_meta: true,
|
|
107
211
|
async updateSettings(values, req) {
|
|
108
212
|
if (!hasPermission(`settings/change`, req)) return false;
|
|
@@ -112,10 +216,16 @@ module.exports = {
|
|
|
112
216
|
try {
|
|
113
217
|
const updated = {
|
|
114
218
|
...currentValue,
|
|
115
|
-
...values,
|
|
219
|
+
..._.omit(values, ['other.licenseKey']),
|
|
116
220
|
};
|
|
117
221
|
await fs.writeFile(path.join(datadir(), 'settings.json'), JSON.stringify(updated, undefined, 2));
|
|
118
222
|
// this.settingsValue = updated;
|
|
223
|
+
|
|
224
|
+
if (currentValue['other.licenseKey'] != values['other.licenseKey']) {
|
|
225
|
+
await this.saveLicenseKey({ licenseKey: values['other.licenseKey'] });
|
|
226
|
+
socket.emitChanged(`config-changed`);
|
|
227
|
+
}
|
|
228
|
+
|
|
119
229
|
socket.emitChanged(`settings-changed`);
|
|
120
230
|
return updated;
|
|
121
231
|
} catch (err) {
|
|
@@ -130,4 +240,10 @@ module.exports = {
|
|
|
130
240
|
const resp = await axios.default.get('https://raw.githubusercontent.com/dbgate/dbgate/master/CHANGELOG.md');
|
|
131
241
|
return resp.data;
|
|
132
242
|
},
|
|
243
|
+
|
|
244
|
+
checkLicense_meta: true,
|
|
245
|
+
async checkLicense({ licenseKey }) {
|
|
246
|
+
const resp = await checkLicenseKey(licenseKey);
|
|
247
|
+
return resp;
|
|
248
|
+
},
|
|
133
249
|
};
|
|
@@ -16,6 +16,9 @@ const { safeJsonParse, getLogger } = require('dbgate-tools');
|
|
|
16
16
|
const platformInfo = require('../utility/platformInfo');
|
|
17
17
|
const { connectionHasPermission, testConnectionPermission } = require('../utility/hasPermission');
|
|
18
18
|
const pipeForkLogs = require('../utility/pipeForkLogs');
|
|
19
|
+
const requireEngineDriver = require('../utility/requireEngineDriver');
|
|
20
|
+
const { getAuthProviderById } = require('../auth/authProvider');
|
|
21
|
+
const { startTokenChecking } = require('../utility/authProxy');
|
|
19
22
|
|
|
20
23
|
const logger = getLogger('connections');
|
|
21
24
|
|
|
@@ -70,6 +73,8 @@ function getPortalCollections() {
|
|
|
70
73
|
displayName: process.env[`LABEL_${id}`],
|
|
71
74
|
isReadOnly: process.env[`READONLY_${id}`],
|
|
72
75
|
databases: process.env[`DBCONFIG_${id}`] ? safeJsonParse(process.env[`DBCONFIG_${id}`]) : null,
|
|
76
|
+
allowedDatabases: process.env[`ALLOWED_DATABASES_${id}`]?.replace(/\|/g, '\n'),
|
|
77
|
+
allowedDatabasesRegex: process.env[`ALLOWED_DATABASES_REGEX_${id}`],
|
|
73
78
|
parent: process.env[`PARENT_${id}`] || undefined,
|
|
74
79
|
|
|
75
80
|
// SSH tunnel
|
|
@@ -199,6 +204,12 @@ module.exports = {
|
|
|
199
204
|
|
|
200
205
|
list_meta: true,
|
|
201
206
|
async list(_params, req) {
|
|
207
|
+
const storage = require('./storage');
|
|
208
|
+
|
|
209
|
+
const storageConnections = await storage.connections(req);
|
|
210
|
+
if (storageConnections) {
|
|
211
|
+
return storageConnections;
|
|
212
|
+
}
|
|
202
213
|
if (portalConnections) {
|
|
203
214
|
if (platformInfo.allowShellConnection) return portalConnections;
|
|
204
215
|
return portalConnections.map(maskConnection).filter(x => connectionHasPermission(x, req));
|
|
@@ -236,14 +247,16 @@ module.exports = {
|
|
|
236
247
|
},
|
|
237
248
|
|
|
238
249
|
saveVolatile_meta: true,
|
|
239
|
-
async saveVolatile({ conid, user, password, test }) {
|
|
250
|
+
async saveVolatile({ conid, user = undefined, password = undefined, accessToken = undefined, test = false }) {
|
|
240
251
|
const old = await this.getCore({ conid });
|
|
241
252
|
const res = {
|
|
242
253
|
...old,
|
|
243
254
|
_id: crypto.randomUUID(),
|
|
244
255
|
password,
|
|
256
|
+
accessToken,
|
|
245
257
|
passwordMode: undefined,
|
|
246
258
|
unsaved: true,
|
|
259
|
+
useRedirectDbLogin: false,
|
|
247
260
|
};
|
|
248
261
|
if (old.passwordMode == 'askUser') {
|
|
249
262
|
res.user = user;
|
|
@@ -336,6 +349,14 @@ module.exports = {
|
|
|
336
349
|
if (volatile) {
|
|
337
350
|
return volatile;
|
|
338
351
|
}
|
|
352
|
+
|
|
353
|
+
const storage = require('./storage');
|
|
354
|
+
|
|
355
|
+
const storageConnection = await storage.getConnection({ conid });
|
|
356
|
+
if (storageConnection) {
|
|
357
|
+
return storageConnection;
|
|
358
|
+
}
|
|
359
|
+
|
|
339
360
|
if (portalConnections) {
|
|
340
361
|
const res = portalConnections.find(x => x._id == conid) || null;
|
|
341
362
|
return mask && !platformInfo.allowShellConnection ? maskConnection(res) : res;
|
|
@@ -365,4 +386,95 @@ module.exports = {
|
|
|
365
386
|
});
|
|
366
387
|
return res;
|
|
367
388
|
},
|
|
389
|
+
|
|
390
|
+
dbloginWeb_meta: {
|
|
391
|
+
raw: true,
|
|
392
|
+
method: 'get',
|
|
393
|
+
},
|
|
394
|
+
async dbloginWeb(req, res) {
|
|
395
|
+
const { conid, state, redirectUri } = req.query;
|
|
396
|
+
const connection = await this.getCore({ conid });
|
|
397
|
+
const driver = requireEngineDriver(connection);
|
|
398
|
+
const authResp = await driver.getRedirectAuthUrl(connection, {
|
|
399
|
+
redirectUri,
|
|
400
|
+
state,
|
|
401
|
+
client: 'web',
|
|
402
|
+
});
|
|
403
|
+
res.redirect(authResp.url);
|
|
404
|
+
},
|
|
405
|
+
|
|
406
|
+
dbloginApp_meta: true,
|
|
407
|
+
async dbloginApp({ conid, state }) {
|
|
408
|
+
const connection = await this.getCore({ conid });
|
|
409
|
+
const driver = requireEngineDriver(connection);
|
|
410
|
+
const resp = await driver.getRedirectAuthUrl(connection, {
|
|
411
|
+
state,
|
|
412
|
+
client: 'app',
|
|
413
|
+
});
|
|
414
|
+
startTokenChecking(resp.sid, async token => {
|
|
415
|
+
const volatile = await this.saveVolatile({ conid, accessToken: token });
|
|
416
|
+
socket.emit('got-volatile-token', { savedConId: conid, volatileConId: volatile._id });
|
|
417
|
+
});
|
|
418
|
+
return resp;
|
|
419
|
+
},
|
|
420
|
+
|
|
421
|
+
dbloginToken_meta: true,
|
|
422
|
+
async dbloginToken({ code, conid, strmid, redirectUri, sid }) {
|
|
423
|
+
try {
|
|
424
|
+
const connection = await this.getCore({ conid });
|
|
425
|
+
const driver = requireEngineDriver(connection);
|
|
426
|
+
const accessToken = await driver.getAuthTokenFromCode(connection, { sid, code, redirectUri });
|
|
427
|
+
const volatile = await this.saveVolatile({ conid, accessToken });
|
|
428
|
+
// console.log('******************************** WE HAVE ACCESS TOKEN', accessToken);
|
|
429
|
+
socket.emit('got-volatile-token', { strmid, savedConId: conid, volatileConId: volatile._id });
|
|
430
|
+
return { success: true };
|
|
431
|
+
} catch (err) {
|
|
432
|
+
logger.error({ err }, 'Error getting DB token');
|
|
433
|
+
return { error: err.message };
|
|
434
|
+
}
|
|
435
|
+
},
|
|
436
|
+
|
|
437
|
+
dbloginAuthToken_meta: true,
|
|
438
|
+
async dbloginAuthToken({ amoid, code, conid, redirectUri, sid }) {
|
|
439
|
+
try {
|
|
440
|
+
const connection = await this.getCore({ conid });
|
|
441
|
+
const driver = requireEngineDriver(connection);
|
|
442
|
+
const accessToken = await driver.getAuthTokenFromCode(connection, { code, redirectUri, sid });
|
|
443
|
+
const volatile = await this.saveVolatile({ conid, accessToken });
|
|
444
|
+
const authProvider = getAuthProviderById(amoid);
|
|
445
|
+
const resp = await authProvider.login(null, null, { conid: volatile._id });
|
|
446
|
+
return resp;
|
|
447
|
+
} catch (err) {
|
|
448
|
+
logger.error({ err }, 'Error getting DB token');
|
|
449
|
+
return { error: err.message };
|
|
450
|
+
}
|
|
451
|
+
},
|
|
452
|
+
|
|
453
|
+
dbloginAuth_meta: true,
|
|
454
|
+
async dbloginAuth({ amoid, conid, user, password }) {
|
|
455
|
+
if (user || password) {
|
|
456
|
+
const saveResp = await this.saveVolatile({ conid, user, password, test: true });
|
|
457
|
+
if (saveResp.msgtype == 'connected') {
|
|
458
|
+
const loginResp = await getAuthProviderById(amoid).login(user, password, { conid: saveResp._id });
|
|
459
|
+
return loginResp;
|
|
460
|
+
}
|
|
461
|
+
return saveResp;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// user and password is stored in connection, volatile connection is not needed
|
|
465
|
+
const loginResp = await getAuthProviderById(amoid).login(null, null, { conid });
|
|
466
|
+
return loginResp;
|
|
467
|
+
},
|
|
468
|
+
|
|
469
|
+
volatileDbloginFromAuth_meta: true,
|
|
470
|
+
async volatileDbloginFromAuth({ conid }, req) {
|
|
471
|
+
const connection = await this.getCore({ conid });
|
|
472
|
+
const driver = requireEngineDriver(connection);
|
|
473
|
+
const accessToken = await driver.getAccessTokenFromAuth(connection, req);
|
|
474
|
+
if (accessToken) {
|
|
475
|
+
const volatile = await this.saveVolatile({ conid, accessToken });
|
|
476
|
+
return volatile;
|
|
477
|
+
}
|
|
478
|
+
return null;
|
|
479
|
+
},
|
|
368
480
|
};
|
|
@@ -89,6 +89,9 @@ module.exports = {
|
|
|
89
89
|
if (connection.passwordMode == 'askPassword' || connection.passwordMode == 'askUser') {
|
|
90
90
|
throw new MissingCredentialsError({ conid, passwordMode: connection.passwordMode });
|
|
91
91
|
}
|
|
92
|
+
if (connection.useRedirectDbLogin) {
|
|
93
|
+
throw new MissingCredentialsError({ conid, redirectToDbLogin: true });
|
|
94
|
+
}
|
|
92
95
|
const subprocess = fork(
|
|
93
96
|
global['API_PACKAGE'] || process.argv[1],
|
|
94
97
|
[
|
|
@@ -179,6 +182,15 @@ module.exports = {
|
|
|
179
182
|
return res;
|
|
180
183
|
},
|
|
181
184
|
|
|
185
|
+
runOperation_meta: true,
|
|
186
|
+
async runOperation({ conid, database, operation, useTransaction }, req) {
|
|
187
|
+
testConnectionPermission(conid, req);
|
|
188
|
+
logger.info({ conid, database, operation }, 'Processing operation');
|
|
189
|
+
const opened = await this.ensureOpened(conid, database);
|
|
190
|
+
const res = await this.sendRequest(opened, { msgtype: 'runOperation', operation, useTransaction });
|
|
191
|
+
return res;
|
|
192
|
+
},
|
|
193
|
+
|
|
182
194
|
collectionData_meta: true,
|
|
183
195
|
async collectionData({ conid, database, options }, req) {
|
|
184
196
|
testConnectionPermission(conid, req);
|
|
@@ -42,13 +42,14 @@ module.exports = {
|
|
|
42
42
|
|
|
43
43
|
info_meta: true,
|
|
44
44
|
async info({ packageName }) {
|
|
45
|
+
// @ts-ignore
|
|
46
|
+
const isPackaged = await fs.exists(path.join(packagedPluginsDir(), packageName));
|
|
47
|
+
|
|
45
48
|
try {
|
|
46
49
|
const infoResp = await axios.default.get(`https://registry.npmjs.org/${packageName}`);
|
|
47
50
|
const { latest } = infoResp.data['dist-tags'];
|
|
48
51
|
const manifest = infoResp.data.versions[latest];
|
|
49
52
|
const { readme } = infoResp.data;
|
|
50
|
-
// @ts-ignore
|
|
51
|
-
const isPackaged = await fs.exists(path.join(packagedPluginsDir(), packageName));
|
|
52
53
|
|
|
53
54
|
return {
|
|
54
55
|
readme,
|
|
@@ -57,6 +58,7 @@ module.exports = {
|
|
|
57
58
|
};
|
|
58
59
|
} catch (err) {
|
|
59
60
|
return {
|
|
61
|
+
isPackaged,
|
|
60
62
|
state: 'error',
|
|
61
63
|
error: err.message,
|
|
62
64
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const crypto = require('crypto');
|
|
1
2
|
const connections = require('./connections');
|
|
2
3
|
const socket = require('../utility/socket');
|
|
3
4
|
const { fork } = require('child_process');
|
|
@@ -56,6 +57,9 @@ module.exports = {
|
|
|
56
57
|
if (connection.passwordMode == 'askPassword' || connection.passwordMode == 'askUser') {
|
|
57
58
|
throw new MissingCredentialsError({ conid, passwordMode: connection.passwordMode });
|
|
58
59
|
}
|
|
60
|
+
if (connection.useRedirectDbLogin) {
|
|
61
|
+
throw new MissingCredentialsError({ conid, redirectToDbLogin: true });
|
|
62
|
+
}
|
|
59
63
|
const subprocess = fork(
|
|
60
64
|
global['API_PACKAGE'] || process.argv[1],
|
|
61
65
|
[
|
|
@@ -181,22 +185,29 @@ module.exports = {
|
|
|
181
185
|
return { status: 'ok' };
|
|
182
186
|
},
|
|
183
187
|
|
|
184
|
-
|
|
185
|
-
async createDatabase({ conid, name }, req) {
|
|
188
|
+
async sendDatabaseOp({ conid, msgtype, name }, req) {
|
|
186
189
|
testConnectionPermission(conid, req);
|
|
187
190
|
const opened = await this.ensureOpened(conid);
|
|
188
191
|
if (opened.connection.isReadOnly) return false;
|
|
189
|
-
|
|
190
|
-
|
|
192
|
+
const res = await this.sendRequest(opened, { msgtype, name });
|
|
193
|
+
if (res.errorMessage) {
|
|
194
|
+
console.error(res.errorMessage);
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
apiErrorMessage: res.errorMessage,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
return res.result || null;
|
|
201
|
+
},
|
|
202
|
+
|
|
203
|
+
createDatabase_meta: true,
|
|
204
|
+
async createDatabase({ conid, name }, req) {
|
|
205
|
+
return this.sendDatabaseOp({ conid, msgtype: 'createDatabase', name }, req);
|
|
191
206
|
},
|
|
192
207
|
|
|
193
208
|
dropDatabase_meta: true,
|
|
194
209
|
async dropDatabase({ conid, name }, req) {
|
|
195
|
-
|
|
196
|
-
const opened = await this.ensureOpened(conid);
|
|
197
|
-
if (opened.connection.isReadOnly) return false;
|
|
198
|
-
opened.subprocess.send({ msgtype: 'dropDatabase', name });
|
|
199
|
-
return { status: 'ok' };
|
|
210
|
+
return this.sendDatabaseOp({ conid, msgtype: 'dropDatabase', name }, req);
|
|
200
211
|
},
|
|
201
212
|
|
|
202
213
|
sendRequest(conn, message) {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
connections_meta: true,
|
|
3
|
+
async connections(req) {
|
|
4
|
+
return null;
|
|
5
|
+
},
|
|
6
|
+
|
|
7
|
+
getConnection_meta: true,
|
|
8
|
+
async getConnection({ conid }) {
|
|
9
|
+
return null;
|
|
10
|
+
},
|
|
11
|
+
|
|
12
|
+
async loadSuperadminPermissions() {
|
|
13
|
+
return [];
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
getConnectionsForLoginPage_meta: true,
|
|
17
|
+
async getConnectionsForLoginPage() {
|
|
18
|
+
return null;
|
|
19
|
+
},
|
|
20
|
+
};
|
package/src/currentVersion.js
CHANGED
package/src/index.js
CHANGED
|
@@ -97,9 +97,12 @@ if (processArgs.listenApi) {
|
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
const shell = require('./shell/index');
|
|
100
|
-
const
|
|
100
|
+
const currentVersion = require('./currentVersion');
|
|
101
101
|
|
|
102
|
-
global
|
|
102
|
+
global.DBGATE_PACKAGES = {
|
|
103
|
+
'dbgate-tools': require('dbgate-tools'),
|
|
104
|
+
'dbgate-sqltree': require('dbgate-sqltree'),
|
|
105
|
+
};
|
|
103
106
|
|
|
104
107
|
if (processArgs.startProcess) {
|
|
105
108
|
const proc = require('./proc');
|
|
@@ -116,6 +119,7 @@ module.exports = {
|
|
|
116
119
|
...shell,
|
|
117
120
|
getLogger,
|
|
118
121
|
configureLogger,
|
|
122
|
+
currentVersion,
|
|
119
123
|
// loadLogsContent,
|
|
120
124
|
getMainModule: () => require('./main'),
|
|
121
125
|
};
|
package/src/main.js
CHANGED
|
@@ -18,6 +18,7 @@ const sessions = require('./controllers/sessions');
|
|
|
18
18
|
const runners = require('./controllers/runners');
|
|
19
19
|
const jsldata = require('./controllers/jsldata');
|
|
20
20
|
const config = require('./controllers/config');
|
|
21
|
+
const storage = require('./controllers/storage');
|
|
21
22
|
const archive = require('./controllers/archive');
|
|
22
23
|
const apps = require('./controllers/apps');
|
|
23
24
|
const auth = require('./controllers/auth');
|
|
@@ -31,9 +32,9 @@ const onFinished = require('on-finished');
|
|
|
31
32
|
const { rundir } = require('./utility/directories');
|
|
32
33
|
const platformInfo = require('./utility/platformInfo');
|
|
33
34
|
const getExpressPath = require('./utility/getExpressPath');
|
|
34
|
-
const { getLogins } = require('./utility/hasPermission');
|
|
35
35
|
const _ = require('lodash');
|
|
36
36
|
const { getLogger } = require('dbgate-tools');
|
|
37
|
+
const { getDefaultAuthProvider } = require('./auth/authProvider');
|
|
37
38
|
|
|
38
39
|
const logger = getLogger('main');
|
|
39
40
|
|
|
@@ -44,11 +45,23 @@ function start() {
|
|
|
44
45
|
|
|
45
46
|
const server = http.createServer(app);
|
|
46
47
|
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
if (process.env.BASIC_AUTH && !process.env.STORAGE_DATABASE) {
|
|
49
|
+
async function authorizer(username, password, cb) {
|
|
50
|
+
try {
|
|
51
|
+
const resp = await getDefaultAuthProvider().login(username, password);
|
|
52
|
+
if (resp.accessToken) {
|
|
53
|
+
cb(null, true);
|
|
54
|
+
} else {
|
|
55
|
+
cb(null, false);
|
|
56
|
+
}
|
|
57
|
+
} catch (err) {
|
|
58
|
+
cb(err, false);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
49
61
|
app.use(
|
|
50
62
|
basicAuth({
|
|
51
|
-
|
|
63
|
+
authorizer,
|
|
64
|
+
authorizeAsync: true,
|
|
52
65
|
challenge: true,
|
|
53
66
|
realm: 'DbGate Web App',
|
|
54
67
|
})
|
|
@@ -72,9 +85,7 @@ function start() {
|
|
|
72
85
|
});
|
|
73
86
|
}
|
|
74
87
|
|
|
75
|
-
|
|
76
|
-
app.use(auth.authMiddleware);
|
|
77
|
-
}
|
|
88
|
+
app.use(auth.authMiddleware);
|
|
78
89
|
|
|
79
90
|
app.get(getExpressPath('/stream'), async function (req, res) {
|
|
80
91
|
const strmid = req.query.strmid;
|
|
@@ -162,6 +173,7 @@ function useAllControllers(app, electron) {
|
|
|
162
173
|
useController(app, electron, '/runners', runners);
|
|
163
174
|
useController(app, electron, '/jsldata', jsldata);
|
|
164
175
|
useController(app, electron, '/config', config);
|
|
176
|
+
useController(app, electron, '/storage', storage);
|
|
165
177
|
useController(app, electron, '/archive', archive);
|
|
166
178
|
useController(app, electron, '/uploads', uploads);
|
|
167
179
|
useController(app, electron, '/plugins', plugins);
|
|
@@ -170,6 +170,18 @@ async function handleRunScript({ msgid, sql, useTransaction }, skipReadonlyCheck
|
|
|
170
170
|
}
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
+
async function handleRunOperation({ msgid, operation, useTransaction }, skipReadonlyCheck = false) {
|
|
174
|
+
await waitConnected();
|
|
175
|
+
const driver = requireEngineDriver(storedConnection);
|
|
176
|
+
try {
|
|
177
|
+
if (!skipReadonlyCheck) ensureExecuteCustomScript(driver);
|
|
178
|
+
await driver.operation(systemConnection, operation, { useTransaction });
|
|
179
|
+
process.send({ msgtype: 'response', msgid });
|
|
180
|
+
} catch (err) {
|
|
181
|
+
process.send({ msgtype: 'response', msgid, errorMessage: err.message });
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
173
185
|
async function handleQueryData({ msgid, sql }, skipReadonlyCheck = false) {
|
|
174
186
|
await waitConnected();
|
|
175
187
|
const driver = requireEngineDriver(storedConnection);
|
|
@@ -311,6 +323,7 @@ const messageHandlers = {
|
|
|
311
323
|
connect: handleConnect,
|
|
312
324
|
queryData: handleQueryData,
|
|
313
325
|
runScript: handleRunScript,
|
|
326
|
+
runOperation: handleRunOperation,
|
|
314
327
|
updateCollection: handleUpdateCollection,
|
|
315
328
|
collectionData: handleCollectionData,
|
|
316
329
|
loadKeys: handleLoadKeys,
|
|
@@ -16,7 +16,18 @@ let afterConnectCallbacks = [];
|
|
|
16
16
|
async function handleRefresh() {
|
|
17
17
|
const driver = requireEngineDriver(storedConnection);
|
|
18
18
|
try {
|
|
19
|
-
|
|
19
|
+
let databases = await driver.listDatabases(systemConnection);
|
|
20
|
+
if (storedConnection?.allowedDatabases?.trim()) {
|
|
21
|
+
const allowedDatabaseList = storedConnection.allowedDatabases
|
|
22
|
+
.split('\n')
|
|
23
|
+
.map(x => x.trim().toLowerCase())
|
|
24
|
+
.filter(x => x);
|
|
25
|
+
databases = databases.filter(x => allowedDatabaseList.includes(x.name.toLocaleLowerCase()));
|
|
26
|
+
}
|
|
27
|
+
if (storedConnection?.allowedDatabasesRegex?.trim()) {
|
|
28
|
+
const regex = new RegExp(storedConnection.allowedDatabasesRegex, 'i');
|
|
29
|
+
databases = databases.filter(x => regex.test(x.name));
|
|
30
|
+
}
|
|
20
31
|
setStatusName('ok');
|
|
21
32
|
const databasesString = stableStringify(databases);
|
|
22
33
|
if (lastDatabases != databasesString) {
|
|
@@ -94,18 +105,24 @@ function handlePing() {
|
|
|
94
105
|
lastPing = new Date().getTime();
|
|
95
106
|
}
|
|
96
107
|
|
|
97
|
-
async function handleDatabaseOp(op, { name }) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
108
|
+
async function handleDatabaseOp(op, { msgid, name }) {
|
|
109
|
+
try {
|
|
110
|
+
const driver = requireEngineDriver(storedConnection);
|
|
111
|
+
systemConnection = await connectUtility(driver, storedConnection, 'app');
|
|
112
|
+
if (driver[op]) {
|
|
113
|
+
await driver[op](systemConnection, name);
|
|
114
|
+
} else {
|
|
115
|
+
const dmp = driver.createDumper();
|
|
116
|
+
dmp[op](name);
|
|
117
|
+
logger.info({ sql: dmp.s }, 'Running script');
|
|
118
|
+
await driver.query(systemConnection, dmp.s);
|
|
119
|
+
}
|
|
120
|
+
await handleRefresh();
|
|
121
|
+
|
|
122
|
+
process.send({ msgtype: 'response', msgid, status: 'ok' });
|
|
123
|
+
} catch (err) {
|
|
124
|
+
process.send({ msgtype: 'response', msgid, errorMessage: err.message });
|
|
107
125
|
}
|
|
108
|
-
await handleRefresh();
|
|
109
126
|
}
|
|
110
127
|
|
|
111
128
|
async function handleDriverDataCore(msgid, callMethod) {
|