yiranxiaohui Claude Opus 4.7 (1M context) commited on
Commit
63bfd99
·
1 Parent(s): c49ad87

refactor(proxy): 代理 URL 不再做密码遮蔽

Browse files

- proxy_service 删除 _mask_proxy_url,get_public 返回原始 URL
- /api/proxy 与 /api/proxy/test 去掉 ***@ 特殊处理
- 设置页去掉显示/隐藏切换,直接展示完整 URL

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

services/api.py CHANGED
@@ -580,15 +580,8 @@ def create_app() -> FastAPI:
580
  @router.post("/api/proxy")
581
  async def update_proxy(body: ProxyUpdateRequest, authorization: str | None = Header(default=None)):
582
  require_auth_key(authorization)
583
-
584
- # If the client echoes back the masked form (contains `***@`), treat it as "no change"
585
- # so the stored password is preserved.
586
- url = body.url
587
- if url is not None and "***@" in url:
588
- url = None
589
-
590
  try:
591
- proxy_config.update(enabled=body.enabled, url=url)
592
  except ValueError as exc:
593
  raise HTTPException(status_code=400, detail={"error": str(exc)}) from exc
594
  return {"proxy": proxy_config.get_public()}
@@ -600,8 +593,7 @@ def create_app() -> FastAPI:
600
  ):
601
  require_auth_key(authorization)
602
  candidate = (body.url or "").strip()
603
- if not candidate or "***@" in candidate:
604
- # Fall back to the stored url when caller omits it or sent the masked form.
605
  candidate = (proxy_config.get().get("url") or "").strip()
606
  if not candidate:
607
  raise HTTPException(status_code=400, detail={"error": "proxy url is required"})
 
580
  @router.post("/api/proxy")
581
  async def update_proxy(body: ProxyUpdateRequest, authorization: str | None = Header(default=None)):
582
  require_auth_key(authorization)
 
 
 
 
 
 
 
583
  try:
584
+ proxy_config.update(enabled=body.enabled, url=body.url)
585
  except ValueError as exc:
586
  raise HTTPException(status_code=400, detail={"error": str(exc)}) from exc
587
  return {"proxy": proxy_config.get_public()}
 
593
  ):
594
  require_auth_key(authorization)
595
  candidate = (body.url or "").strip()
596
+ if not candidate:
 
597
  candidate = (proxy_config.get().get("url") or "").strip()
598
  if not candidate:
599
  raise HTTPException(status_code=400, detail={"error": "proxy url is required"})
services/proxy_service.py CHANGED
@@ -11,7 +11,7 @@ import os
11
  import time
12
  from pathlib import Path
13
  from threading import Lock
14
- from urllib.parse import quote, urlparse, urlunparse
15
 
16
  from curl_cffi.requests import Session
17
 
@@ -37,23 +37,6 @@ def _is_valid_proxy_url(url: str) -> bool:
37
  return bool(parsed.hostname)
38
 
39
 
40
- def _mask_proxy_url(url: str) -> str:
41
- """Return a display-safe version of the proxy URL where password is hidden."""
42
- if not url:
43
- return ""
44
- try:
45
- parsed = urlparse(url)
46
- except Exception:
47
- return url
48
- if not parsed.password:
49
- return url
50
- user = parsed.username or ""
51
- host = parsed.hostname or ""
52
- port = f":{parsed.port}" if parsed.port else ""
53
- netloc = f"{quote(user, safe='')}:***@{host}{port}"
54
- return urlunparse(parsed._replace(netloc=netloc))
55
-
56
-
57
  class ProxyConfig:
58
  def __init__(self, store_file: Path):
59
  self._store_file = store_file
@@ -92,11 +75,10 @@ class ProxyConfig:
92
  return dict(self._state)
93
 
94
  def get_public(self) -> dict:
95
- """Return a masked representation safe to expose over the API."""
96
  state = self.get()
97
  return {
98
  "enabled": bool(state.get("enabled")),
99
- "url": _mask_proxy_url(_clean(state.get("url"))),
100
  }
101
 
