diff --git a/gateway-proxy/gateway_proxy_user_integration.py b/gateway-proxy/gateway_proxy_user_integration.py new file mode 100644 index 0000000..f25946f --- /dev/null +++ b/gateway-proxy/gateway_proxy_user_integration.py @@ -0,0 +1,147 @@ +""" +Integration instructions for user management into gateway_proxy.py +================================================================== + +STEP 1: Add import at the top of gateway_proxy.py (after line 32): +------------------------------------------------------------------- +from user_management import user_manager +from user_routes import ( + create_user, list_users, get_user, delete_user, toggle_user, + generate_api_key, revoke_api_key, set_mcp_access +) + + +STEP 2: Modify validate_bearer_token() function (around line 254): +------------------------------------------------------------------ +Replace the validate_bearer_token function with: + +def validate_bearer_token(request: Request) -> dict | None: + auth_header = request.headers.get("Authorization", "") + if not auth_header.startswith("Bearer "): + return None + token = auth_header[7:] + + # Check static API key first (persistent, survives restarts) + if STATIC_API_KEY and token == STATIC_API_KEY: + return {"client_id": "static", "scope": "mcp:tools", "user": "static"} + + # Check user API key + user_info = user_manager.validate_api_key(token) + if user_info: + return { + "client_id": "api-key", + "scope": "mcp:tools", + "user": user_info['username'], + "user_id": user_info['user_id'], + "mcp_allowed": user_info['mcp_allowed'], + "mcp_blocked": user_info['mcp_blocked'] + } + + # Check OAuth token + token_hash = _hash(token) + info = ACCESS_TOKENS.get(token_hash) + if not info: + return None + if info["expires_at"] < time.time(): + del ACCESS_TOKENS[token_hash] + return None + return info + + +STEP 3: Modify handle_mcp() function to filter by user MCP access: +------------------------------------------------------------------ +In the handle_mcp() function, after getting token info, add: + +# Check MCP access control +if token_info and 'user' in token_info: + # Check if user can access this backend + user = token_info.get('user') + mcp_allowed = token_info.get('mcp_allowed', []) + mcp_blocked = token_info.get('mcp_blocked', []) + + # Parse which backend is being accessed from params + params = body.get('params', {}) + backend_name = params.get('backend') # Should be set by frontend + + if backend_name: + if backend_name in mcp_blocked: + return JSONResponse( + {'error': 'access_denied', 'message': f'Access to {backend_name} is blocked'}, + status_code=403 + ) + if mcp_allowed and backend_name not in mcp_allowed: + return JSONResponse( + {'error': 'access_denied', 'message': f'You do not have access to {backend_name}'}, + status_code=403 + ) + + +STEP 4: Add new routes before building the routes list (before line 1012): +-------------------------------------------------------------------------- +Add these route definitions right before the routes list: + +# User management routes +Route("/users", create_user, methods=["POST"]), +Route("/users", list_users, methods=["GET"]), +Route("/users/{username}", get_user, methods=["GET"]), +Route("/users/{username}", delete_user, methods=["DELETE"]), +Route("/users/{username}/enable", toggle_user, methods=["PATCH"]), +Route("/users/{username}/keys", generate_api_key, methods=["POST"]), +Route("/users/{username}/mcp-access", set_mcp_access, methods=["PUT"]), +Route("/keys/revoke", revoke_api_key, methods=["POST"]), + + +STEP 5: Update the routes list (around line 1012): +-------------------------------------------------- +Insert the user management routes into the routes list: + +routes = [ + # ... existing routes ... + + # User management (admin endpoints) + Route("/users", create_user, methods=["POST"]), + Route("/users", list_users, methods=["GET"]), + Route("/users/{username}", get_user, methods=["GET"]), + Route("/users/{username}", delete_user, methods=["DELETE"]), + Route("/users/{username}/enable", toggle_user, methods=["PATCH"]), + Route("/users/{username}/keys", generate_api_key, methods=["POST"]), + Route("/users/{username}/mcp-access", set_mcp_access, methods=["PUT"]), + Route("/keys/revoke", revoke_api_key, methods=["POST"]), + + # ... rest of routes ... +] + + +STEP 6: Update docker-compose.yml volume mounts: +----------------------------------------------- +Add a data volume to persist user database: + +volumes: + - ./data:/data + - ./gateway-proxy:/app + +And in the service, add: + +volumes: + - ./data:/data + + +STEP 7: Optional - Create initial admin user during startup: +---------------------------------------------------------- +In the startup() function, add: + +async def startup(): + # Initialize admin user if not exists + try: + users = user_manager.list_users() + if not any(u['username'] == 'admin' for u in users): + user_manager.create_user('admin', email='admin@localhost', description='Administrator') + admin_key = user_manager.generate_api_key('admin', key_name='initial-setup') + logger.info(f"Created admin user. Initial API key: {admin_key}") + except Exception as e: + logger.warning(f"Could not initialize admin user: {e}") + + # ... rest of startup ... +""" + +print(__doc__)