fix(config): dedupe and normalize Gemini keys and headers

This commit is contained in:
hkfires
2025-10-31 13:20:10 +08:00
parent 7c1c4ee60b
commit 16be3f0a12
3 changed files with 107 additions and 51 deletions

View File

@@ -12,7 +12,13 @@ import (
) )
func (h *Handler) GetConfig(c *gin.Context) { func (h *Handler) GetConfig(c *gin.Context) {
c.JSON(200, h.cfg) if h == nil || h.cfg == nil {
c.JSON(200, gin.H{})
return
}
cfgCopy := *h.cfg
cfgCopy.GlAPIKey = geminiKeyStringsFromConfig(h.cfg)
c.JSON(200, &cfgCopy)
} }
func (h *Handler) GetConfigYAML(c *gin.Context) { func (h *Handler) GetConfigYAML(c *gin.Context) {

View File

@@ -87,10 +87,10 @@ func (h *Handler) deleteFromStringList(c *gin.Context, target *[]string, after f
return return
} }
} }
if val := c.Query("value"); val != "" { if val := strings.TrimSpace(c.Query("value")); val != "" {
out := make([]string, 0, len(*target)) out := make([]string, 0, len(*target))
for _, v := range *target { for _, v := range *target {
if v != val { if strings.TrimSpace(v) != val {
out = append(out, v) out = append(out, v)
} }
} }
@@ -104,6 +104,53 @@ func (h *Handler) deleteFromStringList(c *gin.Context, target *[]string, after f
c.JSON(400, gin.H{"error": "missing index or value"}) c.JSON(400, gin.H{"error": "missing index or value"})
} }
func sanitizeStringSlice(in []string) []string {
out := make([]string, 0, len(in))
for i := range in {
if trimmed := strings.TrimSpace(in[i]); trimmed != "" {
out = append(out, trimmed)
}
}
return out
}
func geminiKeyStringsFromConfig(cfg *config.Config) []string {
if cfg == nil || len(cfg.GeminiKey) == 0 {
return nil
}
out := make([]string, 0, len(cfg.GeminiKey))
for i := range cfg.GeminiKey {
if key := strings.TrimSpace(cfg.GeminiKey[i].APIKey); key != "" {
out = append(out, key)
}
}
return out
}
func (h *Handler) applyLegacyKeys(keys []string) {
if h == nil || h.cfg == nil {
return
}
sanitized := sanitizeStringSlice(keys)
existing := make(map[string]config.GeminiKey, len(h.cfg.GeminiKey))
for _, entry := range h.cfg.GeminiKey {
if key := strings.TrimSpace(entry.APIKey); key != "" {
existing[key] = entry
}
}
newList := make([]config.GeminiKey, 0, len(sanitized))
for _, key := range sanitized {
if entry, ok := existing[key]; ok {
newList = append(newList, entry)
} else {
newList = append(newList, config.GeminiKey{APIKey: key})
}
}
h.cfg.GeminiKey = newList
h.cfg.GlAPIKey = sanitized
h.cfg.SyncGeminiKeys()
}
// api-keys // api-keys
func (h *Handler) GetAPIKeys(c *gin.Context) { c.JSON(200, gin.H{"api-keys": h.cfg.APIKeys}) } func (h *Handler) GetAPIKeys(c *gin.Context) { c.JSON(200, gin.H{"api-keys": h.cfg.APIKeys}) }
func (h *Handler) PutAPIKeys(c *gin.Context) { func (h *Handler) PutAPIKeys(c *gin.Context) {
@@ -121,20 +168,20 @@ func (h *Handler) DeleteAPIKeys(c *gin.Context) {
// generative-language-api-key // generative-language-api-key
func (h *Handler) GetGlKeys(c *gin.Context) { func (h *Handler) GetGlKeys(c *gin.Context) {
c.JSON(200, gin.H{"generative-language-api-key": h.cfg.GlAPIKey}) c.JSON(200, gin.H{"generative-language-api-key": geminiKeyStringsFromConfig(h.cfg)})
} }
func (h *Handler) PutGlKeys(c *gin.Context) { func (h *Handler) PutGlKeys(c *gin.Context) {
h.putStringList(c, func(v []string) { h.putStringList(c, func(v []string) {
h.cfg.GlAPIKey = append([]string(nil), v...) h.applyLegacyKeys(v)
}, func() { }, nil)
h.cfg.SyncGeminiKeys()
})
} }
func (h *Handler) PatchGlKeys(c *gin.Context) { func (h *Handler) PatchGlKeys(c *gin.Context) {
h.patchStringList(c, &h.cfg.GlAPIKey, func() { h.cfg.SyncGeminiKeys() }) target := append([]string(nil), geminiKeyStringsFromConfig(h.cfg)...)
h.patchStringList(c, &target, func() { h.applyLegacyKeys(target) })
} }
func (h *Handler) DeleteGlKeys(c *gin.Context) { func (h *Handler) DeleteGlKeys(c *gin.Context) {
h.deleteFromStringList(c, &h.cfg.GlAPIKey, func() { h.cfg.SyncGeminiKeys() }) target := append([]string(nil), geminiKeyStringsFromConfig(h.cfg)...)
h.deleteFromStringList(c, &target, func() { h.applyLegacyKeys(target) })
} }
// gemini-api-key: []GeminiKey // gemini-api-key: []GeminiKey

View File

@@ -303,56 +303,40 @@ func (cfg *Config) SyncGeminiKeys() {
return return
} }
if len(cfg.GeminiKey) > 0 { seen := make(map[string]struct{}, len(cfg.GeminiKey))
out := make([]GeminiKey, 0, len(cfg.GeminiKey)) out := cfg.GeminiKey[:0]
for i := range cfg.GeminiKey { for i := range cfg.GeminiKey {
entry := cfg.GeminiKey[i] entry := cfg.GeminiKey[i]
entry.APIKey = strings.TrimSpace(entry.APIKey) entry.APIKey = strings.TrimSpace(entry.APIKey)
entry.BaseURL = strings.TrimSpace(entry.BaseURL) if entry.APIKey == "" {
entry.ProxyURL = strings.TrimSpace(entry.ProxyURL) continue
if entry.APIKey == "" {
continue
}
if len(entry.Headers) > 0 {
clean := make(map[string]string, len(entry.Headers))
for hk, hv := range entry.Headers {
key := strings.TrimSpace(hk)
val := strings.TrimSpace(hv)
if key == "" || val == "" {
continue
}
clean[key] = val
}
if len(clean) == 0 {
entry.Headers = nil
} else {
entry.Headers = clean
}
}
out = append(out, entry)
} }
cfg.GeminiKey = out entry.BaseURL = strings.TrimSpace(entry.BaseURL)
entry.ProxyURL = strings.TrimSpace(entry.ProxyURL)
entry.Headers = normalizeGeminiHeaders(entry.Headers)
if _, exists := seen[entry.APIKey]; exists {
continue
}
seen[entry.APIKey] = struct{}{}
out = append(out, entry)
} }
cfg.GeminiKey = out
if len(cfg.GeminiKey) == 0 && len(cfg.GlAPIKey) > 0 { if len(cfg.GlAPIKey) > 0 {
out := make([]GeminiKey, 0, len(cfg.GlAPIKey)) for _, raw := range cfg.GlAPIKey {
for i := range cfg.GlAPIKey { key := strings.TrimSpace(raw)
key := strings.TrimSpace(cfg.GlAPIKey[i])
if key == "" { if key == "" {
continue continue
} }
out = append(out, GeminiKey{APIKey: key}) if _, exists := seen[key]; exists {
continue
}
cfg.GeminiKey = append(cfg.GeminiKey, GeminiKey{APIKey: key})
seen[key] = struct{}{}
} }
cfg.GeminiKey = out
} }
cfg.GlAPIKey = cfg.GlAPIKey[:0] cfg.GlAPIKey = nil
if len(cfg.GeminiKey) > 0 {
cfg.GlAPIKey = make([]string, 0, len(cfg.GeminiKey))
for i := range cfg.GeminiKey {
cfg.GlAPIKey = append(cfg.GlAPIKey, cfg.GeminiKey[i].APIKey)
}
}
} }
func syncInlineAccessProvider(cfg *Config) { func syncInlineAccessProvider(cfg *Config) {
@@ -372,6 +356,25 @@ 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 normalizeGeminiHeaders(headers map[string]string) map[string]string {
if len(headers) == 0 {
return nil
}
clean := make(map[string]string, len(headers))
for k, v := range headers {
key := strings.TrimSpace(k)
val := strings.TrimSpace(v)
if key == "" || val == "" {
continue
}
clean[key] = val
}
if len(clean) == 0 {
return nil
}
return clean
}
// hashSecret hashes the given secret using bcrypt. // hashSecret hashes the given secret using bcrypt.
func hashSecret(secret string) (string, error) { func hashSecret(secret string) (string, error) {
// Use default cost for simplicity. // Use default cost for simplicity.