mirror of
https://github.com/open-webui/open-webui.git
synced 2025-12-22 17:25:25 +00:00
fix: MCP OAuth 2.1 token exchange and multi-node propagation (#20076)
* sequential * zero default * fix * fix: preserve absolute paths in sqlite+sqlcipher URLs Previously, the connection logic incorrectly stripped the leading slash from `sqlite+sqlcipher` paths, forcibly converting absolute paths (e.g., `sqlite+sqlcipher:////app/data.db`) into relative paths (which became `app/data.db`). This caused database initialization failures when using absolute paths, such as with Docker volume mounts. This change removes the slash-stripping logic, ensuring that absolute path conventions (starting with `/`) are respected while maintaining support for relative paths (which do not start with `/`). * fix: MCP OAuth 2.1 token exchange and multi-node propagation Fix two MCP OAuth 2.1 bugs affecting tool server authentication: 1. Token exchange failing with duplicate credentials (#19823) - Removed explicit client_id/client_secret passing in handle_callback() - Authlib already has credentials configured during add_client(), passing them again caused concatenation (e.g., "ID1,ID1") and 401 errors - Added token validation to detect missing access_token and provide clear error messages instead of cryptic database constraint errors 2. OAuth clients not propagating across multi-node setups (#19901) - Updated get_client() and get_client_info() to auto-lazy-load OAuth clients from the Redis-synced TOOL_SERVER_CONNECTIONS config - Clients are now instantiated on-demand on any node that needs them Fixes #19823, #19901 * Update db.py * Update wrappers.py
This commit is contained in:
parent
f826d3ed75
commit
ef43e81f9a
1 changed files with 20 additions and 11 deletions
|
|
@ -577,10 +577,14 @@ class OAuthClientManager:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def get_client(self, client_id):
|
def get_client(self, client_id):
|
||||||
|
if client_id not in self.clients:
|
||||||
|
self.ensure_client_from_config(client_id)
|
||||||
client = self.clients.get(client_id)
|
client = self.clients.get(client_id)
|
||||||
return client["client"] if client else None
|
return client["client"] if client else None
|
||||||
|
|
||||||
def get_client_info(self, client_id):
|
def get_client_info(self, client_id):
|
||||||
|
if client_id not in self.clients:
|
||||||
|
self.ensure_client_from_config(client_id)
|
||||||
client = self.clients.get(client_id)
|
client = self.clients.get(client_id)
|
||||||
return client["client_info"] if client else None
|
return client["client_info"] if client else None
|
||||||
|
|
||||||
|
|
@ -786,16 +790,20 @@ class OAuthClientManager:
|
||||||
try:
|
try:
|
||||||
client_info = self.get_client_info(client_id)
|
client_info = self.get_client_info(client_id)
|
||||||
|
|
||||||
auth_params = {}
|
# Note: Do NOT pass client_id/client_secret explicitly here.
|
||||||
if (
|
# The Authlib client already has these configured during add_client().
|
||||||
client_info
|
# Passing them again causes Authlib to concatenate them (e.g., "ID1,ID1"),
|
||||||
and hasattr(client_info, "client_id")
|
# which results in 401 errors from the token endpoint. (Fix for #19823)
|
||||||
and hasattr(client_info, "client_secret")
|
token = await client.authorize_access_token(request)
|
||||||
):
|
|
||||||
auth_params["client_id"] = client_info.client_id
|
# Validate that we received a proper token response
|
||||||
auth_params["client_secret"] = client_info.client_secret
|
# If token exchange failed (e.g., 401), we may get an error response instead
|
||||||
|
if token and not token.get("access_token"):
|
||||||
token = await client.authorize_access_token(request, **auth_params)
|
error_desc = token.get("error_description", token.get("error", "Unknown error"))
|
||||||
|
error_message = f"Token exchange failed: {error_desc}"
|
||||||
|
log.error(f"Invalid token response for client_id {client_id}: {token}")
|
||||||
|
token = None
|
||||||
|
|
||||||
if token:
|
if token:
|
||||||
try:
|
try:
|
||||||
# Add timestamp for tracking
|
# Add timestamp for tracking
|
||||||
|
|
@ -825,7 +833,8 @@ class OAuthClientManager:
|
||||||
error_message = "Failed to store OAuth session server-side"
|
error_message = "Failed to store OAuth session server-side"
|
||||||
log.error(f"Failed to store OAuth session server-side: {e}")
|
log.error(f"Failed to store OAuth session server-side: {e}")
|
||||||
else:
|
else:
|
||||||
error_message = "Failed to obtain OAuth token"
|
if not error_message:
|
||||||
|
error_message = "Failed to obtain OAuth token"
|
||||||
log.warning(error_message)
|
log.warning(error_message)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_message = _build_oauth_callback_error_message(e)
|
error_message = _build_oauth_callback_error_message(e)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue