From 75aa20b36bcad0989df9374266ca0f333b22fd60 Mon Sep 17 00:00:00 2001 From: icy Date: Fri, 7 Nov 2025 16:22:56 +0800 Subject: [PATCH] feat: exchange api security handle --- api/server.go | 31 ++++++++++++++++- web/src/components/AITradersPage.tsx | 50 ++++++++-------------------- web/src/lib/crypto.ts | 5 +++ web/src/types.ts | 13 +++----- 4 files changed, 52 insertions(+), 47 deletions(-) diff --git a/api/server.go b/api/server.go index ca06904c..a2f5ad9c 100644 --- a/api/server.go +++ b/api/server.go @@ -399,6 +399,19 @@ type ExchangeConfig struct { Testnet bool `json:"testnet,omitempty"` } +// SafeExchangeConfig 安全的交易所配置响应结构(不包含敏感信息) +type SafeExchangeConfig struct { + ID string `json:"id"` + UserID string `json:"user_id"` + Name string `json:"name"` + Type string `json:"type"` + Enabled bool `json:"enabled"` + Testnet bool `json:"testnet"` + Deleted bool `json:"deleted"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + type UpdateModelConfigRequest struct { Models map[string]struct { Enabled bool `json:"enabled"` @@ -1023,7 +1036,23 @@ func (s *Server) handleGetExchangeConfigs(c *gin.Context) { } log.Printf("✅ 找到 %d 个交易所配置", len(exchanges)) - c.JSON(http.StatusOK, exchanges) + // 转换为安全的响应结构,过滤敏感信息 + safeExchanges := make([]SafeExchangeConfig, len(exchanges)) + for i, exchange := range exchanges { + safeExchanges[i] = SafeExchangeConfig{ + ID: exchange.ID, + UserID: exchange.UserID, + Name: exchange.Name, + Type: exchange.Type, + Enabled: exchange.Enabled, + Testnet: exchange.Testnet, + Deleted: exchange.Deleted, + CreatedAt: exchange.CreatedAt, + UpdatedAt: exchange.UpdatedAt, + } + } + + c.JSON(http.StatusOK, safeExchanges) } // handleUpdateExchangeConfigs 更新交易所配置 diff --git a/web/src/components/AITradersPage.tsx b/web/src/components/AITradersPage.tsx index 87833f6c..d03c2dea 100644 --- a/web/src/components/AITradersPage.tsx +++ b/web/src/components/AITradersPage.tsx @@ -150,30 +150,9 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) { allExchanges?.filter((e) => { if (!e.enabled) return false - // Aster 交易所需要特殊字段 - if (e.id === 'aster') { - return ( - e.asterUser && - e.asterUser.trim() !== '' && - e.asterSigner && - e.asterSigner.trim() !== '' && - e.asterPrivateKey && - e.asterPrivateKey.trim() !== '' - ) - } - - // Hyperliquid 只需要私钥(作为apiKey),钱包地址会自动从私钥生成 - if (e.id === 'hyperliquid') { - return e.apiKey && e.apiKey.trim() !== '' - } - - // Binance 等其他交易所需要 apiKey 和 secretKey - return ( - e.apiKey && - e.apiKey.trim() !== '' && - e.secretKey && - e.secretKey.trim() !== '' - ) + // 由于API不再返回敏感字段信息,只能基于enabled状态判断 + // 实际的配置验证将在后端进行 + return true }) || [] // 检查模型是否正在被运行中的交易员使用 @@ -818,7 +797,7 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
) @@ -1691,21 +1670,18 @@ function ExchangeConfigModal({ ? t('hyperliquidExchangeName', language) : undefined - // 如果是编辑现有交易所,初始化表单数据 + // 如果是编辑现有交易所,清空所有敏感字段以保证安全 useEffect(() => { if (editingExchangeId && selectedExchange) { - setApiKey(selectedExchange.apiKey || '') - setSecretKey(selectedExchange.secretKey || '') - setPassphrase('') // Don't load existing passphrase for security + // 编辑模式下清空所有敏感字段,用户需要重新输入 + setApiKey('') + setSecretKey('') + setPassphrase('') setTestnet(selectedExchange.testnet || false) - - // Hyperliquid 字段 - setHyperliquidWalletAddr(selectedExchange.hyperliquidWalletAddr || '') - - // Aster 字段 - setAsterUser(selectedExchange.asterUser || '') - setAsterSigner(selectedExchange.asterSigner || '') - setAsterPrivateKey('') // Don't load existing private key for security + setHyperliquidWalletAddr('') + setAsterUser('') + setAsterSigner('') + setAsterPrivateKey('') } }, [editingExchangeId, selectedExchange]) diff --git a/web/src/lib/crypto.ts b/web/src/lib/crypto.ts index 61548cf0..26ceb96f 100644 --- a/web/src/lib/crypto.ts +++ b/web/src/lib/crypto.ts @@ -12,6 +12,11 @@ export class CryptoService { private static publicKeyPEM: string | null = null; static async initialize(publicKeyPEM: string) { + // 检查 Web Crypto API 是否可用 + if (!window.crypto || !window.crypto.subtle) { + throw new Error('Web Crypto API is not available. Please use HTTPS or localhost to access the application.'); + } + if (this.publicKey && this.publicKeyPEM === publicKeyPEM) { return; } diff --git a/web/src/types.ts b/web/src/types.ts index efe0aa73..b230f082 100644 --- a/web/src/types.ts +++ b/web/src/types.ts @@ -108,19 +108,14 @@ export interface AIModel { export interface Exchange { id: string + user_id: string name: string type: 'cex' | 'dex' enabled: boolean - apiKey?: string - secretKey?: string testnet?: boolean - // Hyperliquid 特定字段 - hyperliquidWalletAddr?: string - // Aster 特定字段 - asterUser?: string - asterSigner?: string - asterPrivateKey?: string - deleted?: boolean + deleted: boolean + created_at: string + updated_at: string } export interface CreateTraderRequest {