refactor(auth): simplify inline API key provider logic and improve configuration consistency

- Replaced `SyncInlineAPIKeys` with `MakeInlineAPIKeyProvider` for better clarity and reduced redundancy.
- Removed legacy logic for inline API key syncing and migration.
- Enhanced provider synchronization logic to handle empty states consistently.
- Added normalization to API key handling across configurations.
- Updated handlers to reflect streamlined provider update logic.
This commit is contained in:
Luis Pater
2025-10-01 00:55:09 +08:00
parent 7c642bee09
commit 7c157d6ab1
5 changed files with 101 additions and 94 deletions

View File

@@ -79,51 +79,35 @@ func ReconcileProviders(oldCfg, newCfg *config.Config, existing []sdkaccess.Prov
finalIDs[key] = struct{}{} finalIDs[key] = struct{}{}
} }
if len(result) == 0 && len(newCfg.APIKeys) > 0 { if len(result) == 0 {
sdkConfig.SyncInlineAPIKeys(&newCfg.SDKConfig, newCfg.APIKeys) if inline := sdkConfig.MakeInlineAPIKeyProvider(newCfg.APIKeys); inline != nil {
if providerCfg := newCfg.ConfigAPIKeyProvider(); providerCfg != nil { key := providerIdentifier(inline)
key := providerIdentifier(providerCfg)
if key != "" { if key != "" {
if oldCfgProvider, ok := oldCfgMap[key]; ok { if oldCfgProvider, ok := oldCfgMap[key]; ok {
isAliased := oldCfgProvider == providerCfg if providerConfigEqual(oldCfgProvider, inline) {
if !isAliased && providerConfigEqual(oldCfgProvider, providerCfg) {
if existingProvider, okExisting := existingMap[key]; okExisting { if existingProvider, okExisting := existingMap[key]; okExisting {
result = append(result, existingProvider) result = append(result, existingProvider)
} else { finalIDs[key] = struct{}{}
provider, buildErr := sdkaccess.BuildProvider(providerCfg, &newCfg.SDKConfig) goto inlineDone
if buildErr != nil {
return nil, nil, nil, nil, buildErr
}
if _, existed := existingMap[key]; existed {
appendChange(&updated, key)
} else {
appendChange(&added, key)
}
result = append(result, provider)
} }
} else {
provider, buildErr := sdkaccess.BuildProvider(providerCfg, &newCfg.SDKConfig)
if buildErr != nil {
return nil, nil, nil, nil, buildErr
}
if _, existed := existingMap[key]; existed {
appendChange(&updated, key)
} else {
appendChange(&added, key)
}
result = append(result, provider)
} }
} else {
provider, buildErr := sdkaccess.BuildProvider(providerCfg, &newCfg.SDKConfig)
if buildErr != nil {
return nil, nil, nil, nil, buildErr
}
appendChange(&added, key)
result = append(result, provider)
} }
provider, buildErr := sdkaccess.BuildProvider(inline, &newCfg.SDKConfig)
if buildErr != nil {
return nil, nil, nil, nil, buildErr
}
if _, existed := existingMap[key]; existed {
appendChange(&updated, key)
} else if _, hadOld := oldCfgMap[key]; hadOld {
appendChange(&updated, key)
} else {
appendChange(&added, key)
}
result = append(result, provider)
finalIDs[key] = struct{}{} finalIDs[key] = struct{}{}
} }
} }
inlineDone:
} }
removedSet := make(map[string]struct{}) removedSet := make(map[string]struct{})
@@ -192,7 +176,7 @@ func accessProviderMap(cfg *config.Config) map[string]*sdkConfig.AccessProvider
result[key] = providerCfg result[key] = providerCfg
} }
if len(result) == 0 && len(cfg.APIKeys) > 0 { if len(result) == 0 && len(cfg.APIKeys) > 0 {
if provider := cfg.ConfigAPIKeyProvider(); provider != nil { if provider := sdkConfig.MakeInlineAPIKeyProvider(cfg.APIKeys); provider != nil {
if key := providerIdentifier(provider); key != "" { if key := providerIdentifier(provider); key != "" {
result[key] = provider result[key] = provider
} }
@@ -212,6 +196,11 @@ func collectProviderEntries(cfg *config.Config) []*sdkConfig.AccessProvider {
entries = append(entries, providerCfg) entries = append(entries, providerCfg)
} }
} }
if len(entries) == 0 && len(cfg.APIKeys) > 0 {
if inline := sdkConfig.MakeInlineAPIKeyProvider(cfg.APIKeys); inline != nil {
entries = append(entries, inline)
}
}
return entries return entries
} }