102
  def update(self, *, enabled: bool | None, url: str | None) -> dict:
 
11
  import time
12
  from pathlib import Path
13
  from threading import Lock
14
+ from urllib.parse import urlparse
15
 
16
  from curl_cffi.requests import Session
17
 
 
37
  return bool(parsed.hostname)
38
 
39
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  class ProxyConfig:
41
  def __init__(self, store_file: Path):
42
  self._store_file = store_file
 
75
  return dict(self._state)
76
 
77
  def get_public(self) -> dict:
 
78
  state = self.get()
79
  return {
80
  "enabled": bool(state.get("enabled")),
81
+ "url": _clean(state.get("url")),
82
  }
83
 
84
  def update(self, *, enabled: bool | None, url: str | None) -> dict:
web/src/app/settings/components/proxy-settings.tsx CHANGED
@@ -2,8 +2,6 @@
2
 
3
  import { useEffect, useRef, useState } from "react";
4
  import {
5
- Eye,
6
- EyeOff,
7
  LoaderCircle,
8
  PlugZap,
9
  Save,
@@ -27,7 +25,6 @@ export function ProxySettingsCard() {
27
  const [settings, setSettings] = useState<ProxySettings>({ enabled: false, url: "" });
28
  const [formUrl, setFormUrl] = useState("");
29
  const [formEnabled, setFormEnabled] = useState(false);
30
- const [showUrl, setShowUrl] = useState(false);
31
  const [isLoading, setIsLoading] = useState(true);
32
  const [isSaving, setIsSaving] = useState(false);
33
  const [isTesting, setIsTesting] = useState(false);
@@ -147,25 +144,14 @@ export function ProxySettingsCard() {
147
  <PlugZap className="size-3.5" />
148
  代理地址
149
  </label>
150
- <div className="relative">
151
- <Input
152
- type={showUrl ? "text" : "password"}
153
- value={formUrl}
154
- onChange={(event) => setFormUrl(event.target.value)}
155
- placeholder="http://user:pass@host:port 或 socks5://host:port"
156
- className="h-11 rounded-xl border-stone-200 bg-white pr-10 font-mono text-xs"
157
- />
158
- <button
159
- type="button"
160
- className="absolute top-1/2 right-3 -translate-y-1/2 text-stone-400 transition hover:text-stone-600"
161
- onClick={() => setShowUrl((prev) => !prev)}
162
- >
163
- {showUrl ? <EyeOff className="size-4" /> : <Eye className="size-4" />}
164
- </button>
165
- </div>
166
  <div className="text-xs text-stone-400">
167
  支持 <code className="font-mono">http / https / socks4 / socks5 / socks5h</code>。
168
- 密码部分展示时会被遮蔽(<code className="font-mono">***</code>),回传时保持原样即可保留密码。
169
  </div>
170
  </div>
171
 
 
2
 
3
  import { useEffect, useRef, useState } from "react";
4
  import {
 
 
5
  LoaderCircle,
6
  PlugZap,
7
  Save,
 
25
  const [settings, setSettings] = useState<ProxySettings>({ enabled: false, url: "" });
26
  const [formUrl, setFormUrl] = useState("");
27
  const [formEnabled, setFormEnabled] = useState(false);
 
28
  const [isLoading, setIsLoading] = useState(true);
29
  const [isSaving, setIsSaving] = useState(false);
30
  const [isTesting, setIsTesting] = useState(false);
 
144
  <PlugZap className="size-3.5" />
145
  代理地址
146
  </label>
147
+ <Input
148
+ value={formUrl}
149
+ onChange={(event) => setFormUrl(event.target.value)}
150
+ placeholder="http://user:pass@host:port 或 socks5://host:port"
151
+ className="h-11 rounded-xl border-stone-200 bg-white font-mono text-xs"
152
+ />
 
 
 
 
 
 
 
 
 
 
153
  <div className="text-xs text-stone-400">
154
  支持 <code className="font-mono">http / https / socks4 / socks5 / socks5h</code>。
 
155
  </div>
156
  </div>
157