更新bug
This commit is contained in:
@@ -5,6 +5,7 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import socket
|
||||
import threading
|
||||
import time
|
||||
@@ -25,6 +26,23 @@ DEFAULT_SERVER_URL = "http://xchat.zhenyangtang.com.cn"
|
||||
DESKTOP_SYNC_KEY = "wcrpa-v1-H3q9mT7xK2pN8cR5vL4sF6dB1yG0uJ"
|
||||
DESKTOP_CONFIG_PATH = "/api/v1/desktop/config"
|
||||
_LOCK = threading.RLock()
|
||||
_SENSITIVE_NAME_PARTS = (
|
||||
"api_key",
|
||||
"apikey",
|
||||
"key",
|
||||
"token",
|
||||
"secret",
|
||||
"password",
|
||||
"passwd",
|
||||
"authorization",
|
||||
"cookie",
|
||||
"credential",
|
||||
"signature",
|
||||
"auth",
|
||||
)
|
||||
_INLINE_SECRET_RE = re.compile(
|
||||
r"(?i)(--?(?:api[-_]?key|token|secret|password|passwd|authorization|auth)\s*[=:]\s*)([^\s,;]+)"
|
||||
)
|
||||
|
||||
|
||||
class BackendError(RuntimeError):
|
||||
@@ -197,6 +215,96 @@ def normalize_server_url(value: Any) -> str:
|
||||
return url
|
||||
|
||||
|
||||
def _development_mode_enabled(config: dict[str, Any]) -> bool:
|
||||
value = config.get("AI_DEVELOPMENT_MODE", False)
|
||||
if isinstance(value, str):
|
||||
return value.strip().lower() in {"1", "true", "yes", "on"}
|
||||
return bool(value)
|
||||
|
||||
|
||||
def _sensitive_name(name: Any) -> bool:
|
||||
normalized = str(name or "").strip().lower().replace("-", "_")
|
||||
return any(part in normalized for part in _SENSITIVE_NAME_PARTS)
|
||||
|
||||
|
||||
def redact_diagnostic_value(value: Any, *, field_name: str = "") -> Any:
|
||||
"""Return a JSON-safe diagnostic copy with credentials removed."""
|
||||
if _sensitive_name(field_name):
|
||||
return "[已配置,值已隐藏]" if value not in (None, "", [], {}) else "[未配置]"
|
||||
if isinstance(value, dict):
|
||||
return {
|
||||
str(key): redact_diagnostic_value(item, field_name=str(key))
|
||||
for key, item in value.items()
|
||||
}
|
||||
if isinstance(value, list):
|
||||
result = []
|
||||
hide_next = False
|
||||
for item in value:
|
||||
if hide_next:
|
||||
result.append("[值已隐藏]")
|
||||
hide_next = False
|
||||
continue
|
||||
result.append(redact_diagnostic_value(item))
|
||||
if isinstance(item, str) and _sensitive_name(item.lstrip("-")):
|
||||
hide_next = True
|
||||
return result
|
||||
if isinstance(value, str):
|
||||
text = value
|
||||
if re.match(r"(?i)^bearer\s+\S+", text.strip()):
|
||||
return "[认证值已隐藏]"
|
||||
if re.match(r"(?i)^(?:sk|app)-[A-Za-z0-9_.-]{8,}$", text.strip()):
|
||||
return "[密钥值已隐藏]"
|
||||
text = _INLINE_SECRET_RE.sub(r"\1[值已隐藏]", text)
|
||||
if text.startswith(("http://", "https://")):
|
||||
return diagnostic_url(text)
|
||||
return text
|
||||
return value
|
||||
|
||||
|
||||
def diagnostic_url(value: Any) -> str:
|
||||
"""Keep the request destination visible while hiding credentials in its query."""
|
||||
url = str(value or "")
|
||||
try:
|
||||
parsed = urllib.parse.urlsplit(url)
|
||||
if not parsed.scheme or not parsed.netloc:
|
||||
return url
|
||||
hostname = parsed.hostname or ""
|
||||
if ":" in hostname and not hostname.startswith("["):
|
||||
hostname = f"[{hostname}]"
|
||||
netloc = hostname
|
||||
if parsed.port:
|
||||
netloc += f":{parsed.port}"
|
||||
query = urllib.parse.parse_qsl(parsed.query, keep_blank_values=True)
|
||||
safe_query = urllib.parse.urlencode(
|
||||
[
|
||||
(name, "[值已隐藏]" if _sensitive_name(name) else item)
|
||||
for name, item in query
|
||||
]
|
||||
)
|
||||
return urllib.parse.urlunsplit(
|
||||
(parsed.scheme, netloc, parsed.path, safe_query, "")
|
||||
)
|
||||
except (TypeError, ValueError):
|
||||
return url
|
||||
|
||||
|
||||
def _config_diagnostics(
|
||||
config: dict[str, Any], response: dict[str, Any], request_url: str
|
||||
) -> list[str]:
|
||||
if not _development_mode_enabled(config):
|
||||
return []
|
||||
safe_config = redact_diagnostic_value(config)
|
||||
config_json = json.dumps(safe_config, ensure_ascii=False, indent=2, sort_keys=True)
|
||||
return [
|
||||
f"[开发模式] 云端配置请求地址: {diagnostic_url(request_url)}",
|
||||
(
|
||||
f"[开发模式] 云端配置版本: v{int(response.get('version', 0))}"
|
||||
f";更新时间: {response.get('updated_at') or '未提供'}"
|
||||
),
|
||||
f"[开发模式] 获取到的云端配置(敏感值已隐藏):\n{config_json}",
|
||||
]
|
||||
|
||||
|
||||
def is_configured(settings: dict[str, Any] | None = None) -> bool:
|
||||
current = settings or load_settings()
|
||||
return bool(
|
||||
@@ -331,7 +439,7 @@ def logout(*, revoke_remote: bool = True) -> None:
|
||||
|
||||
|
||||
def _apply_config_response(
|
||||
response: dict[str, Any], settings: dict[str, Any]
|
||||
response: dict[str, Any], settings: dict[str, Any], *, request_url: str = ""
|
||||
) -> dict[str, Any]:
|
||||
config = response.get("config")
|
||||
if not isinstance(config, dict):
|
||||
@@ -370,6 +478,7 @@ def _apply_config_response(
|
||||
"local_app_version": APP_VERSION,
|
||||
"update_available": release["update_available"],
|
||||
"force_upgrade": release["force_upgrade"],
|
||||
"diagnostics": _config_diagnostics(config, response, request_url),
|
||||
}
|
||||
|
||||
|
||||
@@ -396,7 +505,11 @@ def sync_config(*, force: bool = False, timeout: float = 10.0) -> dict[str, Any]
|
||||
),
|
||||
timeout=timeout,
|
||||
)
|
||||
return _apply_config_response(response, settings)
|
||||
return _apply_config_response(
|
||||
response,
|
||||
settings,
|
||||
request_url=normalize_server_url(settings["server_url"]) + "/api/v1/config",
|
||||
)
|
||||
except Exception as exc:
|
||||
settings["last_error"] = str(exc)
|
||||
save_settings(settings)
|
||||
@@ -422,7 +535,11 @@ def sync_cloud_config(*, timeout: float = 10.0) -> dict[str, Any]:
|
||||
desktop_sync_key=DESKTOP_SYNC_KEY,
|
||||
timeout=timeout,
|
||||
)
|
||||
return _apply_config_response(response, settings)
|
||||
return _apply_config_response(
|
||||
response,
|
||||
settings,
|
||||
request_url=normalize_server_url(DEFAULT_SERVER_URL) + DESKTOP_CONFIG_PATH,
|
||||
)
|
||||
except Exception as exc:
|
||||
settings["last_error"] = str(exc)
|
||||
save_settings(settings)
|
||||
|
||||
Reference in New Issue
Block a user