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.
- package/package.json +6 -6
- package/src/auth/authProvider.js +3 -0
- package/src/auth/storageAuthProvider.js +37 -0
- package/src/controllers/auth.js +80 -2
- package/src/controllers/config.js +12 -3
- package/src/controllers/connections.js +10 -3
- package/src/controllers/databaseConnections.js +9 -0
- package/src/controllers/mcpAdmin.js +156 -0
- package/src/controllers/storage.js +26 -17
- package/src/controllers/storageDb.js +6 -0
- package/src/currentVersion.js +2 -2
- package/src/main.js +15 -0
- package/src/mcp.js +1719 -0
- package/src/proc/databaseConnectionProcess.js +10 -0
- package/src/storageModel.js +4 -0
- package/src/utility/envtools.js +5 -0
- package/src/utility/mcpAuth.js +137 -0
|
@@ -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,
|
package/src/storageModel.js
CHANGED
package/src/utility/envtools.js
CHANGED
|
@@ -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
|
+
};
|