feat: exchange api security handle

This commit is contained in:
icy
2025-11-07 16:22:56 +08:00
parent 5e5a1df1a7
commit b715020d35
4 changed files with 52 additions and 47 deletions

View File

@@ -399,6 +399,19 @@ type ExchangeConfig struct {
Testnet bool `json:"testnet,omitempty"` 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 { type UpdateModelConfigRequest struct {
Models map[string]struct { Models map[string]struct {
Enabled bool `json:"enabled"` Enabled bool `json:"enabled"`
@@ -1023,7 +1036,23 @@ func (s *Server) handleGetExchangeConfigs(c *gin.Context) {
} }
log.Printf("✅ 找到 %d 个交易所配置", len(exchanges)) 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 更新交易所配置 // handleUpdateExchangeConfigs 更新交易所配置

View File

@@ -150,30 +150,9 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
allExchanges?.filter((e) => { allExchanges?.filter((e) => {
if (!e.enabled) return false if (!e.enabled) return false
// Aster 交易所需要特殊字段 // 由于API不再返回敏感字段信息只能基于enabled状态判断
if (e.id === 'aster') { // 实际的配置验证将在后端进行
return ( return true
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() !== ''
)
}) || [] }) || []
// 检查模型是否正在被运行中的交易员使用 // 检查模型是否正在被运行中的交易员使用
@@ -818,7 +797,7 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
</div> </div>
</div> </div>
<div <div
className={`w-2.5 h-2.5 md:w-3 md:h-3 rounded-full flex-shrink-0 ${exchange.enabled && exchange.apiKey ? 'bg-green-400' : 'bg-gray-500'}`} className={`w-2.5 h-2.5 md:w-3 md:h-3 rounded-full flex-shrink-0 ${exchange.enabled ? 'bg-green-400' : 'bg-gray-500'}`}
/> />
</div> </div>
) )
@@ -1691,21 +1670,18 @@ function ExchangeConfigModal({
? t('hyperliquidExchangeName', language) ? t('hyperliquidExchangeName', language)
: undefined : undefined
// 如果是编辑现有交易所,初始化表单数据 // 如果是编辑现有交易所,清空所有敏感字段以保证安全
useEffect(() => { useEffect(() => {
if (editingExchangeId && selectedExchange) { if (editingExchangeId && selectedExchange) {
setApiKey(selectedExchange.apiKey || '') // 编辑模式下清空所有敏感字段,用户需要重新输入
setSecretKey(selectedExchange.secretKey || '') setApiKey('')
setPassphrase('') // Don't load existing passphrase for security setSecretKey('')
setPassphrase('')
setTestnet(selectedExchange.testnet || false) setTestnet(selectedExchange.testnet || false)
setHyperliquidWalletAddr('')
// Hyperliquid 字段 setAsterUser('')
setHyperliquidWalletAddr(selectedExchange.hyperliquidWalletAddr || '') setAsterSigner('')
setAsterPrivateKey('')
// Aster 字段
setAsterUser(selectedExchange.asterUser || '')
setAsterSigner(selectedExchange.asterSigner || '')
setAsterPrivateKey('') // Don't load existing private key for security
} }
}, [editingExchangeId, selectedExchange]) }, [editingExchangeId, selectedExchange])

View File

@@ -12,6 +12,11 @@ export class CryptoService {
private static publicKeyPEM: string | null = null; private static publicKeyPEM: string | null = null;
static async initialize(publicKeyPEM: string) { 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) { if (this.publicKey && this.publicKeyPEM === publicKeyPEM) {
return; return;
} }

View File

@@ -108,19 +108,14 @@ export interface AIModel {
export interface Exchange { export interface Exchange {
id: string id: string
user_id: string
name: string name: string
type: 'cex' | 'dex' type: 'cex' | 'dex'
enabled: boolean enabled: boolean
apiKey?: string
secretKey?: string
testnet?: boolean testnet?: boolean
// Hyperliquid 特定字段 deleted: boolean
hyperliquidWalletAddr?: string created_at: string
// Aster 特定字段 updated_at: string
asterUser?: string
asterSigner?: string
asterPrivateKey?: string
deleted?: boolean
} }
export interface CreateTraderRequest { export interface CreateTraderRequest {