View File

@@ -7,7 +7,6 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/router-for-me/CLIProxyAPI/v6/internal/config" "github.com/router-for-me/CLIProxyAPI/v6/internal/config"
sdkConfig "github.com/router-for-me/CLIProxyAPI/v6/sdk/config"
) )
// Generic helpers for list[string] // Generic helpers for list[string]
@@ -108,13 +107,16 @@ func (h *Handler) deleteFromStringList(c *gin.Context, target *[]string, after f
// 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) {
h.putStringList(c, func(v []string) { sdkConfig.SyncInlineAPIKeys(&h.cfg.SDKConfig, v) }, nil) h.putStringList(c, func(v []string) {
h.cfg.APIKeys = append([]string(nil), v...)
h.cfg.Access.Providers = nil
}, nil)
} }
func (h *Handler) PatchAPIKeys(c *gin.Context) { func (h *Handler) PatchAPIKeys(c *gin.Context) {
h.patchStringList(c, &h.cfg.APIKeys, func() { sdkConfig.SyncInlineAPIKeys(&h.cfg.SDKConfig, h.cfg.APIKeys) }) h.patchStringList(c, &h.cfg.APIKeys, func() { h.cfg.Access.Providers = nil })
} }
func (h *Handler) DeleteAPIKeys(c *gin.Context) { func (h *Handler) DeleteAPIKeys(c *gin.Context) {
h.deleteFromStringList(c, &h.cfg.APIKeys, func() { sdkConfig.SyncInlineAPIKeys(&h.cfg.SDKConfig, h.cfg.APIKeys) }) h.deleteFromStringList(c, &h.cfg.APIKeys, func() { h.cfg.Access.Providers = nil })
} }
// generative-language-api-key // generative-language-api-key
@@ -203,7 +205,7 @@ func (h *Handler) DeleteClaudeKey(c *gin.Context) {
// openai-compatibility: []OpenAICompatibility // openai-compatibility: []OpenAICompatibility
func (h *Handler) GetOpenAICompat(c *gin.Context) { func (h *Handler) GetOpenAICompat(c *gin.Context) {
c.JSON(200, gin.H{"openai-compatibility": h.cfg.OpenAICompatibility}) c.JSON(200, gin.H{"openai-compatibility": normalizedOpenAICompatibilityEntries(h.cfg.OpenAICompatibility)})
} }
func (h *Handler) PutOpenAICompat(c *gin.Context) { func (h *Handler) PutOpenAICompat(c *gin.Context) {
data, err := c.GetRawData() data, err := c.GetRawData()
@@ -381,3 +383,22 @@ func normalizeOpenAICompatibilityEntry(entry *config.OpenAICompatibility) {
} }
entry.APIKeys = nil entry.APIKeys = nil
} }
func normalizedOpenAICompatibilityEntries(entries []config.OpenAICompatibility) []config.OpenAICompatibility {
if len(entries) == 0 {
return nil
}
out := make([]config.OpenAICompatibility, len(entries))
for i := range entries {
copyEntry := entries[i]
if len(copyEntry.APIKeyEntries) > 0 {
copyEntry.APIKeyEntries = append([]config.OpenAICompatibilityAPIKey(nil), copyEntry.APIKeyEntries...)
}
if len(copyEntry.APIKeys) > 0 {
copyEntry.APIKeys = append([]string(nil), copyEntry.APIKeys...)
}
normalizeOpenAICompatibilityEntry(&copyEntry)
out[i] = copyEntry
}
return out
}

View File

