refactor(llm): deduplicate and validate auth header normalization

Export NormalizeAuthHeader from the llm package and remove the duplicate
normalizeConfigAuthHeader in config_cmd.go. Invalid auth_header values
now return an error at configuration time instead of being silently
accepted and causing opaque 401s at runtime.
This commit is contained in:
kite 2026-06-08 20:21:28 +08:00
parent 48dc1b37d4
commit fa6c7d8e04
3 changed files with 28 additions and 22 deletions

View file

@ -6,7 +6,8 @@ import (
"os"
"path/filepath"
"strconv"
"strings"
"github.com/open-code-review/open-code-review/internal/llm"
)
// Default config file location: ~/.opencodereview/config.json
@ -132,7 +133,11 @@ func setConfigValue(cfg *Config, key, value string) error {
case "llm.auth_token", "llm.AuthToken":
cfg.Llm.AuthToken = value
case "llm.auth_header", "llm.AuthHeader":
cfg.Llm.AuthHeader = normalizeConfigAuthHeader(value)
normalized, err := llm.NormalizeAuthHeader(value)
if err != nil {
return err
}
cfg.Llm.AuthHeader = normalized
case "llm.model", "llm.Model":
cfg.Llm.Model = value
case "llm.use_anthropic", "llm.UseAnthropic":
@ -175,18 +180,6 @@ func setConfigValue(cfg *Config, key, value string) error {
return nil
}
func normalizeConfigAuthHeader(header string) string {
header = strings.TrimSpace(header)
switch strings.ToLower(header) {
case "x-api-key":
return "x-api-key"
case "authorization", "bearer":
return "authorization"
default:
return header
}
}
func (c *Config) ensureTelemetry() {
if c.Telemetry == nil {
c.Telemetry = &TelemetryConfig{}

View file

@ -182,7 +182,7 @@ type ClientConfig struct {
URL string // Full API endpoint URL
APIKey string // Bearer token / API key
Model string // Default model override
AuthHeader string // Anthropic auth header: "x-api-key" or "authorization"
AuthHeader string // Auth header name: "x-api-key", "authorization", or empty for protocol default
Timeout time.Duration // Request timeout
ExtraBody map[string]any // Vendor-specific fields merged into every request body
}
@ -484,7 +484,7 @@ func NewAnthropicClient(cfg ClientConfig) *AnthropicClient {
}
sdkBaseURL := strings.TrimSuffix(strings.TrimRight(cfg.URL, "/"), "/v1/messages")
authHeader := normalizeAuthHeader(cfg.AuthHeader)
authHeader, _ := NormalizeAuthHeader(cfg.AuthHeader)
if authHeader == "" {
authHeader = "authorization"
}

View file

@ -87,7 +87,11 @@ func tryOCREnv() (ResolvedEndpoint, bool, error) {
var authHeader string
if protocol == "anthropic" {
authHeader = normalizeAuthHeader(os.Getenv(envOCRLLMAuthHeader))
var err error
authHeader, err = NormalizeAuthHeader(os.Getenv(envOCRLLMAuthHeader))
if err != nil {
return ResolvedEndpoint{}, false, fmt.Errorf("OCR environment: %w", err)
}
if authHeader == "" {
authHeader = defaultAuthHeader(protocol)
}
@ -141,7 +145,11 @@ func tryOCRConfig(path string) (ResolvedEndpoint, bool, error) {
var authHeader string
if protocol == "anthropic" {
authHeader = normalizeAuthHeader(cfg.Llm.AuthHeader)
var err error
authHeader, err = NormalizeAuthHeader(cfg.Llm.AuthHeader)
if err != nil {
return ResolvedEndpoint{}, false, fmt.Errorf("OCR config file: %w", err)
}
if authHeader == "" {
authHeader = defaultAuthHeader(protocol)
}
@ -256,15 +264,20 @@ func defaultAuthHeader(protocol string) string {
return ""
}
func normalizeAuthHeader(header string) string {
// NormalizeAuthHeader normalizes an auth header value to a canonical form.
// It returns an error for unrecognized values.
func NormalizeAuthHeader(header string) (string, error) {
header = strings.TrimSpace(header)
if header == "" {
return "", nil
}
switch strings.ToLower(header) {
case "x-api-key":
return "x-api-key"
return "x-api-key", nil
case "authorization", "bearer":
return "authorization"
return "authorization", nil
default:
return header
return "", fmt.Errorf("unsupported auth_header value %q; expected \"x-api-key\" or \"authorization\"", header)
}
}