dbgate-api-premium 7.2.1 → 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.
@@ -210,6 +210,15 @@ function waitStructure() {
210
210
  });
211
211
  }
212
212
 
213
+ async function handleGetStructure({ msgid }) {
214
+ await waitStructure();
215
+ process.send({
216
+ msgtype: 'response',
217
+ msgid,
218
+ structure: serializeJsTypesForJsonStringify(analysedStructure),
219
+ });
220
+ }
221
+
213
222
  function resolveAnalysedPromises() {
214
223
  for (const [resolve] of afterAnalyseCallbacks) {
215
224
  resolve();
@@ -705,6 +714,7 @@ const messageHandlers = {
705
714
  sqlSelect: handleSqlSelect,
706
715
  exportKeys: handleExportKeys,
707
716
  schemaList: handleSchemaList,
717
+ getStructure: handleGetStructure,
708
718
  executeSessionQuery: handleExecuteSessionQuery,
709
719
  evalJsonScript: handleEvalJsonScript,
710
720
  multiCallMethod: handleMultiCallMethod,
@@ -1047,6 +1047,10 @@ module.exports = {
1047
1047
  {
1048
1048
  "id": -3,
1049
1049
  "name": "superadmin"
1050
+ },
1051
+ {
1052
+ "id": -4,
1053
+ "name": "mcp"
1050
1054
  }
1051
1055
  ]
1052
1056
  },
@@ -3,6 +3,10 @@ const _ = require('lodash');
3
3
  const { safeJsonParse, getDatabaseFileLabel } = require('dbgate-tools');
4
4
  const crypto = require('crypto');
5
5
 
6
+ function isTrueValue(value) {
7
+ return [true, 1, '1', 'true'].includes(value);
8
+ }
9
+
6
10
  function extractConnectionsFromEnv(env) {
7
11
  if (!env?.CONNECTIONS) {
8
12
  return null;
@@ -34,6 +38,7 @@ function extractConnectionsFromEnv(env) {
34
38
  : null),
35
39
  singleDatabase: !!env[`DATABASE_${id}`] || !!env[`FILE_${id}`] || !!env[`APISERVERURL1_${id}`],
36
40
  displayName: env[`LABEL_${id}`],
41
+ mcpEnabled: isTrueValue(env[`MCP_ENABLED_${id}`]),
37
42
  isReadOnly: env[`READONLY_${id}`],
38
43
  databases: env[`DBCONFIG_${id}`] ? safeJsonParse(env[`DBCONFIG_${id}`]) : null,
39
44
  allowedDatabases: env[`ALLOWED_DATABASES_${id}`]?.replace(/\|/g, '\n'),
@@ -0,0 +1,137 @@
1
+ const crypto = require('crypto');
2
+ const getExpressPath = require('./getExpressPath');
3
+
4
+ const MCP_ROLE_ID = -4;
5
+ const MCP_AUTH_MODES = ['token', 'oauth', 'none'];
6
+
7
+ function isLocalHostname(hostname) {
8
+ return ['localhost', '127.0.0.1', '::1'].includes(hostname.toLowerCase());
9
+ }
10
+
11
+ function hashMcpToken(token) {
12
+ return crypto.createHash('sha256').update(String(token)).digest('hex');
13
+ }
14
+
15
+ function isEnabledValue(value) {
16
+ return [true, 1, '1', 'true'].includes(value);
17
+ }
18
+
19
+ function sanitizeMcpConfig(config) {
20
+ const authMode = MCP_AUTH_MODES.includes(config?.authMode) ? config.authMode : 'none';
21
+ return {
22
+ enabled: isEnabledValue(config?.enabled),
23
+ authMode,
24
+ tokenHash: config?.tokenHash || null,
25
+ tokenEncrypted: config?.tokenEncrypted || null,
26
+ tokenSuffix: config?.tokenSuffix || null,
27
+ tokenGeneratedAt: config?.tokenGeneratedAt || null,
28
+ oauthClientId: config?.oauthClientId || null,
29
+ oauthClientSecretHash: config?.oauthClientSecretHash || null,
30
+ oauthClientSecretEncrypted: config?.oauthClientSecretEncrypted || null,
31
+ oauthClientSecretSuffix: config?.oauthClientSecretSuffix || null,
32
+ oauthClientGeneratedAt: config?.oauthClientGeneratedAt || null,
33
+ };
34
+ }
35
+
36
+ async function readMcpConfig() {
37
+ if (process.env.STORAGE_DATABASE) {
38
+ const storage = require('../controllers/storage');
39
+ return sanitizeMcpConfig((await storage.readConfig({ group: 'mcp' })) || {});
40
+ }
41
+
42
+ const token = process.env.MCP_TOKEN?.trim();
43
+ return {
44
+ enabled: !!token,
45
+ authMode: 'token',
46
+ tokenHash: token ? hashMcpToken(token) : null,
47
+ tokenEncrypted: null,
48
+ tokenSuffix: token ? token.slice(-6) : null,
49
+ tokenGeneratedAt: null,
50
+ oauthClientId: null,
51
+ oauthClientSecretHash: null,
52
+ oauthClientSecretEncrypted: null,
53
+ oauthClientSecretSuffix: null,
54
+ oauthClientGeneratedAt: null,
55
+ };
56
+ }
57
+
58
+ function getPublicBaseUrl(req) {
59
+ const configuredUrl = process.env.MCP_PUBLIC_URL?.trim();
60
+ if (configuredUrl) {
61
+ let parsed;
62
+ try {
63
+ parsed = new URL(configuredUrl);
64
+ } catch (error) {
65
+ throw new Error('DBGM-00000 MCP_PUBLIC_URL must be a valid HTTP(S) origin');
66
+ }
67
+ if (
68
+ !['http:', 'https:'].includes(parsed.protocol) ||
69
+ parsed.username ||
70
+ parsed.password ||
71
+ parsed.pathname !== '/' ||
72
+ parsed.search ||
73
+ parsed.hash
74
+ ) {
75
+ throw new Error(
76
+ 'DBGM-00000 MCP_PUBLIC_URL must be an HTTP(S) origin without a path, credentials, query, or fragment'
77
+ );
78
+ }
79
+ if (parsed.protocol !== 'https:' && !isLocalHostname(parsed.hostname)) {
80
+ throw new Error('DBGM-00000 MCP_PUBLIC_URL must use HTTPS for a non-local origin');
81
+ }
82
+ return parsed.origin;
83
+ }
84
+
85
+ const protocol = req?.protocol || (req?.socket?.encrypted ? 'https' : 'http');
86
+ const host = req?.headers?.host || `localhost:${process.env.PORT || 3000}`;
87
+ if (!['http', 'https'].includes(protocol)) {
88
+ throw new Error('DBGM-00000 Could not determine a valid MCP public protocol');
89
+ }
90
+ let requestUrl;
91
+ try {
92
+ requestUrl = new URL(`${protocol}://${host}`);
93
+ } catch (error) {
94
+ throw new Error('DBGM-00000 Could not determine a valid MCP public URL');
95
+ }
96
+ if (requestUrl.username || requestUrl.password || requestUrl.pathname !== '/' || requestUrl.search || requestUrl.hash) {
97
+ throw new Error('DBGM-00000 Could not determine a valid MCP public URL');
98
+ }
99
+ if (requestUrl.protocol !== 'https:' && !isLocalHostname(requestUrl.hostname)) {
100
+ throw new Error('DBGM-00000 MCP OAuth requires HTTPS for a non-local public URL');
101
+ }
102
+ return requestUrl.origin;
103
+ }
104
+
105
+ function getMcpResourceUrl(req) {
106
+ return `${getPublicBaseUrl(req)}${getExpressPath('/mcp')}`;
107
+ }
108
+
109
+ function setMcpIdentity(req, details = {}) {
110
+ req.user = {
111
+ ...details,
112
+ amoid: 'mcp',
113
+ roleId: MCP_ROLE_ID,
114
+ licenseUid: details.licenseUid || 'mcp',
115
+ login: details.login || 'mcp',
116
+ tokenUse: 'mcp',
117
+ };
118
+ }
119
+
120
+ function safeTokenHashEquals(token, expectedHash) {
121
+ if (!token || !expectedHash) return false;
122
+ const actual = Buffer.from(hashMcpToken(token), 'hex');
123
+ const expected = Buffer.from(expectedHash, 'hex');
124
+ return actual.length === expected.length && crypto.timingSafeEqual(actual, expected);
125
+ }
126
+
127
+ module.exports = {
128
+ MCP_ROLE_ID,
129
+ MCP_AUTH_MODES,
130
+ hashMcpToken,
131
+ readMcpConfig,
132
+ sanitizeMcpConfig,
133
+ getPublicBaseUrl,
134
+ getMcpResourceUrl,
135
+ setMcpIdentity,
136
+ safeTokenHashEquals,
137
+ };