dbgate-api-premium 7.2.1 → 7.2.3
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 +49 -18
- package/src/controllers/storageDb.js +6 -0
- package/src/currentVersion.js +2 -2
- package/src/main.js +15 -0
- package/src/mcp.js +1716 -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
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.
|
|
4
|
+
"version": "7.2.3",
|
|
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.
|
|
33
|
+
"dbgate-datalib": "7.2.3",
|
|
34
34
|
"dbgate-query-splitter": "^4.12.0",
|
|
35
|
-
"dbgate-rest": "7.2.
|
|
36
|
-
"dbgate-sqltree": "7.2.
|
|
37
|
-
"dbgate-tools": "7.2.
|
|
35
|
+
"dbgate-rest": "7.2.3",
|
|
36
|
+
"dbgate-sqltree": "7.2.3",
|
|
37
|
+
"dbgate-tools": "7.2.3",
|
|
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.
|
|
99
|
+
"dbgate-types": "7.2.3",
|
|
100
100
|
"env-cmd": "^10.1.0",
|
|
101
101
|
"jest": "^30.4.2",
|
|
102
102
|
"jsdoc-to-markdown": "^9.0.5",
|
package/src/auth/authProvider.js
CHANGED
|
@@ -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('../controllers/storage').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
|
};
|
package/src/controllers/auth.js
CHANGED
|
@@ -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
|
|
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
|
|
|
@@ -32,6 +32,12 @@ const {
|
|
|
32
32
|
const lock = new AsyncLock();
|
|
33
33
|
let cachedSettingsValue = null;
|
|
34
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
|
+
|
|
35
41
|
module.exports = {
|
|
36
42
|
// settingsValue: {},
|
|
37
43
|
|
|
@@ -173,9 +179,12 @@ module.exports = {
|
|
|
173
179
|
}
|
|
174
180
|
for (const envVar in process.env) {
|
|
175
181
|
if (envVar.startsWith('SETTINGS_')) {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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]);
|
|
179
188
|
}
|
|
180
189
|
}
|
|
181
190
|
}
|
|
@@ -489,10 +489,17 @@ module.exports = {
|
|
|
489
489
|
|
|
490
490
|
if (portalConnections) {
|
|
491
491
|
const res = portalConnections.find(x => x._id == conid) || null;
|
|
492
|
-
|
|
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
|
|
@@ -247,6 +247,15 @@ module.exports = {
|
|
|
247
247
|
return newOpened;
|
|
248
248
|
},
|
|
249
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
|
+
|
|
250
259
|
/** @param {import('dbgate-types').OpenedDatabaseConnection} conn */
|
|
251
260
|
sendRequest(conn, message, additionalData = {}) {
|
|
252
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
|
+
};
|
|
@@ -2,12 +2,16 @@ const fs = require('fs-extra');
|
|
|
2
2
|
const _ = require('lodash');
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const { setAuthProviders, getAuthProviderById } = require('../auth/authProvider');
|
|
5
|
-
const { createStorageAuthProvider, SuperadminAuthProvider } = require('../auth/storageAuthProvider');
|
|
5
|
+
const { createStorageAuthProvider, SuperadminAuthProvider, getMcpAuthProvider } = require('../auth/storageAuthProvider');
|
|
6
6
|
const {
|
|
7
7
|
getStorageConnection,
|
|
8
8
|
getDbConnectionParams,
|
|
9
9
|
storageSelectFmt,
|
|
10
10
|
storageReadUserRolePermissions,
|
|
11
|
+
storageReadRolePermissions: storageDbReadRolePermissions,
|
|
12
|
+
readComplexRolePermissions: storageDbReadComplexRolePermissions,
|
|
13
|
+
resolvePermissionConnectionIds: storageDbResolvePermissionConnectionIds,
|
|
14
|
+
storageCheckMcpConnectionAccess: storageDbCheckMcpConnectionAccess,
|
|
11
15
|
storageReadConfig,
|
|
12
16
|
storageWriteConfig,
|
|
13
17
|
getStorageConnectionError,
|
|
@@ -59,6 +63,24 @@ function mapConnection(connnection) {
|
|
|
59
63
|
let refreshLicenseStarted = false;
|
|
60
64
|
|
|
61
65
|
module.exports = {
|
|
66
|
+
getMcpAuthProvider,
|
|
67
|
+
|
|
68
|
+
async storageReadRolePermissions(roleId) {
|
|
69
|
+
return storageDbReadRolePermissions(roleId);
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
async readComplexRolePermissions(roleId, permissionType) {
|
|
73
|
+
return storageDbReadComplexRolePermissions(roleId, permissionType);
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
async resolvePermissionConnectionIds(permissions) {
|
|
77
|
+
return storageDbResolvePermissionConnectionIds(permissions);
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
async storageCheckMcpConnectionAccess(req, conid) {
|
|
81
|
+
return storageDbCheckMcpConnectionAccess(req, conid);
|
|
82
|
+
},
|
|
83
|
+
|
|
62
84
|
async _init() {
|
|
63
85
|
if (!process.env.STORAGE_DATABASE) {
|
|
64
86
|
return;
|
|
@@ -193,8 +215,12 @@ module.exports = {
|
|
|
193
215
|
},
|
|
194
216
|
|
|
195
217
|
readConfig_meta: true,
|
|
196
|
-
async readConfig({ group }) {
|
|
197
|
-
|
|
218
|
+
async readConfig({ group }, req) {
|
|
219
|
+
const config = await storageReadConfig(group);
|
|
220
|
+
if (req && group == 'mcp') {
|
|
221
|
+
return _.omit(config, ['tokenHash']);
|
|
222
|
+
}
|
|
223
|
+
return config;
|
|
198
224
|
},
|
|
199
225
|
|
|
200
226
|
readAuthConfig_meta: true,
|
|
@@ -290,7 +316,10 @@ module.exports = {
|
|
|
290
316
|
},
|
|
291
317
|
|
|
292
318
|
writeConfig_meta: true,
|
|
293
|
-
async writeConfig({ group, config }) {
|
|
319
|
+
async writeConfig({ group, config }, req) {
|
|
320
|
+
if (req && group == 'mcp') {
|
|
321
|
+
throw new Error('DBGM-00000 MCP configuration must be changed through MCP administration');
|
|
322
|
+
}
|
|
294
323
|
await storageWriteConfig(group, config);
|
|
295
324
|
await this._init();
|
|
296
325
|
return null;
|
|
@@ -808,7 +837,7 @@ module.exports = {
|
|
|
808
837
|
id
|
|
809
838
|
);
|
|
810
839
|
|
|
811
|
-
const basePermissions = getPredefinedPermissions(id < 0 && resp[0]?.name ? resp[0]?.name : 'logged-user');
|
|
840
|
+
const basePermissions = getPredefinedPermissions(id < 0 && resp[0]?.name ? resp[0]?.name : 'logged-user') ?? [];
|
|
812
841
|
const permissions =
|
|
813
842
|
id == 'new'
|
|
814
843
|
? []
|
|
@@ -931,18 +960,18 @@ module.exports = {
|
|
|
931
960
|
|
|
932
961
|
const {
|
|
933
962
|
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,
|
|
963
|
+
usedConnections = [],
|
|
964
|
+
usedUsers = [],
|
|
965
|
+
usedTeamFilesRead = [],
|
|
966
|
+
usedTeamFilesWrite = [],
|
|
967
|
+
usedTeamFilesUse = [],
|
|
968
|
+
permissions = [],
|
|
969
|
+
databasePermissions = [],
|
|
970
|
+
tablePermissions = [],
|
|
971
|
+
filePermissions = [],
|
|
972
|
+
usedTeamFoldersRead = [],
|
|
973
|
+
usedTeamFoldersWrite = [],
|
|
974
|
+
usedTeamFoldersUse = [],
|
|
946
975
|
} = role;
|
|
947
976
|
let id = role.id;
|
|
948
977
|
|
|
@@ -1087,7 +1116,9 @@ module.exports = {
|
|
|
1087
1116
|
const role_connections = await storageSelectFmt(`select * from ~role_connections`);
|
|
1088
1117
|
const user_connections = await storageSelectFmt(`select * from ~user_connections`);
|
|
1089
1118
|
const user_roles = await storageSelectFmt(`select * from ~user_roles`);
|
|
1090
|
-
const config = await storageSelectFmt(`select * from ~config`)
|
|
1119
|
+
const config = (await storageSelectFmt(`select * from ~config`)).filter(
|
|
1120
|
+
item => item.group != 'mcp' || item.key != 'tokenHash'
|
|
1121
|
+
);
|
|
1091
1122
|
const auth_methods_config = await storageSelectFmt(`select * from ~auth_methods_config`);
|
|
1092
1123
|
const role_permissions = await storageSelectFmt(`select * from ~role_permissions`);
|
|
1093
1124
|
const roles = await storageSelectFmt(`select * from ~roles`);
|
|
@@ -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,
|
package/src/currentVersion.js
CHANGED
package/src/main.js
CHANGED
|
@@ -31,6 +31,8 @@ const scheduler = require('./controllers/scheduler');
|
|
|
31
31
|
const queryHistory = require('./controllers/queryHistory');
|
|
32
32
|
const cloud = require('./controllers/cloud');
|
|
33
33
|
const teamFiles = require('./controllers/teamFiles');
|
|
34
|
+
const mcpAdmin = require('./controllers/mcpAdmin');
|
|
35
|
+
const mcp = require('./mcp');
|
|
34
36
|
|
|
35
37
|
const onFinished = require('on-finished');
|
|
36
38
|
const processArgs = require('./utility/processArgs');
|
|
@@ -94,6 +96,7 @@ function start() {
|
|
|
94
96
|
// console.log('process.argv', process.argv);
|
|
95
97
|
|
|
96
98
|
const app = express();
|
|
99
|
+
app.set('trust proxy', 'loopback, linklocal, uniquelocal');
|
|
97
100
|
|
|
98
101
|
const server = http.createServer(app);
|
|
99
102
|
|
|
@@ -175,6 +178,17 @@ function start() {
|
|
|
175
178
|
});
|
|
176
179
|
|
|
177
180
|
app.use(bodyParser.json({ limit: '50mb' }));
|
|
181
|
+
app.use(bodyParser.urlencoded({ extended: false }));
|
|
182
|
+
|
|
183
|
+
app.get(getExpressPath('/.well-known/oauth-protected-resource'), mcp.handleOAuthProtectedResourceMetadata);
|
|
184
|
+
app.get(getExpressPath('/.well-known/oauth-protected-resource/mcp'), mcp.handleOAuthProtectedResourceMetadata);
|
|
185
|
+
app.get(getExpressPath('/.well-known/oauth-authorization-server'), mcp.handleOAuthAuthorizationServerMetadata);
|
|
186
|
+
app.get(getExpressPath('/.well-known/openid-configuration'), mcp.handleOAuthAuthorizationServerMetadata);
|
|
187
|
+
app.get(getExpressPath('/authorize'), mcp.handleOAuthAuthorize);
|
|
188
|
+
app.post(getExpressPath('/token'), mcp.handleOAuthToken);
|
|
189
|
+
app.get(getExpressPath('/mcp/oauth/authorize'), mcp.handleOAuthAuthorize);
|
|
190
|
+
app.post(getExpressPath('/mcp/oauth/token'), mcp.handleOAuthToken);
|
|
191
|
+
app.post(getExpressPath('/mcp'), mcp.handleMcpRequest);
|
|
178
192
|
|
|
179
193
|
app.use(
|
|
180
194
|
getExpressPath('/uploads'),
|
|
@@ -269,6 +283,7 @@ function useAllControllers(app, electron) {
|
|
|
269
283
|
useController(app, electron, '/cloud', cloud);
|
|
270
284
|
useController(app, electron, '/team-files', teamFiles);
|
|
271
285
|
useController(app, electron, '/rest-connections', restConnections);
|
|
286
|
+
useController(app, electron, '/mcp-admin', mcpAdmin);
|
|
272
287
|
}
|
|
273
288
|
|
|
274
289
|
function setElectronSender(electronSender) {
|