feat: encrypt user's client secret

This commit is contained in:
Novice 2025-10-15 14:03:32 +08:00
parent 6405228f3f
commit 979d35d87f
No known key found for this signature in database
GPG Key ID: EE3F68E3105DAAAB
3 changed files with 18 additions and 35 deletions

View File

@ -1012,7 +1012,7 @@ class ToolMCPAuthApi(Resource):
except MCPRefreshTokenError as e: except MCPRefreshTokenError as e:
with session.begin(): with session.begin():
service.clear_provider_credentials(provider=db_provider) service.clear_provider_credentials(provider=db_provider)
raise ValueError(f"Failed to refresh token: {e}") from e raise ValueError(f"Failed to refresh token, please try to authorize again: {e}") from e
except MCPError as e: except MCPError as e:
with session.begin(): with session.begin():
service.clear_provider_credentials(provider=db_provider) service.clear_provider_credentials(provider=db_provider)

View File

@ -189,34 +189,16 @@ class MCPProviderEntity(BaseModel):
return None return None
# Check if we have nested client_information structure # Check if we have nested client_information structure
if "client_information" in credentials: if "client_information" not in credentials:
# Handle nested structure (Authorization Code flow)
client_info_data = credentials["client_information"]
if isinstance(client_info_data, dict):
return OAuthClientInformation.model_validate(client_info_data)
return None return None
client_info_data = credentials["client_information"]
# Handle flat structure (Client Credentials flow) if isinstance(client_info_data, dict):
if "client_id" not in credentials: if "encrypted_client_secret" in client_info_data:
return None client_info_data["client_secret"] = encrypter.decrypt_token(
self.tenant_id, client_info_data["encrypted_client_secret"]
# Build client information from flat structure )
client_info = { return OAuthClientInformation.model_validate(client_info_data)
"client_id": credentials.get("client_id", ""), return None
"client_secret": credentials.get("client_secret", ""),
"client_name": credentials.get("client_name", CLIENT_NAME),
}
# Parse JSON fields if they exist
json_fields = ["redirect_uris", "grant_types", "response_types"]
for field in json_fields:
if field in credentials:
client_info[field] = json.loads(credentials[field])
if "scope" in credentials:
client_info["scope"] = credentials["scope"]
return OAuthClientInformation.model_validate(client_info)
def retrieve_tokens(self) -> OAuthTokens | None: def retrieve_tokens(self) -> OAuthTokens | None:
"""OAuth tokens if available""" """OAuth tokens if available"""

View File

@ -305,7 +305,7 @@ class MCPToolManageService:
if not authed: if not authed:
provider.tools = EMPTY_TOOLS_JSON provider.tools = EMPTY_TOOLS_JSON
self._session.flush() self._session.commit()
def save_oauth_data(self, provider_id: str, tenant_id: str, data: dict[str, Any], data_type: str = "mixed") -> None: def save_oauth_data(self, provider_id: str, tenant_id: str, data: dict[str, Any], data_type: str = "mixed") -> None:
""" """
@ -360,7 +360,7 @@ class MCPToolManageService:
return json.dumps({"content": icon, "background": icon_background}) return json.dumps({"content": icon, "background": icon_background})
return icon return icon
def _encrypt_dict_fields(self, data: dict[str, Any], secret_fields: list[str], tenant_id: str) -> str: def _encrypt_dict_fields(self, data: dict[str, Any], secret_fields: list[str], tenant_id: str) -> dict[str, str]:
"""Encrypt specified fields in a dictionary. """Encrypt specified fields in a dictionary.
Args: Args:
@ -386,12 +386,12 @@ class MCPToolManageService:
) )
encrypted_data = encrypter_instance.encrypt(data) encrypted_data = encrypter_instance.encrypt(data)
return json.dumps(encrypted_data) return encrypted_data
def _prepare_encrypted_dict(self, headers: dict[str, str], tenant_id: str) -> str: def _prepare_encrypted_dict(self, headers: dict[str, str], tenant_id: str) -> str:
"""Encrypt headers and prepare for storage.""" """Encrypt headers and prepare for storage."""
# All headers are treated as secret # All headers are treated as secret
return self._encrypt_dict_fields(headers, list(headers.keys()), tenant_id) return json.dumps(self._encrypt_dict_fields(headers, list(headers.keys()), tenant_id))
def _prepare_auth_headers(self, provider_entity: MCPProviderEntity) -> dict[str, str]: def _prepare_auth_headers(self, provider_entity: MCPProviderEntity) -> dict[str, str]:
"""Prepare headers with OAuth token if available.""" """Prepare headers with OAuth token if available."""
@ -530,11 +530,12 @@ class MCPToolManageService:
# Create a flat structure with all credential data # Create a flat structure with all credential data
credentials_data = { credentials_data = {
"client_id": client_id, "client_id": client_id,
"client_secret": client_secret, "encrypted_client_secret": client_secret,
"client_name": CLIENT_NAME, "client_name": CLIENT_NAME,
"is_dynamic_registration": False, "is_dynamic_registration": False,
} }
# Only client_id and client_secret need encryption # Only client_id and client_secret need encryption
secret_fields = ["client_id", "client_secret"] if client_secret else ["client_id"] secret_fields = ["encrypted_client_secret"] if client_secret else []
return self._encrypt_dict_fields(credentials_data, secret_fields, tenant_id) client_info = self._encrypt_dict_fields(credentials_data, secret_fields, tenant_id)
return json.dumps({"client_information": client_info})