dbgate-api 5.1.5 → 5.1.7-alpha.13
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/env/dblogin/.env +14 -0
- package/env/singledb/.env +2 -2
- package/package.json +10 -5
- package/src/controllers/apps.js +6 -6
- package/src/controllers/archive.js +15 -5
- package/src/controllers/auth.js +143 -0
- package/src/controllers/config.js +11 -2
- package/src/controllers/connections.js +70 -3
- package/src/controllers/databaseConnections.js +9 -5
- package/src/controllers/files.js +7 -7
- package/src/controllers/jsldata.js +92 -7
- package/src/controllers/serverConnections.js +52 -4
- package/src/controllers/sessions.js +42 -0
- package/src/currentVersion.js +2 -2
- package/src/main.js +22 -13
- package/src/nativeModulesContent.js +1 -1
- package/src/proc/databaseConnectionProcess.js +2 -2
- package/src/proc/serverConnectionProcess.js +36 -2
- package/src/proc/sessionProcess.js +45 -0
- package/src/utility/JsonLinesDatabase.js +6 -0
- package/src/utility/JsonLinesDatastore.js +11 -3
- package/src/utility/exceptions.js +9 -0
- package/src/utility/requirePluginFunction.js +16 -0
- package/src/utility/socket.js +4 -3
- package/src/utility/useController.js +17 -1
package/env/dblogin/.env
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
DEVMODE=1
|
|
2
|
+
|
|
3
|
+
CONNECTIONS=mysql
|
|
4
|
+
SINGLE_CONNECTION=mysql
|
|
5
|
+
# SINGLE_DATABASE=Chinook
|
|
6
|
+
|
|
7
|
+
LABEL_mysql=MySql localhost
|
|
8
|
+
SERVER_mysql=localhost
|
|
9
|
+
# USER_mysql=root
|
|
10
|
+
PORT_mysql=3306
|
|
11
|
+
# PASSWORD_mysql=Pwd2020Db
|
|
12
|
+
ENGINE_mysql=mysql@dbgate-plugin-mysql
|
|
13
|
+
# PASSWORD_MODE_mysql=askPassword
|
|
14
|
+
PASSWORD_MODE_mysql=askUser
|
package/env/singledb/.env
CHANGED
|
@@ -5,8 +5,8 @@ CONNECTIONS=mysql
|
|
|
5
5
|
LABEL_mysql=MySql localhost
|
|
6
6
|
SERVER_mysql=localhost
|
|
7
7
|
USER_mysql=root
|
|
8
|
-
PASSWORD_mysql=
|
|
9
|
-
PORT_mysql=
|
|
8
|
+
PASSWORD_mysql=Pwd2020Db
|
|
9
|
+
PORT_mysql=3306
|
|
10
10
|
ENGINE_mysql=mysql@dbgate-plugin-mysql
|
|
11
11
|
DBCONFIG_mysql=[{"name":"Chinook","connectionColor":"cyan"}]
|
|
12
12
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dbgate-api",
|
|
3
3
|
"main": "src/index.js",
|
|
4
|
-
"version": "5.1.
|
|
4
|
+
"version": "5.1.7-alpha.13",
|
|
5
5
|
"homepage": "https://dbgate.org/",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"dbgate"
|
|
18
18
|
],
|
|
19
19
|
"dependencies": {
|
|
20
|
+
"activedirectory2": "^2.1.0",
|
|
20
21
|
"async-lock": "^1.2.4",
|
|
21
22
|
"axios": "^0.21.1",
|
|
22
23
|
"body-parser": "^1.19.0",
|
|
@@ -25,9 +26,9 @@
|
|
|
25
26
|
"compare-versions": "^3.6.0",
|
|
26
27
|
"cors": "^2.8.5",
|
|
27
28
|
"cross-env": "^6.0.3",
|
|
28
|
-
"dbgate-query-splitter": "^4.9.
|
|
29
|
-
"dbgate-sqltree": "^5.1.
|
|
30
|
-
"dbgate-tools": "^5.1.
|
|
29
|
+
"dbgate-query-splitter": "^4.9.3",
|
|
30
|
+
"dbgate-sqltree": "^5.1.7-alpha.13",
|
|
31
|
+
"dbgate-tools": "^5.1.7-alpha.13",
|
|
31
32
|
"debug": "^4.3.4",
|
|
32
33
|
"diff": "^5.0.0",
|
|
33
34
|
"diff2html": "^3.4.13",
|
|
@@ -42,6 +43,7 @@
|
|
|
42
43
|
"is-electron": "^2.2.1",
|
|
43
44
|
"js-yaml": "^4.1.0",
|
|
44
45
|
"json-stable-stringify": "^1.0.1",
|
|
46
|
+
"jsonwebtoken": "^8.5.1",
|
|
45
47
|
"line-reader": "^0.4.0",
|
|
46
48
|
"lodash": "^4.17.21",
|
|
47
49
|
"ncp": "^2.0.0",
|
|
@@ -57,6 +59,8 @@
|
|
|
57
59
|
"start": "env-cmd node src/index.js --listen-api",
|
|
58
60
|
"start:portal": "env-cmd -f env/portal/.env node src/index.js --listen-api",
|
|
59
61
|
"start:singledb": "env-cmd -f env/singledb/.env node src/index.js --listen-api",
|
|
62
|
+
"start:auth": "env-cmd -f env/auth/.env node src/index.js --listen-api",
|
|
63
|
+
"start:dblogin": "env-cmd -f env/dblogin/.env node src/index.js --listen-api",
|
|
60
64
|
"start:filedb": "env-cmd node src/index.js /home/jena/test/chinook/Chinook.db --listen-api",
|
|
61
65
|
"start:singleconn": "env-cmd node src/index.js --server localhost --user root --port 3307 --engine mysql@dbgate-plugin-mysql --password test --listen-api",
|
|
62
66
|
"ts": "tsc",
|
|
@@ -65,7 +69,7 @@
|
|
|
65
69
|
"devDependencies": {
|
|
66
70
|
"@types/fs-extra": "^9.0.11",
|
|
67
71
|
"@types/lodash": "^4.14.149",
|
|
68
|
-
"dbgate-types": "^5.1.
|
|
72
|
+
"dbgate-types": "^5.1.7-alpha.13",
|
|
69
73
|
"env-cmd": "^10.1.0",
|
|
70
74
|
"node-loader": "^1.0.2",
|
|
71
75
|
"nodemon": "^2.0.2",
|
|
@@ -75,6 +79,7 @@
|
|
|
75
79
|
},
|
|
76
80
|
"optionalDependencies": {
|
|
77
81
|
"better-sqlite3": "7.6.2",
|
|
82
|
+
"oracledb": "^5.5.0",
|
|
78
83
|
"msnodesqlv8": "^2.6.0"
|
|
79
84
|
}
|
|
80
85
|
}
|
package/src/controllers/apps.js
CHANGED
|
@@ -58,7 +58,7 @@ module.exports = {
|
|
|
58
58
|
|
|
59
59
|
refreshFiles_meta: true,
|
|
60
60
|
async refreshFiles({ folder }) {
|
|
61
|
-
socket.emitChanged(
|
|
61
|
+
socket.emitChanged('app-files-changed', { app: folder });
|
|
62
62
|
},
|
|
63
63
|
|
|
64
64
|
refreshFolders_meta: true,
|
|
@@ -69,7 +69,7 @@ module.exports = {
|
|
|
69
69
|
deleteFile_meta: true,
|
|
70
70
|
async deleteFile({ folder, file, fileType }) {
|
|
71
71
|
await fs.unlink(path.join(appdir(), folder, `${file}.${fileType}`));
|
|
72
|
-
socket.emitChanged(
|
|
72
|
+
socket.emitChanged('app-files-changed', { app: folder });
|
|
73
73
|
this.emitChangedDbApp(folder);
|
|
74
74
|
},
|
|
75
75
|
|
|
@@ -79,7 +79,7 @@ module.exports = {
|
|
|
79
79
|
path.join(path.join(appdir(), folder), `${file}.${fileType}`),
|
|
80
80
|
path.join(path.join(appdir(), folder), `${newFile}.${fileType}`)
|
|
81
81
|
);
|
|
82
|
-
socket.emitChanged(
|
|
82
|
+
socket.emitChanged('app-files-changed', { app: folder });
|
|
83
83
|
this.emitChangedDbApp(folder);
|
|
84
84
|
},
|
|
85
85
|
|
|
@@ -95,7 +95,7 @@ module.exports = {
|
|
|
95
95
|
if (!folder) throw new Error('Missing folder parameter');
|
|
96
96
|
await fs.rmdir(path.join(appdir(), folder), { recursive: true });
|
|
97
97
|
socket.emitChanged(`app-folders-changed`);
|
|
98
|
-
socket.emitChanged(
|
|
98
|
+
socket.emitChanged('app-files-changed', { app: folder });
|
|
99
99
|
socket.emitChanged('used-apps-changed');
|
|
100
100
|
},
|
|
101
101
|
|
|
@@ -219,7 +219,7 @@ module.exports = {
|
|
|
219
219
|
|
|
220
220
|
await fs.writeFile(file, JSON.stringify(json, undefined, 2));
|
|
221
221
|
|
|
222
|
-
socket.emitChanged(
|
|
222
|
+
socket.emitChanged('app-files-changed', { app: appFolder });
|
|
223
223
|
socket.emitChanged('used-apps-changed');
|
|
224
224
|
},
|
|
225
225
|
|
|
@@ -271,7 +271,7 @@ module.exports = {
|
|
|
271
271
|
const file = path.join(appdir(), appFolder, fileName);
|
|
272
272
|
if (!(await fs.exists(file))) {
|
|
273
273
|
await fs.writeFile(file, JSON.stringify(content, undefined, 2));
|
|
274
|
-
socket.emitChanged(
|
|
274
|
+
socket.emitChanged('app-files-changed', { app: appFolder });
|
|
275
275
|
socket.emitChanged('used-apps-changed');
|
|
276
276
|
return true;
|
|
277
277
|
}
|
|
@@ -5,6 +5,7 @@ const { archivedir, clearArchiveLinksCache, resolveArchiveFolder } = require('..
|
|
|
5
5
|
const socket = require('../utility/socket');
|
|
6
6
|
const { saveFreeTableData } = require('../utility/freeTableStorage');
|
|
7
7
|
const loadFilesRecursive = require('../utility/loadFilesRecursive');
|
|
8
|
+
const getJslFileName = require('../utility/getJslFileName');
|
|
8
9
|
|
|
9
10
|
module.exports = {
|
|
10
11
|
folders_meta: true,
|
|
@@ -74,7 +75,7 @@ module.exports = {
|
|
|
74
75
|
|
|
75
76
|
refreshFiles_meta: true,
|
|
76
77
|
async refreshFiles({ folder }) {
|
|
77
|
-
socket.emitChanged(
|
|
78
|
+
socket.emitChanged('archive-files-changed', { folder });
|
|
78
79
|
},
|
|
79
80
|
|
|
80
81
|
refreshFolders_meta: true,
|
|
@@ -85,7 +86,7 @@ module.exports = {
|
|
|
85
86
|
deleteFile_meta: true,
|
|
86
87
|
async deleteFile({ folder, file, fileType }) {
|
|
87
88
|
await fs.unlink(path.join(resolveArchiveFolder(folder), `${file}.${fileType}`));
|
|
88
|
-
socket.emitChanged(`archive-files-changed
|
|
89
|
+
socket.emitChanged(`archive-files-changed`, { folder });
|
|
89
90
|
},
|
|
90
91
|
|
|
91
92
|
renameFile_meta: true,
|
|
@@ -94,7 +95,7 @@ module.exports = {
|
|
|
94
95
|
path.join(resolveArchiveFolder(folder), `${file}.${fileType}`),
|
|
95
96
|
path.join(resolveArchiveFolder(folder), `${newFile}.${fileType}`)
|
|
96
97
|
);
|
|
97
|
-
socket.emitChanged(`archive-files-changed
|
|
98
|
+
socket.emitChanged(`archive-files-changed`, { folder });
|
|
98
99
|
},
|
|
99
100
|
|
|
100
101
|
renameFolder_meta: true,
|
|
@@ -118,7 +119,7 @@ module.exports = {
|
|
|
118
119
|
saveFreeTable_meta: true,
|
|
119
120
|
async saveFreeTable({ folder, file, data }) {
|
|
120
121
|
await saveFreeTableData(path.join(resolveArchiveFolder(folder), `${file}.jsonl`), data);
|
|
121
|
-
socket.emitChanged(`archive-files-changed
|
|
122
|
+
socket.emitChanged(`archive-files-changed`, { folder });
|
|
122
123
|
return true;
|
|
123
124
|
},
|
|
124
125
|
|
|
@@ -146,7 +147,16 @@ module.exports = {
|
|
|
146
147
|
saveText_meta: true,
|
|
147
148
|
async saveText({ folder, file, text }) {
|
|
148
149
|
await fs.writeFile(path.join(resolveArchiveFolder(folder), `${file}.jsonl`), text);
|
|
149
|
-
socket.emitChanged(`archive-files-changed
|
|
150
|
+
socket.emitChanged(`archive-files-changed`, { folder });
|
|
151
|
+
return true;
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
saveJslData_meta: true,
|
|
155
|
+
async saveJslData({ folder, file, jslid }) {
|
|
156
|
+
const source = getJslFileName(jslid);
|
|
157
|
+
const target = path.join(resolveArchiveFolder(folder), `${file}.jsonl`);
|
|
158
|
+
await fs.copyFile(source, target);
|
|
159
|
+
socket.emitChanged(`archive-files-changed`, { folder });
|
|
150
160
|
return true;
|
|
151
161
|
},
|
|
152
162
|
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
const axios = require('axios');
|
|
2
|
+
const jwt = require('jsonwebtoken');
|
|
3
|
+
const getExpressPath = require('../utility/getExpressPath');
|
|
4
|
+
const uuidv1 = require('uuid/v1');
|
|
5
|
+
const { getLogins } = require('../utility/hasPermission');
|
|
6
|
+
const AD = require('activedirectory2').promiseWrapper;
|
|
7
|
+
|
|
8
|
+
const tokenSecret = uuidv1();
|
|
9
|
+
|
|
10
|
+
function shouldAuthorizeApi() {
|
|
11
|
+
const logins = getLogins();
|
|
12
|
+
return !!process.env.OAUTH_AUTH || !!process.env.AD_URL || (!!logins && !process.env.BASIC_AUTH);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function getTokenLifetime() {
|
|
16
|
+
return process.env.TOKEN_LIFETIME || '1d';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function unauthorizedResponse(req, res, text) {
|
|
20
|
+
// if (req.path == getExpressPath('/config/get-settings')) {
|
|
21
|
+
// return res.json({});
|
|
22
|
+
// }
|
|
23
|
+
// if (req.path == getExpressPath('/connections/list')) {
|
|
24
|
+
// return res.json([]);
|
|
25
|
+
// }
|
|
26
|
+
return res.sendStatus(401).send(text);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function authMiddleware(req, res, next) {
|
|
30
|
+
const SKIP_AUTH_PATHS = ['/config/get', '/auth/oauth-token', '/auth/login', '/stream'];
|
|
31
|
+
|
|
32
|
+
if (!shouldAuthorizeApi()) {
|
|
33
|
+
return next();
|
|
34
|
+
}
|
|
35
|
+
let skipAuth = !!SKIP_AUTH_PATHS.find(x => req.path == getExpressPath(x));
|
|
36
|
+
|
|
37
|
+
const authHeader = req.headers.authorization;
|
|
38
|
+
if (!authHeader) {
|
|
39
|
+
if (skipAuth) {
|
|
40
|
+
return next();
|
|
41
|
+
}
|
|
42
|
+
return unauthorizedResponse(req, res, 'missing authorization header');
|
|
43
|
+
}
|
|
44
|
+
const token = authHeader.split(' ')[1];
|
|
45
|
+
try {
|
|
46
|
+
const decoded = jwt.verify(token, tokenSecret);
|
|
47
|
+
req.user = decoded;
|
|
48
|
+
return next();
|
|
49
|
+
} catch (err) {
|
|
50
|
+
if (skipAuth) {
|
|
51
|
+
return next();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
console.log('Sending invalid token error', err.message);
|
|
55
|
+
|
|
56
|
+
return unauthorizedResponse(req, res, 'invalid token');
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = {
|
|
61
|
+
oauthToken_meta: true,
|
|
62
|
+
async oauthToken(params) {
|
|
63
|
+
const { redirectUri, code } = params;
|
|
64
|
+
|
|
65
|
+
const resp = await axios.default.post(
|
|
66
|
+
`${process.env.OAUTH_TOKEN}`,
|
|
67
|
+
`grant_type=authorization_code&code=${encodeURIComponent(code)}&redirect_uri=${encodeURIComponent(
|
|
68
|
+
redirectUri
|
|
69
|
+
)}&client_id=${process.env.OAUTH_CLIENT_ID}&client_secret=${process.env.OAUTH_CLIENT_SECRET}`
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const { access_token, refresh_token } = resp.data;
|
|
73
|
+
|
|
74
|
+
const payload = jwt.decode(access_token);
|
|
75
|
+
|
|
76
|
+
console.log('User payload returned from OAUTH:', payload);
|
|
77
|
+
|
|
78
|
+
const login = process.env.OAUTH_LOGIN_FIELD ? payload[process.env.OAUTH_LOGIN_FIELD] : 'oauth';
|
|
79
|
+
|
|
80
|
+
if (
|
|
81
|
+
process.env.OAUTH_ALLOWED_LOGINS &&
|
|
82
|
+
!process.env.OAUTH_ALLOWED_LOGINS.split(',').find(x => x.toLowerCase().trim() == login.toLowerCase().trim())
|
|
83
|
+
) {
|
|
84
|
+
return { error: `Username ${login} not allowed to log in` };
|
|
85
|
+
}
|
|
86
|
+
if (access_token) {
|
|
87
|
+
return {
|
|
88
|
+
accessToken: jwt.sign({ login }, tokenSecret, { expiresIn: getTokenLifetime() }),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return { error: 'Token not found' };
|
|
93
|
+
},
|
|
94
|
+
login_meta: true,
|
|
95
|
+
async login(params) {
|
|
96
|
+
const { login, password } = params;
|
|
97
|
+
|
|
98
|
+
if (process.env.AD_URL) {
|
|
99
|
+
const adConfig = {
|
|
100
|
+
url: process.env.AD_URL,
|
|
101
|
+
baseDN: process.env.AD_BASEDN,
|
|
102
|
+
username: process.env.AD_USERNAME,
|
|
103
|
+
password: process.env.AD_PASSOWRD,
|
|
104
|
+
};
|
|
105
|
+
const ad = new AD(adConfig);
|
|
106
|
+
try {
|
|
107
|
+
const res = await ad.authenticate(login, password);
|
|
108
|
+
if (!res) {
|
|
109
|
+
return { error: 'Login failed' };
|
|
110
|
+
}
|
|
111
|
+
if (
|
|
112
|
+
process.env.AD_ALLOWED_LOGINS &&
|
|
113
|
+
!process.env.AD_ALLOWED_LOGINS.split(',').find(x => x.toLowerCase().trim() == login.toLowerCase().trim())
|
|
114
|
+
) {
|
|
115
|
+
return { error: `Username ${login} not allowed to log in` };
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
accessToken: jwt.sign({ login }, tokenSecret, { expiresIn: getTokenLifetime() }),
|
|
119
|
+
};
|
|
120
|
+
} catch (err) {
|
|
121
|
+
console.log('Failed active directory authentization', err.message);
|
|
122
|
+
return {
|
|
123
|
+
error: err.message,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const logins = getLogins();
|
|
129
|
+
if (!logins) {
|
|
130
|
+
return { error: 'Logins not configured' };
|
|
131
|
+
}
|
|
132
|
+
const foundLogin = logins.find(x => x.login == login)
|
|
133
|
+
if (foundLogin && foundLogin.password == password) {
|
|
134
|
+
return {
|
|
135
|
+
accessToken: jwt.sign({ login }, tokenSecret, { expiresIn: getTokenLifetime() }),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
return { error: 'Invalid credentials' };
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
authMiddleware,
|
|
142
|
+
shouldAuthorizeApi,
|
|
143
|
+
};
|
|
@@ -28,18 +28,27 @@ module.exports = {
|
|
|
28
28
|
get_meta: true,
|
|
29
29
|
async get(_params, req) {
|
|
30
30
|
const logins = getLogins();
|
|
31
|
-
const login =
|
|
31
|
+
const login =
|
|
32
|
+
req && req.user
|
|
33
|
+
? req.user.login
|
|
34
|
+
: logins
|
|
35
|
+
? logins.find(x => x.login == (req && req.auth && req.auth.user))
|
|
36
|
+
: null;
|
|
32
37
|
const permissions = login ? login.permissions : process.env.PERMISSIONS;
|
|
33
38
|
|
|
34
39
|
return {
|
|
35
40
|
runAsPortal: !!connections.portalConnections,
|
|
36
|
-
|
|
41
|
+
singleDbConnection: connections.singleDbConnection,
|
|
42
|
+
singleConnection: connections.singleConnection,
|
|
37
43
|
// hideAppEditor: !!process.env.HIDE_APP_EDITOR,
|
|
38
44
|
allowShellConnection: platformInfo.allowShellConnection,
|
|
39
45
|
allowShellScripting: platformInfo.allowShellScripting,
|
|
40
46
|
isDocker: platformInfo.isDocker,
|
|
41
47
|
permissions,
|
|
42
48
|
login,
|
|
49
|
+
oauth: process.env.OAUTH_AUTH,
|
|
50
|
+
oauthLogout: process.env.OAUTH_LOGOUT,
|
|
51
|
+
isLoginForm: !!process.env.AD_URL || (!!logins && !process.env.BASIC_AUTH),
|
|
43
52
|
...currentVersion,
|
|
44
53
|
};
|
|
45
54
|
},
|
|
@@ -2,6 +2,7 @@ const path = require('path');
|
|
|
2
2
|
const { fork } = require('child_process');
|
|
3
3
|
const _ = require('lodash');
|
|
4
4
|
const fs = require('fs-extra');
|
|
5
|
+
const crypto = require('crypto');
|
|
5
6
|
|
|
6
7
|
const { datadir, filesdir } = require('../utility/directories');
|
|
7
8
|
const socket = require('../utility/socket');
|
|
@@ -15,6 +16,8 @@ const { safeJsonParse } = require('dbgate-tools');
|
|
|
15
16
|
const platformInfo = require('../utility/platformInfo');
|
|
16
17
|
const { connectionHasPermission, testConnectionPermission } = require('../utility/hasPermission');
|
|
17
18
|
|
|
19
|
+
let volatileConnections = {};
|
|
20
|
+
|
|
18
21
|
function getNamedArgs() {
|
|
19
22
|
const res = {};
|
|
20
23
|
for (let i = 0; i < process.argv.length; i++) {
|
|
@@ -49,6 +52,7 @@ function getPortalCollections() {
|
|
|
49
52
|
server: process.env[`SERVER_${id}`],
|
|
50
53
|
user: process.env[`USER_${id}`],
|
|
51
54
|
password: process.env[`PASSWORD_${id}`],
|
|
55
|
+
passwordMode: process.env[`PASSWORD_MODE_${id}`],
|
|
52
56
|
port: process.env[`PORT_${id}`],
|
|
53
57
|
databaseUrl: process.env[`URL_${id}`],
|
|
54
58
|
useDatabaseUrl: !!process.env[`URL_${id}`],
|
|
@@ -62,6 +66,7 @@ function getPortalCollections() {
|
|
|
62
66
|
displayName: process.env[`LABEL_${id}`],
|
|
63
67
|
isReadOnly: process.env[`READONLY_${id}`],
|
|
64
68
|
databases: process.env[`DBCONFIG_${id}`] ? safeJsonParse(process.env[`DBCONFIG_${id}`]) : null,
|
|
69
|
+
parent: process.env[`PARENT_${id}`] || undefined,
|
|
65
70
|
|
|
66
71
|
// SSH tunnel
|
|
67
72
|
useSshTunnel: process.env[`USE_SSH_${id}`],
|
|
@@ -125,9 +130,10 @@ function getPortalCollections() {
|
|
|
125
130
|
|
|
126
131
|
return null;
|
|
127
132
|
}
|
|
133
|
+
|
|
128
134
|
const portalConnections = getPortalCollections();
|
|
129
135
|
|
|
130
|
-
function
|
|
136
|
+
function getSingleDbConnection() {
|
|
131
137
|
if (process.env.SINGLE_CONNECTION && process.env.SINGLE_DATABASE) {
|
|
132
138
|
// @ts-ignore
|
|
133
139
|
const connection = portalConnections.find(x => x._id == process.env.SINGLE_CONNECTION);
|
|
@@ -151,12 +157,31 @@ function getSingleDatabase() {
|
|
|
151
157
|
return null;
|
|
152
158
|
}
|
|
153
159
|
|
|
154
|
-
|
|
160
|
+
function getSingleConnection() {
|
|
161
|
+
if (getSingleDbConnection()) return null;
|
|
162
|
+
if (process.env.SINGLE_CONNECTION) {
|
|
163
|
+
// @ts-ignore
|
|
164
|
+
const connection = portalConnections.find(x => x._id == process.env.SINGLE_CONNECTION);
|
|
165
|
+
if (connection) {
|
|
166
|
+
return connection;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// @ts-ignore
|
|
170
|
+
const arg0 = (portalConnections || []).find(x => x._id == 'argv');
|
|
171
|
+
if (arg0) {
|
|
172
|
+
return arg0;
|
|
173
|
+
}
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const singleDbConnection = getSingleDbConnection();
|
|
178
|
+
const singleConnection = getSingleConnection();
|
|
155
179
|
|
|
156
180
|
module.exports = {
|
|
157
181
|
datastore: null,
|
|
158
182
|
opened: [],
|
|
159
|
-
|
|
183
|
+
singleDbConnection,
|
|
184
|
+
singleConnection,
|
|
160
185
|
portalConnections,
|
|
161
186
|
|
|
162
187
|
async _init() {
|
|
@@ -198,6 +223,36 @@ module.exports = {
|
|
|
198
223
|
});
|
|
199
224
|
},
|
|
200
225
|
|
|
226
|
+
saveVolatile_meta: true,
|
|
227
|
+
async saveVolatile({ conid, user, password, test }) {
|
|
228
|
+
const old = await this.getCore({ conid });
|
|
229
|
+
const res = {
|
|
230
|
+
...old,
|
|
231
|
+
_id: crypto.randomUUID(),
|
|
232
|
+
password,
|
|
233
|
+
passwordMode: undefined,
|
|
234
|
+
unsaved: true,
|
|
235
|
+
};
|
|
236
|
+
if (old.passwordMode == 'askUser') {
|
|
237
|
+
res.user = user;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (test) {
|
|
241
|
+
const testRes = await this.test(res);
|
|
242
|
+
if (testRes.msgtype == 'connected') {
|
|
243
|
+
volatileConnections[res._id] = res;
|
|
244
|
+
return {
|
|
245
|
+
...res,
|
|
246
|
+
msgtype: 'connected',
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
return testRes;
|
|
250
|
+
} else {
|
|
251
|
+
volatileConnections[res._id] = res;
|
|
252
|
+
return res;
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
|
|
201
256
|
save_meta: true,
|
|
202
257
|
async save(connection) {
|
|
203
258
|
if (portalConnections) return;
|
|
@@ -228,6 +283,14 @@ module.exports = {
|
|
|
228
283
|
return res;
|
|
229
284
|
},
|
|
230
285
|
|
|
286
|
+
batchChangeFolder_meta: true,
|
|
287
|
+
async batchChangeFolder({ folder, newFolder }, req) {
|
|
288
|
+
// const updated = await this.datastore.find(x => x.parent == folder);
|
|
289
|
+
const res = await this.datastore.updateAll(x => (x.parent == folder ? { ...x, parent: newFolder } : x));
|
|
290
|
+
socket.emitChanged('connection-list-changed');
|
|
291
|
+
return res;
|
|
292
|
+
},
|
|
293
|
+
|
|
231
294
|
updateDatabase_meta: true,
|
|
232
295
|
async updateDatabase({ conid, database, values }, req) {
|
|
233
296
|
if (portalConnections) return;
|
|
@@ -257,6 +320,10 @@ module.exports = {
|
|
|
257
320
|
|
|
258
321
|
async getCore({ conid, mask = false }) {
|
|
259
322
|
if (!conid) return null;
|
|
323
|
+
const volatile = volatileConnections[conid];
|
|
324
|
+
if (volatile) {
|
|
325
|
+
return volatile;
|
|
326
|
+
}
|
|
260
327
|
if (portalConnections) {
|
|
261
328
|
const res = portalConnections.find(x => x._id == conid) || null;
|
|
262
329
|
return mask && !platformInfo.allowShellConnection ? maskConnection(res) : res;
|
|
@@ -27,6 +27,7 @@ const { createTwoFilesPatch } = require('diff');
|
|
|
27
27
|
const diff2htmlPage = require('../utility/diff2htmlPage');
|
|
28
28
|
const processArgs = require('../utility/processArgs');
|
|
29
29
|
const { testConnectionPermission } = require('../utility/hasPermission');
|
|
30
|
+
const { MissingCredentialsError } = require('../utility/exceptions');
|
|
30
31
|
|
|
31
32
|
module.exports = {
|
|
32
33
|
/** @type {import('dbgate-types').OpenedDatabaseConnection[]} */
|
|
@@ -42,19 +43,19 @@ module.exports = {
|
|
|
42
43
|
const existing = this.opened.find(x => x.conid == conid && x.database == database);
|
|
43
44
|
if (!existing) return;
|
|
44
45
|
existing.structure = structure;
|
|
45
|
-
socket.emitChanged(
|
|
46
|
+
socket.emitChanged('database-structure-changed', { conid, database });
|
|
46
47
|
},
|
|
47
48
|
handle_structureTime(conid, database, { analysedTime }) {
|
|
48
49
|
const existing = this.opened.find(x => x.conid == conid && x.database == database);
|
|
49
50
|
if (!existing) return;
|
|
50
51
|
existing.analysedTime = analysedTime;
|
|
51
|
-
socket.emitChanged(`database-status-changed
|
|
52
|
+
socket.emitChanged(`database-status-changed`, { conid, database });
|
|
52
53
|
},
|
|
53
54
|
handle_version(conid, database, { version }) {
|
|
54
55
|
const existing = this.opened.find(x => x.conid == conid && x.database == database);
|
|
55
56
|
if (!existing) return;
|
|
56
57
|
existing.serverVersion = version;
|
|
57
|
-
socket.emitChanged(`database-server-version-changed
|
|
58
|
+
socket.emitChanged(`database-server-version-changed`, { conid, database });
|
|
58
59
|
},
|
|
59
60
|
|
|
60
61
|
handle_error(conid, database, props) {
|
|
@@ -72,7 +73,7 @@ module.exports = {
|
|
|
72
73
|
if (!existing) return;
|
|
73
74
|
if (existing.status && status && existing.status.counter > status.counter) return;
|
|
74
75
|
existing.status = status;
|
|
75
|
-
socket.emitChanged(`database-status-changed
|
|
76
|
+
socket.emitChanged(`database-status-changed`, { conid, database });
|
|
76
77
|
},
|
|
77
78
|
|
|
78
79
|
handle_ping() {},
|
|
@@ -81,6 +82,9 @@ module.exports = {
|
|
|
81
82
|
const existing = this.opened.find(x => x.conid == conid && x.database == database);
|
|
82
83
|
if (existing) return existing;
|
|
83
84
|
const connection = await connections.getCore({ conid });
|
|
85
|
+
if (connection.passwordMode == 'askPassword' || connection.passwordMode == 'askUser') {
|
|
86
|
+
throw new MissingCredentialsError({ conid, passwordMode: connection.passwordMode });
|
|
87
|
+
}
|
|
84
88
|
const subprocess = fork(global['API_PACKAGE'] || process.argv[1], [
|
|
85
89
|
'--is-forked-api',
|
|
86
90
|
'--start-process',
|
|
@@ -313,7 +317,7 @@ module.exports = {
|
|
|
313
317
|
},
|
|
314
318
|
structure: existing.structure,
|
|
315
319
|
};
|
|
316
|
-
socket.emitChanged(`database-status-changed
|
|
320
|
+
socket.emitChanged(`database-status-changed`, { conid, database });
|
|
317
321
|
}
|
|
318
322
|
},
|
|
319
323
|
|
package/src/controllers/files.js
CHANGED
|
@@ -49,7 +49,7 @@ module.exports = {
|
|
|
49
49
|
async delete({ folder, file }, req) {
|
|
50
50
|
if (!hasPermission(`files/${folder}/write`, req)) return false;
|
|
51
51
|
await fs.unlink(path.join(filesdir(), folder, file));
|
|
52
|
-
socket.emitChanged(`files-changed
|
|
52
|
+
socket.emitChanged(`files-changed`, { folder });
|
|
53
53
|
socket.emitChanged(`all-files-changed`);
|
|
54
54
|
return true;
|
|
55
55
|
},
|
|
@@ -58,7 +58,7 @@ module.exports = {
|
|
|
58
58
|
async rename({ folder, file, newFile }, req) {
|
|
59
59
|
if (!hasPermission(`files/${folder}/write`, req)) return false;
|
|
60
60
|
await fs.rename(path.join(filesdir(), folder, file), path.join(filesdir(), folder, newFile));
|
|
61
|
-
socket.emitChanged(`files-changed
|
|
61
|
+
socket.emitChanged(`files-changed`, { folder });
|
|
62
62
|
socket.emitChanged(`all-files-changed`);
|
|
63
63
|
return true;
|
|
64
64
|
},
|
|
@@ -66,7 +66,7 @@ module.exports = {
|
|
|
66
66
|
refresh_meta: true,
|
|
67
67
|
async refresh({ folders }, req) {
|
|
68
68
|
for (const folder of folders) {
|
|
69
|
-
socket.emitChanged(`files-changed
|
|
69
|
+
socket.emitChanged(`files-changed`, { folder });
|
|
70
70
|
socket.emitChanged(`all-files-changed`);
|
|
71
71
|
}
|
|
72
72
|
return true;
|
|
@@ -76,7 +76,7 @@ module.exports = {
|
|
|
76
76
|
async copy({ folder, file, newFile }, req) {
|
|
77
77
|
if (!hasPermission(`files/${folder}/write`, req)) return false;
|
|
78
78
|
await fs.copyFile(path.join(filesdir(), folder, file), path.join(filesdir(), folder, newFile));
|
|
79
|
-
socket.emitChanged(`files-changed
|
|
79
|
+
socket.emitChanged(`files-changed`, { folder });
|
|
80
80
|
socket.emitChanged(`all-files-changed`);
|
|
81
81
|
return true;
|
|
82
82
|
},
|
|
@@ -112,13 +112,13 @@ module.exports = {
|
|
|
112
112
|
if (!hasPermission(`archive/write`, req)) return false;
|
|
113
113
|
const dir = resolveArchiveFolder(folder.substring('archive:'.length));
|
|
114
114
|
await fs.writeFile(path.join(dir, file), serialize(format, data));
|
|
115
|
-
socket.emitChanged(`archive-files-changed
|
|
115
|
+
socket.emitChanged(`archive-files-changed`, { folder: folder.substring('archive:'.length) });
|
|
116
116
|
return true;
|
|
117
117
|
} else if (folder.startsWith('app:')) {
|
|
118
118
|
if (!hasPermission(`apps/write`, req)) return false;
|
|
119
119
|
const app = folder.substring('app:'.length);
|
|
120
120
|
await fs.writeFile(path.join(appdir(), app, file), serialize(format, data));
|
|
121
|
-
socket.emitChanged(`app-files-changed
|
|
121
|
+
socket.emitChanged(`app-files-changed`, { app });
|
|
122
122
|
socket.emitChanged('used-apps-changed');
|
|
123
123
|
apps.emitChangedDbApp(folder);
|
|
124
124
|
return true;
|
|
@@ -129,7 +129,7 @@ module.exports = {
|
|
|
129
129
|
await fs.mkdir(dir);
|
|
130
130
|
}
|
|
131
131
|
await fs.writeFile(path.join(dir, file), serialize(format, data));
|
|
132
|
-
socket.emitChanged(`files-changed
|
|
132
|
+
socket.emitChanged(`files-changed`, { folder });
|
|
133
133
|
socket.emitChanged(`all-files-changed`);
|
|
134
134
|
if (folder == 'shell') {
|
|
135
135
|
scheduler.reload();
|