dbgate-api-premium 7.2.0 → 7.2.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dbgate-api-premium",
3
3
  "main": "src/index.js",
4
- "version": "7.2.0",
4
+ "version": "7.2.2",
5
5
  "homepage": "https://www.dbgate.io/",
6
6
  "repository": {
7
7
  "type": "git",
@@ -30,11 +30,11 @@
30
30
  "compare-versions": "^3.6.0",
31
31
  "cors": "^2.8.5",
32
32
  "cross-env": "^6.0.3",
33
- "dbgate-datalib": "7.2.0",
33
+ "dbgate-datalib": "7.2.2",
34
34
  "dbgate-query-splitter": "^4.12.0",
35
- "dbgate-rest": "7.2.0",
36
- "dbgate-sqltree": "7.2.0",
37
- "dbgate-tools": "7.2.0",
35
+ "dbgate-rest": "7.2.2",
36
+ "dbgate-sqltree": "7.2.2",
37
+ "dbgate-tools": "7.2.2",
38
38
  "debug": "^4.3.4",
39
39
  "diff": "^5.0.0",
40
40
  "diff2html": "^3.4.13",
@@ -96,7 +96,7 @@
96
96
  "@types/fs-extra": "^9.0.11",
97
97
  "@types/jest": "^30.0.0",
98
98
  "@types/lodash": "^4.14.149",
99
- "dbgate-types": "7.2.0",
99
+ "dbgate-types": "7.2.2",
100
100
  "env-cmd": "^10.1.0",
101
101
  "jest": "^30.4.2",
102
102
  "jsdoc-to-markdown": "^9.0.5",
@@ -348,6 +348,9 @@ function getDefaultAuthProvider() {
348
348
 
349
349
  function getAuthProviderFromReq(req) {
350
350
  const authProviderId = req?.auth?.amoid || req?.user?.amoid;
351
+ if (authProviderId == 'mcp') {
352
+ return require('./storageAuthProvider').getMcpAuthProvider();
353
+ }
351
354
  return getAuthProviderById(authProviderId) ?? getDefaultAuthProvider();
352
355
  }
353
356
 
@@ -90,6 +90,42 @@ class SuperadminAuthProvider extends AuthProviderBase {
90
90
  }
91
91
  }
92
92
 
93
+ class McpAuthProvider extends AuthProviderBase {
94
+ amoid = 'mcp';
95
+ skipInList = true;
96
+
97
+ async getCurrentPermissions() {
98
+ if (!process.env.STORAGE_DATABASE) return null;
99
+ return [...(getPredefinedPermissions('mcp') ?? []), ...(await storageReadRolePermissions(-4))];
100
+ }
101
+
102
+ async getCurrentDatabasePermissions() {
103
+ if (!process.env.STORAGE_DATABASE) return [];
104
+ return resolvePermissionConnectionIds(await readComplexRolePermissions(-4, 'role_databases'));
105
+ }
106
+
107
+ async getCurrentTablePermissions() {
108
+ if (!process.env.STORAGE_DATABASE) return [];
109
+ return resolvePermissionConnectionIds(await readComplexRolePermissions(-4, 'role_tables'));
110
+ }
111
+
112
+ async getCurrentFilePermissions() {
113
+ if (!process.env.STORAGE_DATABASE) return [];
114
+ return readComplexRolePermissions(-4, 'role_files');
115
+ }
116
+
117
+ async checkCurrentConnectionPermission(_req, conid) {
118
+ if (!process.env.STORAGE_DATABASE) return true;
119
+ return storageCheckRoleConnectionAccess(-4, conid);
120
+ }
121
+ }
122
+
123
+ const mcpAuthProvider = new McpAuthProvider();
124
+
125
+ function getMcpAuthProvider() {
126
+ return mcpAuthProvider;
127
+ }
128
+
93
129
  class StorageProviderBase extends AuthProviderBase {
94
130
  constructor(config) {
95
131
  super();
@@ -724,5 +760,6 @@ function createStorageAuthProvider(config) {
724
760
  module.exports = {
725
761
  createStorageAuthProvider,
726
762
  SuperadminAuthProvider,
763
+ getMcpAuthProvider,
727
764
  getBuiltinRoleIdFromRequest,
728
765
  };
@@ -28,10 +28,17 @@ const {
28
28
  markUserAsActive,
29
29
  markLoginAsLoggedOut,
30
30
  } = require('../utility/loginchecker');
31
+ const {
32
+ readMcpConfig,
33
+ getPublicBaseUrl,
34
+ getMcpResourceUrl,
35
+ setMcpIdentity,
36
+ safeTokenHashEquals,
37
+ } = require('../utility/mcpAuth');
31
38
 
32
39
  const logger = getLogger('auth');
33
40
 
34
- function unauthorizedResponse(req, res, text) {
41
+ function unauthorizedResponse(req, res, text, mcpAuthMode = null) {
35
42
  // if (req.path == getExpressPath('/config/get-settings')) {
36
43
  // return res.json({});
37
44
  // }
@@ -39,10 +46,70 @@ function unauthorizedResponse(req, res, text) {
39
46
  // return res.json([]);
40
47
  // }
41
48
 
49
+ if (req.path == getExpressPath('/mcp')) {
50
+ res.setHeader(
51
+ 'WWW-Authenticate',
52
+ mcpAuthMode == 'oauth'
53
+ ? `Bearer resource_metadata="${getPublicBaseUrl(req)}${getExpressPath('/.well-known/oauth-protected-resource')}"`
54
+ : 'Bearer'
55
+ );
56
+ }
57
+
42
58
  return res.status(401).send(text);
43
59
  }
44
60
 
45
- function authMiddleware(req, res, next) {
61
+ async function authenticateMcpRequest(req, res, next) {
62
+ const config = await readMcpConfig();
63
+ if (!config.enabled) {
64
+ return res.status(403).send('DBGM-00000 MCP server is disabled');
65
+ }
66
+
67
+ if (config.authMode == 'none') {
68
+ setMcpIdentity(req);
69
+ return next();
70
+ }
71
+
72
+ const authHeader = req.headers.authorization;
73
+ const [scheme, token] = authHeader?.split(' ') ?? [];
74
+ if (scheme != 'Bearer' || !token) {
75
+ return unauthorizedResponse(req, res, 'missing or invalid MCP authorization header', config.authMode);
76
+ }
77
+
78
+ if (config.authMode == 'token') {
79
+ if (!safeTokenHashEquals(token, config.tokenHash)) {
80
+ return unauthorizedResponse(req, res, 'invalid MCP token', config.authMode);
81
+ }
82
+ setMcpIdentity(req, { licenseUid: 'mcp-token', login: 'mcp-token' });
83
+ return next();
84
+ }
85
+
86
+ try {
87
+ const decoded = jwt.verify(token, getTokenSecret());
88
+ if (
89
+ decoded.tokenUse != 'mcp' ||
90
+ decoded.aud != getMcpResourceUrl(req) ||
91
+ decoded.oauthClientId != config.oauthClientId
92
+ ) {
93
+ throw new Error('Invalid MCP token claims');
94
+ }
95
+ setMcpIdentity(req, decoded);
96
+ markUserAsActive(decoded.licenseUid, token);
97
+ return next();
98
+ } catch (err) {
99
+ return unauthorizedResponse(req, res, 'invalid MCP OAuth token', config.authMode);
100
+ }
101
+ }
102
+
103
+ async function authMiddleware(req, res, next) {
104
+ if (req.path == getExpressPath('/mcp')) {
105
+ try {
106
+ return await authenticateMcpRequest(req, res, next);
107
+ } catch (err) {
108
+ logger.error(extractErrorLogData(err), 'DBGM-00000 Error loading MCP authentication configuration');
109
+ return res.status(500).send('DBGM-00000 Could not load MCP authentication configuration');
110
+ }
111
+ }
112
+
46
113
  const SKIP_AUTH_PATHS = [
47
114
  '/config/get',
48
115
  '/config/logout',
@@ -58,6 +125,14 @@ function authMiddleware(req, res, next) {
58
125
  '/storage/request-password-reset',
59
126
  '/storage/reset-password',
60
127
  '/auth/get-providers',
128
+ '/.well-known/oauth-protected-resource',
129
+ '/.well-known/oauth-protected-resource/mcp',
130
+ '/.well-known/oauth-authorization-server',
131
+ '/.well-known/openid-configuration',
132
+ '/authorize',
133
+ '/token',
134
+ '/mcp/oauth/authorize',
135
+ '/mcp/oauth/token',
61
136
  '/connections/dblogin-web',
62
137
  '/connections/dblogin-app',
63
138
  '/connections/dblogin-auth',
@@ -92,6 +167,9 @@ function authMiddleware(req, res, next) {
92
167
  const token = authHeader.split(' ')[1];
93
168
  try {
94
169
  const decoded = jwt.verify(token, getTokenSecret());
170
+ if (decoded.tokenUse == 'mcp') {
171
+ throw new Error('MCP token cannot be used for the DbGate API');
172
+ }
95
173
  req.user = decoded;
96
174
  markUserAsActive(decoded.licenseUid, token);
97
175
 
@@ -16,6 +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 dbgateApi = require('../shell');
19
20
  const { getAuthProxyUrl, tryToGetRefreshedLicense } = require('../utility/authProxy');
20
21
  const { getPublicHardwareFingerprint } = require('../utility/hardwareFingerprint');
21
22
  const { extractErrorMessage } = require('dbgate-tools');
@@ -31,6 +32,12 @@ const {
31
32
  const lock = new AsyncLock();
32
33
  let cachedSettingsValue = null;
33
34
 
35
+ function coerceSettingsEnvValue(raw) {
36
+ if (raw === 'true') return true; // so booleans can be enabled AND disabled via env
37
+ if (raw === 'false') return false;
38
+ return raw; // leave everything else as a string (numeric settings are parsed on read)
39
+ }
40
+
34
41
  module.exports = {
35
42
  // settingsValue: {},
36
43
 
@@ -172,9 +179,12 @@ module.exports = {
172
179
  }
173
180
  for (const envVar in process.env) {
174
181
  if (envVar.startsWith('SETTINGS_')) {
175
- const key = envVar.substring('SETTINGS_'.length);
176
- if (!res[key]) {
177
- res[key] = process.env[envVar];
182
+ // dot-safe: allow `SETTINGS_tabGroup__showServerName` on platforms that forbid '.'
183
+ // in env-var names; `__` -> '.'. Existing dotted names are unaffected (no settings
184
+ // key contains '__').
185
+ const key = envVar.substring('SETTINGS_'.length).replace(/__/g, '.');
186
+ if (!(key in res)) {
187
+ res[key] = coerceSettingsEnvValue(process.env[envVar]);
178
188
  }
179
189
  }
180
190
  }
@@ -438,4 +448,20 @@ module.exports = {
438
448
 
439
449
  return true;
440
450
  },
451
+
452
+ createConnectionsAndSettingsZip_meta: true,
453
+ async createConnectionsAndSettingsZip({ db, filePath }, req) {
454
+ const loadedPermissions = await loadPermissionsFromRequest(req);
455
+ if (!hasPermission(`admin/config`, loadedPermissions)) {
456
+ throw new Error('Permission denied: admin/config');
457
+ }
458
+
459
+ if (connections.portalConnections) {
460
+ throw new Error('Not allowed');
461
+ }
462
+
463
+ const exportDb = process.env.STORAGE_DATABASE ? await storage.fillTeamFileContentForExport(db) : db;
464
+ await dbgateApi.zipJsonLinesData(exportDb, filePath);
465
+ return true;
466
+ },
441
467
  };
@@ -489,10 +489,17 @@ module.exports = {
489
489
 
490
490
  if (portalConnections) {
491
491
  const res = portalConnections.find(x => x._id == conid) || null;
492
- return mask && !platformInfo.allowShellConnection ? maskConnection(res) : encryptConnection(res);
492
+ if (res) {
493
+ return mask && !platformInfo.allowShellConnection ? maskConnection(res) : encryptConnection(res);
494
+ }
495
+ if (!(process.send && processArgs.processDisplayName === 'script')) {
496
+ return null;
497
+ }
498
+ }
499
+ if (!portalConnections) {
500
+ const res = await this.datastore.get(conid);
501
+ if (res) return res;
493
502
  }
494
- const res = await this.datastore.get(conid);
495
- if (res) return res;
496
503
 
497
504
  // In a forked runner-script child process, ask the parent for connections that may be
498
505
  // volatile (in-memory only, e.g. ask-for-password). We only do this when
@@ -157,8 +157,15 @@ module.exports = {
157
157
 
158
158
  handle_copyStreamError(conid, database, { copyStreamError }) {
159
159
  const { progressName } = copyStreamError;
160
- const { runid } = progressName;
161
- logger.error(`DBGM-00103 Error in database connection ${conid}, database ${database}: ${copyStreamError}`);
160
+ const runid = progressName?.runid;
161
+ logger.error({ conid, database, copyStreamError }, 'DBGM-00000 Error in database connection copy stream');
162
+ if (!runid) return;
163
+ if (copyStreamError.dbgateCopyStreamErrorReported) return;
164
+ socket.emit(`runner-progress-${runid}`, {
165
+ progressName: progressName?.name,
166
+ status: 'error',
167
+ errorMessage: copyStreamError.message,
168
+ });
162
169
  socket.emit(`runner-done-${runid}`);
163
170
  },
164
171
 
@@ -240,6 +247,15 @@ module.exports = {
240
247
  return newOpened;
241
248
  },
242
249
 
250
+ async ensureStructureLoaded(conid, database) {
251
+ const conn = await this.ensureOpened(conid, database);
252
+ if (conn.isApiConnection || !conn.subprocess) {
253
+ return conn.structure ?? {};
254
+ }
255
+ const response = await this.sendRequest(conn, { msgtype: 'getStructure' });
256
+ return response.structure ?? conn.structure ?? {};
257
+ },
258
+
243
259
  /** @param {import('dbgate-types').OpenedDatabaseConnection} conn */
244
260
  sendRequest(conn, message, additionalData = {}) {
245
261
  const msgid = crypto.randomUUID();
@@ -0,0 +1,156 @@
1
+ const crypto = require('crypto');
2
+ const { isProApp } = require('../utility/checkLicense');
3
+ const { loadPermissionsFromRequest, hasPermission } = require('../utility/hasPermission');
4
+ const { sendToAuditLog } = require('../utility/auditlog');
5
+ const { encryptPasswordString, decryptPasswordString } = require('../utility/crypting');
6
+ const storage = require('./storage');
7
+ const {
8
+ MCP_AUTH_MODES,
9
+ hashMcpToken,
10
+ sanitizeMcpConfig,
11
+ getMcpResourceUrl,
12
+ } = require('../utility/mcpAuth');
13
+
14
+ async function requireMcpAdmin(req) {
15
+ if (!isProApp() || !process.env.STORAGE_DATABASE) {
16
+ throw new Error('DBGM-00000 MCP administration requires Team Premium');
17
+ }
18
+ const permissions = await loadPermissionsFromRequest(req);
19
+ if (!hasPermission('admin/settings', permissions)) {
20
+ throw new Error('DBGM-00000 Permission admin/settings not granted');
21
+ }
22
+ }
23
+
24
+ async function readStoredConfig() {
25
+ return sanitizeMcpConfig((await storage.readConfig({ group: 'mcp' })) || {});
26
+ }
27
+
28
+ function publicConfig(config, req) {
29
+ let mcpUrl = null;
30
+ let mcpUrlError = null;
31
+ let token = null;
32
+ let oauthClientSecret = null;
33
+ try {
34
+ mcpUrl = getMcpResourceUrl(req);
35
+ } catch (error) {
36
+ mcpUrlError = error.message;
37
+ }
38
+ try {
39
+ token = decryptPasswordString(config.tokenEncrypted) || null;
40
+ } catch (error) {
41
+ token = null;
42
+ }
43
+ try {
44
+ oauthClientSecret = decryptPasswordString(config.oauthClientSecretEncrypted) || null;
45
+ } catch (error) {
46
+ oauthClientSecret = null;
47
+ }
48
+ return {
49
+ enabled: config.enabled,
50
+ authMode: config.authMode,
51
+ hasToken: !!config.tokenHash,
52
+ token,
53
+ tokenSuffix: config.tokenSuffix,
54
+ tokenGeneratedAt: config.tokenGeneratedAt,
55
+ mcpUrl,
56
+ mcpUrlError,
57
+ oauthClientId: config.oauthClientId,
58
+ hasOAuthClientSecret: !!config.oauthClientSecretHash,
59
+ oauthClientSecret,
60
+ oauthClientSecretSuffix: config.oauthClientSecretSuffix,
61
+ oauthClientGeneratedAt: config.oauthClientGeneratedAt,
62
+ };
63
+ }
64
+
65
+ async function writeStoredConfig(config) {
66
+ await storage.writeConfig({ group: 'mcp', config });
67
+ }
68
+
69
+ module.exports = {
70
+ getConfig_meta: true,
71
+ async getConfig(_params, req) {
72
+ await requireMcpAdmin(req);
73
+ return publicConfig(await readStoredConfig(), req);
74
+ },
75
+
76
+ updateConfig_meta: true,
77
+ async updateConfig({ enabled, authMode }, req) {
78
+ await requireMcpAdmin(req);
79
+ if (typeof enabled !== 'boolean' || !MCP_AUTH_MODES.includes(authMode)) {
80
+ throw new Error('DBGM-00000 Invalid MCP configuration');
81
+ }
82
+
83
+ const current = await readStoredConfig();
84
+ const updated = { ...current, enabled, authMode };
85
+ await writeStoredConfig(updated);
86
+ sendToAuditLog(req, {
87
+ category: 'admin',
88
+ component: 'MCP',
89
+ action: 'configuration',
90
+ event: 'mcp.configurationChanged',
91
+ severity: 'info',
92
+ detail: { enabled, authMode },
93
+ message: 'MCP configuration changed',
94
+ });
95
+ return publicConfig(updated, req);
96
+ },
97
+
98
+ generateToken_meta: true,
99
+ async generateToken(_params, req) {
100
+ await requireMcpAdmin(req);
101
+ const token = `dbgate_mcp_${crypto.randomBytes(32).toString('base64url')}`;
102
+ const tokenSuffix = token.slice(-6);
103
+ const tokenGeneratedAt = new Date().toISOString();
104
+ const current = await readStoredConfig();
105
+ const updated = {
106
+ ...current,
107
+ tokenHash: hashMcpToken(token),
108
+ tokenEncrypted: encryptPasswordString(token),
109
+ tokenSuffix,
110
+ tokenGeneratedAt,
111
+ };
112
+ await writeStoredConfig(updated);
113
+ sendToAuditLog(req, {
114
+ category: 'admin',
115
+ component: 'MCP',
116
+ action: 'generateToken',
117
+ event: 'mcp.tokenGenerated',
118
+ severity: 'info',
119
+ message: 'MCP token generated',
120
+ });
121
+ return { token, tokenSuffix, tokenGeneratedAt };
122
+ },
123
+
124
+ generateOauthClient_meta: true,
125
+ async generateOauthClient(_params, req) {
126
+ await requireMcpAdmin(req);
127
+ const clientId = `dbgate_mcp_client_${crypto.randomBytes(24).toString('base64url')}`;
128
+ const clientSecret = `dbgate_mcp_secret_${crypto.randomBytes(32).toString('base64url')}`;
129
+ const clientSecretSuffix = clientSecret.slice(-6);
130
+ const clientGeneratedAt = new Date().toISOString();
131
+ const current = await readStoredConfig();
132
+ const updated = {
133
+ ...current,
134
+ oauthClientId: clientId,
135
+ oauthClientSecretHash: hashMcpToken(clientSecret),
136
+ oauthClientSecretEncrypted: encryptPasswordString(clientSecret),
137
+ oauthClientSecretSuffix: clientSecretSuffix,
138
+ oauthClientGeneratedAt: clientGeneratedAt,
139
+ };
140
+ await writeStoredConfig(updated);
141
+ sendToAuditLog(req, {
142
+ category: 'admin',
143
+ component: 'MCP',
144
+ action: 'generateOAuthClient',
145
+ event: 'mcp.oauthClientGenerated',
146
+ severity: 'info',
147
+ message: 'MCP OAuth client credentials generated',
148
+ });
149
+ return {
150
+ clientId,
151
+ clientSecret,
152
+ clientSecretSuffix,
153
+ clientGeneratedAt,
154
+ };
155
+ },
156
+ };
@@ -193,8 +193,12 @@ module.exports = {
193
193
  },
194
194
 
195
195
  readConfig_meta: true,
196
- async readConfig({ group }) {
197
- return await storageReadConfig(group);
196
+ async readConfig({ group }, req) {
197
+ const config = await storageReadConfig(group);
198
+ if (req && group == 'mcp') {
199
+ return _.omit(config, ['tokenHash']);
200
+ }
201
+ return config;
198
202
  },
199
203
 
200
204
  readAuthConfig_meta: true,
@@ -290,7 +294,10 @@ module.exports = {
290
294
  },
291
295
 
292
296
  writeConfig_meta: true,
293
- async writeConfig({ group, config }) {
297
+ async writeConfig({ group, config }, req) {
298
+ if (req && group == 'mcp') {
299
+ throw new Error('DBGM-00000 MCP configuration must be changed through MCP administration');
300
+ }
294
301
  await storageWriteConfig(group, config);
295
302
  await this._init();
296
303
  return null;
@@ -808,7 +815,7 @@ module.exports = {
808
815
  id
809
816
  );
810
817
 
811
- const basePermissions = getPredefinedPermissions(id < 0 && resp[0]?.name ? resp[0]?.name : 'logged-user');
818
+ const basePermissions = getPredefinedPermissions(id < 0 && resp[0]?.name ? resp[0]?.name : 'logged-user') ?? [];
812
819
  const permissions =
813
820
  id == 'new'
814
821
  ? []
@@ -931,18 +938,18 @@ module.exports = {
931
938
 
932
939
  const {
933
940
  name,
934
- usedConnections,
935
- usedUsers,
936
- usedTeamFilesRead,
937
- usedTeamFilesWrite,
938
- usedTeamFilesUse,
939
- permissions,
940
- databasePermissions,
941
- tablePermissions,
942
- filePermissions,
943
- usedTeamFoldersRead,
944
- usedTeamFoldersWrite,
945
- usedTeamFoldersUse,
941
+ usedConnections = [],
942
+ usedUsers = [],
943
+ usedTeamFilesRead = [],
944
+ usedTeamFilesWrite = [],
945
+ usedTeamFilesUse = [],
946
+ permissions = [],
947
+ databasePermissions = [],
948
+ tablePermissions = [],
949
+ filePermissions = [],
950
+ usedTeamFoldersRead = [],
951
+ usedTeamFoldersWrite = [],
952
+ usedTeamFoldersUse = [],
946
953
  } = role;
947
954
  let id = role.id;
948
955
 
@@ -1087,10 +1094,24 @@ module.exports = {
1087
1094
  const role_connections = await storageSelectFmt(`select * from ~role_connections`);
1088
1095
  const user_connections = await storageSelectFmt(`select * from ~user_connections`);
1089
1096
  const user_roles = await storageSelectFmt(`select * from ~user_roles`);
1090
- const config = await storageSelectFmt(`select * from ~config`);
1097
+ const config = (await storageSelectFmt(`select * from ~config`)).filter(
1098
+ item => item.group != 'mcp' || item.key != 'tokenHash'
1099
+ );
1091
1100
  const auth_methods_config = await storageSelectFmt(`select * from ~auth_methods_config`);
1092
1101
  const role_permissions = await storageSelectFmt(`select * from ~role_permissions`);
1093
1102
  const roles = await storageSelectFmt(`select * from ~roles`);
1103
+ const team_folders = await storageSelectFmt(`select * from ~team_folders`);
1104
+ const team_files = (
1105
+ await storageSelectFmt(
1106
+ `select ~team_files.~id, ~team_files.~file_type_id, ~team_file_types.~name as ~type_name, ~team_files.~file_name,
1107
+ ~team_files.~owner_user_id, ~team_files.~metadata, ~team_files.~team_folder_id
1108
+ from ~team_files
1109
+ inner join ~team_file_types on ~team_files.~file_type_id = ~team_file_types.~id`
1110
+ )
1111
+ ).map(file => ({
1112
+ ...file,
1113
+ owner_user_id: null,
1114
+ }));
1094
1115
 
1095
1116
  return {
1096
1117
  connections,
@@ -1104,6 +1125,34 @@ module.exports = {
1104
1125
  auth_methods_config,
1105
1126
  role_permissions,
1106
1127
  roles,
1128
+ team_folders,
1129
+ team_files,
1130
+ };
1131
+ },
1132
+
1133
+ async fillTeamFileContentForExport(db) {
1134
+ if (!db.team_files?.length) {
1135
+ return db;
1136
+ }
1137
+
1138
+ const teamFileIds = _.uniq(db.team_files.map(file => file.id).filter(id => id != null));
1139
+ if (teamFileIds.length == 0) {
1140
+ return db;
1141
+ }
1142
+
1143
+ const fileContentRows = await storageSelectFmt(
1144
+ `select ~id, ~file_content from ~team_files where ~id in (%,v)`,
1145
+ teamFileIds
1146
+ );
1147
+ const fileContentById = _.keyBy(fileContentRows, 'id');
1148
+
1149
+ return {
1150
+ ...db,
1151
+ team_files: db.team_files.map(file => ({
1152
+ ..._.omit(file, ['type_name']),
1153
+ owner_user_id: null,
1154
+ file_content: fileContentById[file.id]?.file_content ?? file.file_content,
1155
+ })),
1107
1156
  };
1108
1157
  },
1109
1158
 
@@ -1121,6 +1170,12 @@ module.exports = {
1121
1170
  .filter(x => x.jsonArray),
1122
1171
  });
1123
1172
  socket.emitChanged('connection-list-changed');
1173
+ if (db.team_files) {
1174
+ socket.emitChanged('team-files-changed');
1175
+ }
1176
+ if (db.team_folders) {
1177
+ socket.emitChanged('team-folders-changed');
1178
+ }
1124
1179
  },
1125
1180
 
1126
1181
  async getUsedEngines() {
@@ -11,6 +11,7 @@ const {
11
11
  const _ = require('lodash');
12
12
  const logger = getLogger('storageDb');
13
13
  const { extractConnectionSslParams } = require('../utility/connectUtility');
14
+ const MCP_ROLE_ID = -4;
14
15
 
15
16
  async function getDbConnectionParams() {
16
17
  const server = process.env.STORAGE_SERVER;
@@ -553,6 +554,10 @@ async function storageCheckUserRoleConnectionAccess(userId, conid) {
553
554
  return respByUser.length > 0 || respByRole.length > 0 || respByLogged.length > 0;
554
555
  }
555
556
 
557
+ async function storageCheckMcpConnectionAccess(req, conid) {
558
+ return storageCheckRoleConnectionAccess(MCP_ROLE_ID, conid);
559
+ }
560
+
556
561
  async function storageCreateTeamFile({ fileType, file, data, ownerUserId, metadata, teamFolderId }) {
557
562
  const [conn, driver] = await getStorageConnection();
558
563
  if (!conn) {
@@ -1121,6 +1126,7 @@ module.exports = {
1121
1126
  resolvePermissionConnectionIds,
1122
1127
  storageCheckRoleConnectionAccess,
1123
1128
  storageCheckUserRoleConnectionAccess,
1129
+ storageCheckMcpConnectionAccess,
1124
1130
  storageCreateTeamFile,
1125
1131
  storageGetExistingFile,
1126
1132
  storageGetTeamFileRoleAccess,
@@ -1,5 +1,5 @@
1
1
 
2
2
  module.exports = {
3
- version: '7.2.0',
4
- buildTime: '2026-06-08T11:54:20.800Z'
3
+ version: '7.2.2',
4
+ buildTime: '2026-07-20T13:18:22.158Z'
5
5
  };