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.
@@ -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=test
9
- PORT_mysql=3307
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.5",
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.2",
29
- "dbgate-sqltree": "^5.1.5",
30
- "dbgate-tools": "^5.1.5",
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.5",
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
  }
@@ -58,7 +58,7 @@ module.exports = {
58
58
 
59
59
  refreshFiles_meta: true,
60
60
  async refreshFiles({ folder }) {
61
- socket.emitChanged(`app-files-changed-${folder}`);
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(`app-files-changed-${folder}`);
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(`app-files-changed-${folder}`);
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(`app-files-changed-${folder}`);
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(`app-files-changed-${appFolder}`);
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(`app-files-changed-${appFolder}`);
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(`archive-files-changed-${folder}`);
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-${folder}`);
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-${folder}`);
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-${folder}`);
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-${folder}`);
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 = logins ? logins.find(x => x.login == (req.auth && req.auth.user)) : null;
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
- singleDatabase: connections.singleDatabase,
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 getSingleDatabase() {
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
- const singleDatabase = getSingleDatabase();
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
- singleDatabase,
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(`database-structure-changed-${conid}-${database}`);
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-${conid}-${database}`);
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-${conid}-${database}`);
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-${conid}-${database}`);
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-${conid}-${database}`);
320
+ socket.emitChanged(`database-status-changed`, { conid, database });
317
321
  }
318
322
  },
319
323
 
@@ -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-${folder}`);
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-${folder}`);
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-${folder}`);
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-${folder}`);
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-${folder.substring('archive:'.length)}`);
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-${app}`);
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-${folder}`);
132
+ socket.emitChanged(`files-changed`, { folder });
133
133
  socket.emitChanged(`all-files-changed`);
134
134
  if (folder == 'shell') {
135
135
  scheduler.reload();