mirror of
https://github.com/CJackHwang/ds2api.git
synced 2026-05-04 08:25:26 +08:00
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:
@@ -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) {
|
||||
|
||||
@@ -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}],
|
||||
|
||||
@@ -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++
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
52
internal/deepseek/client_http_json_test.go
Normal file
52
internal/deepseek/client_http_json_test.go
Normal 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)
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user