Merge pull request #246 from CJackHwang/codex/fix-review-comments-before-merging

Fix proxy-bound fallback behavior and redact proxy password responses
This commit is contained in:
CJACK.
2026-04-07 21:26:13 +08:00
committed by GitHub
6 changed files with 112 additions and 14 deletions

View File

@@ -27,6 +27,19 @@ func validateProxyMutation(cfg *config.Config) error {
return config.ValidateAccountProxyReferences(cfg.Accounts, cfg.Proxies)
}
func proxyResponse(proxy config.Proxy) map[string]any {
proxy = config.NormalizeProxy(proxy)
return map[string]any{
"id": proxy.ID,
"name": proxy.Name,
"type": proxy.Type,
"host": proxy.Host,
"port": proxy.Port,
"username": proxy.Username,
"has_password": strings.TrimSpace(proxy.Password) != "",
}
}
func (h *Handler) listProxies(w http.ResponseWriter, _ *http.Request) {
proxies := h.Store.Snapshot().Proxies
items := make([]map[string]any, 0, len(proxies))
@@ -57,7 +70,7 @@ func (h *Handler) addProxy(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusBadRequest, map[string]any{"detail": err.Error()})
return
}
writeJSON(w, http.StatusOK, map[string]any{"success": true, "proxy": proxy})
writeJSON(w, http.StatusOK, map[string]any{"success": true, "proxy": proxyResponse(proxy)})
}
func (h *Handler) updateProxy(w http.ResponseWriter, r *http.Request) {
@@ -92,7 +105,7 @@ func (h *Handler) updateProxy(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusBadRequest, map[string]any{"detail": err.Error()})
return
}
writeJSON(w, http.StatusOK, map[string]any{"success": true, "proxy": proxy})
writeJSON(w, http.StatusOK, map[string]any{"success": true, "proxy": proxyResponse(proxy)})
}
func (h *Handler) deleteProxy(w http.ResponseWriter, r *http.Request) {

View File

@@ -126,6 +126,40 @@ func TestDeleteProxyClearsAssignedAccountProxyID(t *testing.T) {
}
}
func TestUpdateProxyResponseDoesNotExposeStoredPassword(t *testing.T) {
h := newAdminProxyTestHandler(t, `{
"proxies":[{"id":"proxy-1","name":"Node 1","type":"socks5h","host":"127.0.0.1","port":1080,"username":"u","password":"secret"}]
}`)
r := chi.NewRouter()
r.Put("/admin/proxies/{proxyID}", h.updateProxy)
req := httptest.NewRequest(http.MethodPut, "/admin/proxies/proxy-1", bytes.NewBufferString(`{
"name":"Node 1",
"type":"socks5h",
"host":"127.0.0.2",
"port":1081,
"username":"u2"
}`))
rec := httptest.NewRecorder()
r.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("unexpected status: %d body=%s", rec.Code, rec.Body.String())
}
var payload map[string]any
if err := json.Unmarshal(rec.Body.Bytes(), &payload); err != nil {
t.Fatalf("decode response: %v", err)
}
proxy, _ := payload["proxy"].(map[string]any)
if _, exists := proxy["password"]; exists {
t.Fatalf("response should not expose password, got %#v", proxy)
}
if hasPassword, _ := proxy["has_password"].(bool); !hasPassword {
t.Fatalf("expected has_password=true, got %#v", proxy)
}
}
func TestUpdateAccountProxyAssignsProxyID(t *testing.T) {
h := newAdminProxyTestHandler(t, `{
"proxies":[{"id":"proxy-1","name":"Node 1","type":"socks5h","host":"127.0.0.1","port":1080}],

View File

@@ -28,7 +28,7 @@ func (c *Client) Login(ctx context.Context, acc config.Account) (string, error)
} else {
return "", errors.New("missing email/mobile")
}
resp, err := c.postJSON(ctx, clients.regular, DeepSeekLoginURL, BaseHeaders, payload)
resp, err := c.postJSON(ctx, clients.regular, clients.fallback, DeepSeekLoginURL, BaseHeaders, payload)
if err != nil {
return "", err
}
@@ -58,7 +58,7 @@ func (c *Client) CreateSession(ctx context.Context, a *auth.RequestAuth, maxAtte
refreshed := false
for attempts < maxAttempts {
headers := c.authHeaders(a.DeepSeekToken)
resp, status, err := c.postJSONWithStatus(ctx, clients.regular, DeepSeekCreateSessionURL, headers, map[string]any{"agent": "chat"})
resp, status, err := c.postJSONWithStatus(ctx, clients.regular, clients.fallback, DeepSeekCreateSessionURL, headers, map[string]any{"agent": "chat"})
if err != nil {
config.Logger.Warn("[create_session] request error", "error", err, "account", a.AccountID)
attempts++
@@ -101,7 +101,7 @@ func (c *Client) GetPow(ctx context.Context, a *auth.RequestAuth, maxAttempts in
refreshed := false
for attempts < maxAttempts {
headers := c.authHeaders(a.DeepSeekToken)
resp, status, err := c.postJSONWithStatus(ctx, clients.regular, DeepSeekCreatePowURL, headers, map[string]any{"target_path": "/api/v0/chat/completion"})
resp, status, err := c.postJSONWithStatus(ctx, clients.regular, clients.fallback, DeepSeekCreatePowURL, headers, map[string]any{"target_path": "/api/v0/chat/completion"})
if err != nil {
config.Logger.Warn("[get_pow] request error", "error", err, "account", a.AccountID)
attempts++

View File

@@ -11,8 +11,8 @@ import (
trans "ds2api/internal/deepseek/transport"
)
func (c *Client) postJSON(ctx context.Context, doer trans.Doer, url string, headers map[string]string, payload any) (map[string]any, error) {
body, status, err := c.postJSONWithStatus(ctx, doer, url, headers, payload)
func (c *Client) postJSON(ctx context.Context, doer trans.Doer, fallback trans.Doer, url string, headers map[string]string, payload any) (map[string]any, error) {
body, status, err := c.postJSONWithStatus(ctx, doer, fallback, url, headers, payload)
if err != nil {
return nil, err
}
@@ -22,12 +22,11 @@ func (c *Client) postJSON(ctx context.Context, doer trans.Doer, url string, head
return body, nil
}
func (c *Client) postJSONWithStatus(ctx context.Context, doer trans.Doer, url string, headers map[string]string, payload any) (map[string]any, int, error) {
func (c *Client) postJSONWithStatus(ctx context.Context, doer trans.Doer, fallback trans.Doer, url string, headers map[string]string, payload any) (map[string]any, int, error) {
b, err := json.Marshal(payload)
if err != nil {
return nil, 0, err
}
clients := c.requestClientsFromContext(ctx)
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(b))
if err != nil {
return nil, 0, err
@@ -45,7 +44,7 @@ func (c *Client) postJSONWithStatus(ctx context.Context, doer trans.Doer, url st
for k, v := range headers {
req2.Header.Set(k, v)
}
resp, err = clients.fallback.Do(req2)
resp, err = fallback.Do(req2)
if err != nil {
return nil, 0, err
}

View File

@@ -0,0 +1,52 @@
package deepseek
import (
"context"
"errors"
"io"
"net/http"
"strings"
"testing"
)
func TestPostJSONWithStatusUsesProvidedFallbackClient(t *testing.T) {
var fallbackCalled bool
client := &Client{}
primary := failingDoer{err: errors.New("primary failed")}
fallbackDoer := doerFunc(func(req *http.Request) (*http.Response, error) {
fallbackCalled = true
return &http.Response{
StatusCode: http.StatusOK,
Header: make(http.Header),
Body: io.NopCloser(strings.NewReader(`{"ok":true}`)),
Request: req,
}, nil
})
resp, status, err := client.postJSONWithStatus(
context.Background(),
primary,
fallbackDoer,
"https://example.com/api",
map[string]string{"x-test": "1"},
map[string]any{"foo": "bar"},
)
if err != nil {
t.Fatalf("postJSONWithStatus error: %v", err)
}
if status != http.StatusOK {
t.Fatalf("status=%d want=%d", status, http.StatusOK)
}
if !fallbackCalled {
t.Fatal("expected provided fallback doer to be called")
}
if ok, _ := resp["ok"].(bool); !ok {
t.Fatalf("unexpected response body: %#v", resp)
}
}
type doerFunc func(*http.Request) (*http.Response, error)
func (f doerFunc) Do(req *http.Request) (*http.Response, error) {
return f(req)
}

View File

@@ -43,7 +43,7 @@ func (c *Client) DeleteSession(ctx context.Context, a *auth.RequestAuth, session
"chat_session_id": sessionID,
}
resp, status, err := c.postJSONWithStatus(ctx, clients.regular, DeepSeekDeleteSessionURL, headers, payload)
resp, status, err := c.postJSONWithStatus(ctx, clients.regular, clients.fallback, DeepSeekDeleteSessionURL, headers, payload)
if err != nil {
config.Logger.Warn("[delete_session] request error", "error", err, "session_id", sessionID)
attempts++
@@ -97,7 +97,7 @@ func (c *Client) DeleteSessionForToken(ctx context.Context, token string, sessio
"chat_session_id": sessionID,
}
resp, status, err := c.postJSONWithStatus(ctx, clients.regular, DeepSeekDeleteSessionURL, headers, payload)
resp, status, err := c.postJSONWithStatus(ctx, clients.regular, clients.fallback, DeepSeekDeleteSessionURL, headers, payload)
if err != nil {
result.ErrorMessage = err.Error()
return result, err
@@ -120,7 +120,7 @@ func (c *Client) DeleteAllSessions(ctx context.Context, a *auth.RequestAuth) err
headers := c.authHeaders(a.DeepSeekToken)
payload := map[string]any{}
resp, status, err := c.postJSONWithStatus(ctx, clients.regular, DeepSeekDeleteAllSessionsURL, headers, payload)
resp, status, err := c.postJSONWithStatus(ctx, clients.regular, clients.fallback, DeepSeekDeleteAllSessionsURL, headers, payload)
if err != nil {
config.Logger.Warn("[delete_all_sessions] request error", "error", err)
return err
@@ -142,7 +142,7 @@ func (c *Client) DeleteAllSessionsForToken(ctx context.Context, token string) er
headers := c.authHeaders(token)
payload := map[string]any{}
resp, status, err := c.postJSONWithStatus(ctx, clients.regular, DeepSeekDeleteAllSessionsURL, headers, payload)
resp, status, err := c.postJSONWithStatus(ctx, clients.regular, clients.fallback, DeepSeekDeleteAllSessionsURL, headers, payload)
if err != nil {
config.Logger.Warn("[delete_all_sessions_for_token] request error", "error", err)
return err