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) {
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) {

View File

@@ -87,10 +87,10 @@ func (h *Handler) deleteFromStringList(c *gin.Context, target *[]string, after f
return
}
}
if val := c.Query("value"); val != "" {
if val := strings.TrimSpace(c.Query("value")); val != "" {
out := make([]string, 0, len(*target))
for _, v := range *target {
if v != val {
if strings.TrimSpace(v) != val {
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"})
}
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
func (h *Handler) GetAPIKeys(c *gin.Context) { c.JSON(200, gin.H{"api-keys": h.cfg.APIKeys}) }
func (h *Handler) PutAPIKeys(c *gin.Context) {
@@ -121,20 +168,20 @@ func (h *Handler) DeleteAPIKeys(c *gin.Context) {
// generative-language-api-key
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) {
h.putStringList(c, func(v []string) {
h.cfg.GlAPIKey = append([]string(nil), v...)
}, func() {
h.cfg.SyncGeminiKeys()
})
h.applyLegacyKeys(v)
}, nil)
}
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) {
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

View File

@@ -303,56 +303,40 @@ func (cfg *Config) SyncGeminiKeys() {
return
}
if len(cfg.GeminiKey) > 0 {
out := make([]GeminiKey, 0, len(cfg.GeminiKey))
for i := range cfg.GeminiKey {
entry := cfg.GeminiKey[i]
entry.APIKey = strings.TrimSpace(entry.APIKey)
entry.BaseURL = strings.TrimSpace(entry.BaseURL)
entry.ProxyURL = strings.TrimSpace(entry.ProxyURL)
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)
seen := make(map[string]struct{}, len(cfg.GeminiKey))
out := cfg.GeminiKey[:0]
for i := range cfg.GeminiKey {
entry := cfg.GeminiKey[i]
entry.APIKey = strings.TrimSpace(entry.APIKey)
if entry.APIKey == "" {
continue
}
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 {
out := make([]GeminiKey, 0, len(cfg.GlAPIKey))
for i := range cfg.GlAPIKey {
key := strings.TrimSpace(cfg.GlAPIKey[i])
if len(cfg.GlAPIKey) > 0 {
for _, raw := range cfg.GlAPIKey {
key := strings.TrimSpace(raw)
if key == "" {
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]
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)
}
}
cfg.GlAPIKey = nil
}
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$")
}
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.
func hashSecret(secret string) (string, error) {
// Use default cost for simplicity.