diff --git a/src/middlewares/userContext.ts b/src/middlewares/userContext.ts index 6bcf8a7..64073e2 100644 --- a/src/middlewares/userContext.ts +++ b/src/middlewares/userContext.ts @@ -37,6 +37,10 @@ export const userContextMiddleware = async ( /** * User context middleware for SSE/MCP endpoints * Extracts user from URL path parameter and sets user context + * + * SECURITY: For user-scoped routes (/:user/...), this middleware validates + * that the user is authenticated via JWT, OAuth, or Bearer token and that + * the authenticated user matches the requested username in the URL. */ export const sseUserContextMiddleware = async ( req: Request, @@ -60,19 +64,42 @@ export const sseUserContextMiddleware = async ( }; if (username) { - // For user-scoped routes, set the user context - // Note: In a real implementation, you should validate the user exists - // and has proper permissions - const user: IUser = { - username, - password: '', - isAdmin: false, // TODO: Should be retrieved from user database - }; + // SECURITY FIX: For user-scoped routes, authenticate the request + // and validate that the authenticated user matches the requested username + + // Try to authenticate via Bearer token (OAuth or configured bearer key) + const rawAuthHeader = Array.isArray(req.headers.authorization) + ? req.headers.authorization[0] + : req.headers.authorization; + const bearerUser = resolveOAuthUserFromAuthHeader(rawAuthHeader); - userContextService.setCurrentUser(user); - attachCleanupHandlers(); - console.log(`User context set for SSE/MCP endpoint: ${username}`); + if (bearerUser) { + // Authenticated via OAuth bearer token + // Verify the authenticated user matches the requested username + if (bearerUser.username !== username) { + res.status(403).json({ + error: 'forbidden', + error_description: `Authenticated user '${bearerUser.username}' cannot access resources for user '${username}'`, + }); + return; + } + + userContextService.setCurrentUser(bearerUser); + attachCleanupHandlers(); + console.log(`OAuth user context set for SSE/MCP endpoint: ${bearerUser.username}`); + } else { + // SECURITY: No valid authentication provided for user-scoped route + // User-scoped routes require authentication to prevent impersonation + cleanup(); + res.status(401).json({ + error: 'unauthorized', + error_description: 'Authentication required for user-scoped MCP endpoints. Please provide valid credentials via Authorization header.', + }); + return; + } } else { + // Global route (no user in path) + // Still check for OAuth bearer authentication if provided const rawAuthHeader = Array.isArray(req.headers.authorization) ? req.headers.authorization[0] : req.headers.authorization; diff --git a/src/services/sseService.ts b/src/services/sseService.ts index ec7bf5c..88dee77 100644 --- a/src/services/sseService.ts +++ b/src/services/sseService.ts @@ -5,7 +5,7 @@ import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'; import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js'; import { deleteMcpServer, getMcpServer } from './mcpService.js'; -import { loadSettings } from '../config/index.js'; +import { loadSettings, loadOriginalSettings } from '../config/index.js'; import config from '../config/index.js'; import { UserContextService } from './userContextService.js'; import { RequestContextService } from './requestContextService.js'; @@ -31,7 +31,16 @@ type BearerAuthResult = }; const validateBearerAuth = (req: Request): BearerAuthResult => { - const settings = loadSettings(); + // SECURITY FIX: Use loadOriginalSettings() to bypass user filtering + // This ensures enableBearerAuth configuration is always read correctly + // and not removed by DataServicex.filterSettings() for unauthenticated users + const settings = loadOriginalSettings(); + + // Handle case where settings might be undefined (e.g., in tests) + if (!settings) { + return { valid: true }; + } + const routingConfig = settings.systemConfig?.routing || { enableGlobalRoute: true, enableGroupNameRoute: true,