fix(management): sanitize keys and normalize headers

This commit is contained in:
hkfires
2025-11-09 12:13:02 +08:00
parent a00ba77604
commit 51d2766d5c
2 changed files with 42 additions and 11 deletions

View File

@@ -328,6 +328,7 @@ func (h *Handler) PutClaudeKeys(c *gin.Context) {
normalizeClaudeKey(&arr[i]) normalizeClaudeKey(&arr[i])
} }
h.cfg.ClaudeKey = arr h.cfg.ClaudeKey = arr
h.cfg.SanitizeClaudeKeys()
h.persist(c) h.persist(c)
} }
func (h *Handler) PatchClaudeKey(c *gin.Context) { func (h *Handler) PatchClaudeKey(c *gin.Context) {
@@ -340,16 +341,19 @@ func (h *Handler) PatchClaudeKey(c *gin.Context) {
c.JSON(400, gin.H{"error": "invalid body"}) c.JSON(400, gin.H{"error": "invalid body"})
return return
} }
normalizeClaudeKey(body.Value) value := *body.Value
normalizeClaudeKey(&value)
if body.Index != nil && *body.Index >= 0 && *body.Index < len(h.cfg.ClaudeKey) { if body.Index != nil && *body.Index >= 0 && *body.Index < len(h.cfg.ClaudeKey) {
h.cfg.ClaudeKey[*body.Index] = *body.Value h.cfg.ClaudeKey[*body.Index] = value
h.cfg.SanitizeClaudeKeys()
h.persist(c) h.persist(c)
return return
} }
if body.Match != nil { if body.Match != nil {
for i := range h.cfg.ClaudeKey { for i := range h.cfg.ClaudeKey {
if h.cfg.ClaudeKey[i].APIKey == *body.Match { if h.cfg.ClaudeKey[i].APIKey == *body.Match {
h.cfg.ClaudeKey[i] = *body.Value h.cfg.ClaudeKey[i] = value
h.cfg.SanitizeClaudeKeys()
h.persist(c) h.persist(c)
return return
} }
@@ -366,6 +370,7 @@ func (h *Handler) DeleteClaudeKey(c *gin.Context) {
} }
} }
h.cfg.ClaudeKey = out h.cfg.ClaudeKey = out
h.cfg.SanitizeClaudeKeys()
h.persist(c) h.persist(c)
return return
} }
@@ -374,6 +379,7 @@ func (h *Handler) DeleteClaudeKey(c *gin.Context) {
_, err := fmt.Sscanf(idxStr, "%d", &idx) _, err := fmt.Sscanf(idxStr, "%d", &idx)
if err == nil && idx >= 0 && idx < len(h.cfg.ClaudeKey) { if err == nil && idx >= 0 && idx < len(h.cfg.ClaudeKey) {
h.cfg.ClaudeKey = append(h.cfg.ClaudeKey[:idx], h.cfg.ClaudeKey[idx+1:]...) h.cfg.ClaudeKey = append(h.cfg.ClaudeKey[:idx], h.cfg.ClaudeKey[idx+1:]...)
h.cfg.SanitizeClaudeKeys()
h.persist(c) h.persist(c)
return return
} }
@@ -413,6 +419,7 @@ func (h *Handler) PutOpenAICompat(c *gin.Context) {
} }
} }
h.cfg.OpenAICompatibility = filtered h.cfg.OpenAICompatibility = filtered
h.cfg.SanitizeOpenAICompatibility()
h.persist(c) h.persist(c)
} }
func (h *Handler) PatchOpenAICompat(c *gin.Context) { func (h *Handler) PatchOpenAICompat(c *gin.Context) {
@@ -430,6 +437,7 @@ func (h *Handler) PatchOpenAICompat(c *gin.Context) {
if strings.TrimSpace(body.Value.BaseURL) == "" { if strings.TrimSpace(body.Value.BaseURL) == "" {
if body.Index != nil && *body.Index >= 0 && *body.Index < len(h.cfg.OpenAICompatibility) { if body.Index != nil && *body.Index >= 0 && *body.Index < len(h.cfg.OpenAICompatibility) {
h.cfg.OpenAICompatibility = append(h.cfg.OpenAICompatibility[:*body.Index], h.cfg.OpenAICompatibility[*body.Index+1:]...) h.cfg.OpenAICompatibility = append(h.cfg.OpenAICompatibility[:*body.Index], h.cfg.OpenAICompatibility[*body.Index+1:]...)
h.cfg.SanitizeOpenAICompatibility()
h.persist(c) h.persist(c)
return return
} }
@@ -445,6 +453,7 @@ func (h *Handler) PatchOpenAICompat(c *gin.Context) {
} }
if removed { if removed {
h.cfg.OpenAICompatibility = out h.cfg.OpenAICompatibility = out
h.cfg.SanitizeOpenAICompatibility()
h.persist(c) h.persist(c)
return return
} }
@@ -454,6 +463,7 @@ func (h *Handler) PatchOpenAICompat(c *gin.Context) {
} }
if body.Index != nil && *body.Index >= 0 && *body.Index < len(h.cfg.OpenAICompatibility) { if body.Index != nil && *body.Index >= 0 && *body.Index < len(h.cfg.OpenAICompatibility) {
h.cfg.OpenAICompatibility[*body.Index] = *body.Value h.cfg.OpenAICompatibility[*body.Index] = *body.Value
h.cfg.SanitizeOpenAICompatibility()
h.persist(c) h.persist(c)
return return
} }
@@ -461,6 +471,7 @@ func (h *Handler) PatchOpenAICompat(c *gin.Context) {
for i := range h.cfg.OpenAICompatibility { for i := range h.cfg.OpenAICompatibility {
if h.cfg.OpenAICompatibility[i].Name == *body.Name { if h.cfg.OpenAICompatibility[i].Name == *body.Name {
h.cfg.OpenAICompatibility[i] = *body.Value h.cfg.OpenAICompatibility[i] = *body.Value
h.cfg.SanitizeOpenAICompatibility()
h.persist(c) h.persist(c)
return return
} }
@@ -477,6 +488,7 @@ func (h *Handler) DeleteOpenAICompat(c *gin.Context) {
} }
} }
h.cfg.OpenAICompatibility = out h.cfg.OpenAICompatibility = out
h.cfg.SanitizeOpenAICompatibility()
h.persist(c) h.persist(c)
return return
} }
@@ -485,6 +497,7 @@ func (h *Handler) DeleteOpenAICompat(c *gin.Context) {
_, err := fmt.Sscanf(idxStr, "%d", &idx) _, err := fmt.Sscanf(idxStr, "%d", &idx)
if err == nil && idx >= 0 && idx < len(h.cfg.OpenAICompatibility) { if err == nil && idx >= 0 && idx < len(h.cfg.OpenAICompatibility) {
h.cfg.OpenAICompatibility = append(h.cfg.OpenAICompatibility[:idx], h.cfg.OpenAICompatibility[idx+1:]...) h.cfg.OpenAICompatibility = append(h.cfg.OpenAICompatibility[:idx], h.cfg.OpenAICompatibility[idx+1:]...)
h.cfg.SanitizeOpenAICompatibility()
h.persist(c) h.persist(c)
return return
} }
@@ -517,13 +530,17 @@ func (h *Handler) PutCodexKeys(c *gin.Context) {
filtered := make([]config.CodexKey, 0, len(arr)) filtered := make([]config.CodexKey, 0, len(arr))
for i := range arr { for i := range arr {
entry := arr[i] entry := arr[i]
entry.APIKey = strings.TrimSpace(entry.APIKey)
entry.BaseURL = strings.TrimSpace(entry.BaseURL) entry.BaseURL = strings.TrimSpace(entry.BaseURL)
entry.ProxyURL = strings.TrimSpace(entry.ProxyURL)
entry.Headers = config.NormalizeHeaders(entry.Headers)
if entry.BaseURL == "" { if entry.BaseURL == "" {
continue continue
} }
filtered = append(filtered, entry) filtered = append(filtered, entry)
} }
h.cfg.CodexKey = filtered h.cfg.CodexKey = filtered
h.cfg.SanitizeCodexKeys()
h.persist(c) h.persist(c)
} }
func (h *Handler) PatchCodexKey(c *gin.Context) { func (h *Handler) PatchCodexKey(c *gin.Context) {
@@ -536,10 +553,16 @@ func (h *Handler) PatchCodexKey(c *gin.Context) {
c.JSON(400, gin.H{"error": "invalid body"}) c.JSON(400, gin.H{"error": "invalid body"})
return return
} }
value := *body.Value
value.APIKey = strings.TrimSpace(value.APIKey)
value.BaseURL = strings.TrimSpace(value.BaseURL)
value.ProxyURL = strings.TrimSpace(value.ProxyURL)
value.Headers = config.NormalizeHeaders(value.Headers)
// If base-url becomes empty, delete instead of update // If base-url becomes empty, delete instead of update
if strings.TrimSpace(body.Value.BaseURL) == "" { if value.BaseURL == "" {
if body.Index != nil && *body.Index >= 0 && *body.Index < len(h.cfg.CodexKey) { if body.Index != nil && *body.Index >= 0 && *body.Index < len(h.cfg.CodexKey) {
h.cfg.CodexKey = append(h.cfg.CodexKey[:*body.Index], h.cfg.CodexKey[*body.Index+1:]...) h.cfg.CodexKey = append(h.cfg.CodexKey[:*body.Index], h.cfg.CodexKey[*body.Index+1:]...)
h.cfg.SanitizeCodexKeys()
h.persist(c) h.persist(c)
return return
} }
@@ -555,20 +578,23 @@ func (h *Handler) PatchCodexKey(c *gin.Context) {
} }
if removed { if removed {
h.cfg.CodexKey = out h.cfg.CodexKey = out
h.cfg.SanitizeCodexKeys()
h.persist(c) h.persist(c)
return return
} }
} }
} else { } else {
if body.Index != nil && *body.Index >= 0 && *body.Index < len(h.cfg.CodexKey) { if body.Index != nil && *body.Index >= 0 && *body.Index < len(h.cfg.CodexKey) {
h.cfg.CodexKey[*body.Index] = *body.Value h.cfg.CodexKey[*body.Index] = value
h.cfg.SanitizeCodexKeys()
h.persist(c) h.persist(c)
return return
} }
if body.Match != nil { if body.Match != nil {
for i := range h.cfg.CodexKey { for i := range h.cfg.CodexKey {
if h.cfg.CodexKey[i].APIKey == *body.Match { if h.cfg.CodexKey[i].APIKey == *body.Match {
h.cfg.CodexKey[i] = *body.Value h.cfg.CodexKey[i] = value
h.cfg.SanitizeCodexKeys()
h.persist(c) h.persist(c)
return return
} }
@@ -586,6 +612,7 @@ func (h *Handler) DeleteCodexKey(c *gin.Context) {
} }
} }
h.cfg.CodexKey = out h.cfg.CodexKey = out
h.cfg.SanitizeCodexKeys()
h.persist(c) h.persist(c)
return return
} }
@@ -594,6 +621,7 @@ func (h *Handler) DeleteCodexKey(c *gin.Context) {
_, err := fmt.Sscanf(idxStr, "%d", &idx) _, err := fmt.Sscanf(idxStr, "%d", &idx)
if err == nil && idx >= 0 && idx < len(h.cfg.CodexKey) { if err == nil && idx >= 0 && idx < len(h.cfg.CodexKey) {
h.cfg.CodexKey = append(h.cfg.CodexKey[:idx], h.cfg.CodexKey[idx+1:]...) h.cfg.CodexKey = append(h.cfg.CodexKey[:idx], h.cfg.CodexKey[idx+1:]...)
h.cfg.SanitizeCodexKeys()
h.persist(c) h.persist(c)
return return
} }
@@ -607,6 +635,7 @@ func normalizeOpenAICompatibilityEntry(entry *config.OpenAICompatibility) {
} }
// Trim base-url; empty base-url indicates provider should be removed by sanitization // Trim base-url; empty base-url indicates provider should be removed by sanitization
entry.BaseURL = strings.TrimSpace(entry.BaseURL) entry.BaseURL = strings.TrimSpace(entry.BaseURL)
entry.Headers = config.NormalizeHeaders(entry.Headers)
existing := make(map[string]struct{}, len(entry.APIKeyEntries)) existing := make(map[string]struct{}, len(entry.APIKeyEntries))
for i := range entry.APIKeyEntries { for i := range entry.APIKeyEntries {
trimmed := strings.TrimSpace(entry.APIKeyEntries[i].APIKey) trimmed := strings.TrimSpace(entry.APIKeyEntries[i].APIKey)
@@ -658,6 +687,7 @@ func normalizeClaudeKey(entry *config.ClaudeKey) {
entry.APIKey = strings.TrimSpace(entry.APIKey) entry.APIKey = strings.TrimSpace(entry.APIKey)
entry.BaseURL = strings.TrimSpace(entry.BaseURL) entry.BaseURL = strings.TrimSpace(entry.BaseURL)
entry.ProxyURL = strings.TrimSpace(entry.ProxyURL) entry.ProxyURL = strings.TrimSpace(entry.ProxyURL)
entry.Headers = config.NormalizeHeaders(entry.Headers)
if len(entry.Models) == 0 { if len(entry.Models) == 0 {
return return
} }

View File

@@ -283,7 +283,7 @@ func (cfg *Config) SanitizeOpenAICompatibility() {
e := cfg.OpenAICompatibility[i] e := cfg.OpenAICompatibility[i]
e.Name = strings.TrimSpace(e.Name) e.Name = strings.TrimSpace(e.Name)
e.BaseURL = strings.TrimSpace(e.BaseURL) e.BaseURL = strings.TrimSpace(e.BaseURL)
e.Headers = normalizeHeaders(e.Headers) e.Headers = NormalizeHeaders(e.Headers)
if e.BaseURL == "" { if e.BaseURL == "" {
// Skip providers with no base-url; treated as removed // Skip providers with no base-url; treated as removed
continue continue
@@ -303,7 +303,7 @@ func (cfg *Config) SanitizeCodexKeys() {
for i := range cfg.CodexKey { for i := range cfg.CodexKey {
e := cfg.CodexKey[i] e := cfg.CodexKey[i]
e.BaseURL = strings.TrimSpace(e.BaseURL) e.BaseURL = strings.TrimSpace(e.BaseURL)
e.Headers = normalizeHeaders(e.Headers) e.Headers = NormalizeHeaders(e.Headers)
if e.BaseURL == "" { if e.BaseURL == "" {
continue continue
} }
@@ -319,7 +319,7 @@ func (cfg *Config) SanitizeClaudeKeys() {
} }
for i := range cfg.ClaudeKey { for i := range cfg.ClaudeKey {
entry := &cfg.ClaudeKey[i] entry := &cfg.ClaudeKey[i]
entry.Headers = normalizeHeaders(entry.Headers) entry.Headers = NormalizeHeaders(entry.Headers)
} }
} }
@@ -339,7 +339,7 @@ func (cfg *Config) SanitizeGeminiKeys() {
} }
entry.BaseURL = strings.TrimSpace(entry.BaseURL) entry.BaseURL = strings.TrimSpace(entry.BaseURL)
entry.ProxyURL = strings.TrimSpace(entry.ProxyURL) entry.ProxyURL = strings.TrimSpace(entry.ProxyURL)
entry.Headers = normalizeHeaders(entry.Headers) entry.Headers = NormalizeHeaders(entry.Headers)
if _, exists := seen[entry.APIKey]; exists { if _, exists := seen[entry.APIKey]; exists {
continue continue
} }
@@ -382,7 +382,8 @@ func looksLikeBcrypt(s string) bool {
return len(s) > 4 && (s[:4] == "$2a$" || s[:4] == "$2b$" || s[:4] == "$2y$") return len(s) > 4 && (s[:4] == "$2a$" || s[:4] == "$2b$" || s[:4] == "$2y$")
} }
func normalizeHeaders(headers map[string]string) map[string]string { // NormalizeHeaders trims header keys and values and removes empty pairs.
func NormalizeHeaders(headers map[string]string) map[string]string {
if len(headers) == 0 { if len(headers) == 0 {
return nil return nil
} }