@@ -217,33 +217,12 @@ func syncInlineAccessProvider(cfg *Config) {
if cfg == nil { if cfg == nil {
return return
} }
if len(cfg.Access.Providers) == 0 { if len(cfg.APIKeys) == 0 {
if len(cfg.APIKeys) == 0 { if provider := cfg.ConfigAPIKeyProvider(); provider != nil && len(provider.APIKeys) > 0 {
return cfg.APIKeys = append([]string(nil), provider.APIKeys...)
} }
cfg.Access.Providers = append(cfg.Access.Providers, config.AccessProvider{
Name: config.DefaultAccessProviderName,
Type: config.AccessProviderTypeConfigAPIKey,
APIKeys: append([]string(nil), cfg.APIKeys...),
})
return
} }
provider := cfg.ConfigAPIKeyProvider() cfg.Access.Providers = nil
if provider == nil {
if len(cfg.APIKeys) == 0 {
return
}
cfg.Access.Providers = append(cfg.Access.Providers, config.AccessProvider{
Name: config.DefaultAccessProviderName,
Type: config.AccessProviderTypeConfigAPIKey,
APIKeys: append([]string(nil), cfg.APIKeys...),
})
return
}
if len(provider.APIKeys) == 0 && len(cfg.APIKeys) > 0 {
provider.APIKeys = append([]string(nil), cfg.APIKeys...)
}
cfg.APIKeys = append([]string(nil), provider.APIKeys...)
} }
// looksLikeBcrypt returns true if the provided string appears to be a bcrypt hash. // looksLikeBcrypt returns true if the provided string appears to be a bcrypt hash.
@@ -264,6 +243,7 @@ func hashSecret(secret string) (string, error) {
// SaveConfigPreserveComments writes the config back to YAML while preserving existing comments // SaveConfigPreserveComments writes the config back to YAML while preserving existing comments
// and key ordering by loading the original file into a yaml.Node tree and updating values in-place. // and key ordering by loading the original file into a yaml.Node tree and updating values in-place.
func SaveConfigPreserveComments(configFile string, cfg *Config) error { func SaveConfigPreserveComments(configFile string, cfg *Config) error {
persistCfg := sanitizeConfigForPersist(cfg)
// Load original YAML as a node tree to preserve comments and ordering. // Load original YAML as a node tree to preserve comments and ordering.
data, err := os.ReadFile(configFile) data, err := os.ReadFile(configFile)
if err != nil { if err != nil {
@@ -282,7 +262,7 @@ func SaveConfigPreserveComments(configFile string, cfg *Config) error {
} }
// Marshal the current cfg to YAML, then unmarshal to a yaml.Node we can merge from. // Marshal the current cfg to YAML, then unmarshal to a yaml.Node we can merge from.
rendered, err := yaml.Marshal(cfg) rendered, err := yaml.Marshal(persistCfg)
if err != nil { if err != nil {
return err return err
} }
@@ -297,6 +277,9 @@ func SaveConfigPreserveComments(configFile string, cfg *Config) error {
return fmt.Errorf("expected generated root mapping node") return fmt.Errorf("expected generated root mapping node")
} }
// Remove deprecated auth block before merging to avoid persisting it again.
removeMapKey(original.Content[0], "auth")
// Merge generated into original in-place, preserving comments/order of existing nodes. // Merge generated into original in-place, preserving comments/order of existing nodes.
mergeMappingPreserve(original.Content[0], generated.Content[0]) mergeMappingPreserve(original.Content[0], generated.Content[0])
@@ -315,6 +298,16 @@ func SaveConfigPreserveComments(configFile string, cfg *Config) error {
return enc.Close() return enc.Close()
} }
func sanitizeConfigForPersist(cfg *Config) *Config {
if cfg == nil {
return nil
}
clone := *cfg
clone.SDKConfig = cfg.SDKConfig
clone.SDKConfig.Access = config.AccessConfig{}
return &clone
}
// SaveConfigPreserveCommentsUpdateNestedScalar updates a nested scalar key path like ["a","b"] // SaveConfigPreserveCommentsUpdateNestedScalar updates a nested scalar key path like ["a","b"]
// while preserving comments and positions. // while preserving comments and positions.
func SaveConfigPreserveCommentsUpdateNestedScalar(configFile string, path []string, value string) error { func SaveConfigPreserveCommentsUpdateNestedScalar(configFile string, path []string, value string) error {
@@ -517,3 +510,15 @@ func copyNodeShallow(dst, src *yaml.Node) {
dst.Content = nil dst.Content = nil
} }
} }
func removeMapKey(mapNode *yaml.Node, key string) {
if mapNode == nil || mapNode.Kind != yaml.MappingNode || key == "" {
return
}
for i := 0; i+1 < len(mapNode.Content); i += 2 {
if mapNode.Content[i] != nil && mapNode.Content[i].Value == key {
mapNode.Content = append(mapNode.Content[:i], mapNode.Content[i+2:]...)
return
}
}
}

View File

@@ -74,10 +74,9 @@ func BuildProviders(root *config.SDKConfig) ([]Provider, error) {
} }
providers = append(providers, provider) providers = append(providers, provider)
} }
if len(providers) == 0 && len(root.APIKeys) > 0 { if len(providers) == 0 {
config.SyncInlineAPIKeys(root, root.APIKeys) if inline := config.MakeInlineAPIKeyProvider(root.APIKeys); inline != nil {
if providerCfg := root.ConfigAPIKeyProvider(); providerCfg != nil { provider, err := BuildProvider(inline, root)
provider, err := BuildProvider(providerCfg, root)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -16,13 +16,13 @@ type SDKConfig struct {
APIKeys []string `yaml:"api-keys" json:"api-keys"` APIKeys []string `yaml:"api-keys" json:"api-keys"`
// Access holds request authentication provider configuration. // Access holds request authentication provider configuration.
Access AccessConfig `yaml:"auth" json:"auth"` Access AccessConfig `yaml:"auth,omitempty" json:"auth,omitempty"`
} }
// AccessConfig groups request authentication providers. // AccessConfig groups request authentication providers.
type AccessConfig struct { type AccessConfig struct {
// Providers lists configured authentication providers. // Providers lists configured authentication providers.
Providers []AccessProvider `yaml:"providers" json:"providers"` Providers []AccessProvider `yaml:"providers,omitempty" json:"providers,omitempty"`
} }
// AccessProvider describes a request authentication provider entry. // AccessProvider describes a request authentication provider entry.
@@ -51,27 +51,6 @@ const (
DefaultAccessProviderName = "config-inline" DefaultAccessProviderName = "config-inline"
) )
// SyncInlineAPIKeys updates the inline API key provider and top-level APIKeys field.
func SyncInlineAPIKeys(cfg *SDKConfig, keys []string) {
if cfg == nil {
return
}
cloned := append([]string(nil), keys...)
cfg.APIKeys = cloned
if provider := cfg.ConfigAPIKeyProvider(); provider != nil {
if provider.Name == "" {
provider.Name = DefaultAccessProviderName
}
provider.APIKeys = cloned
return
}
cfg.Access.Providers = append(cfg.Access.Providers, AccessProvider{
Name: DefaultAccessProviderName,
Type: AccessProviderTypeConfigAPIKey,
APIKeys: cloned,
})
}
// ConfigAPIKeyProvider returns the first inline API key provider if present. // ConfigAPIKeyProvider returns the first inline API key provider if present.
func (c *SDKConfig) ConfigAPIKeyProvider() *AccessProvider { func (c *SDKConfig) ConfigAPIKeyProvider() *AccessProvider {
if c == nil { if c == nil {
@@ -87,3 +66,17 @@ func (c *SDKConfig) ConfigAPIKeyProvider() *AccessProvider {
} }
return nil return nil
} }
// MakeInlineAPIKeyProvider constructs an inline API key provider configuration.
// It returns nil when no keys are supplied.
func MakeInlineAPIKeyProvider(keys []string) *AccessProvider {
if len(keys) == 0 {
return nil
}
provider := &AccessProvider{
Name: DefaultAccessProviderName,
Type: AccessProviderTypeConfigAPIKey,
APIKeys: append([]string(nil), keys...),
}
return provider
}