diff --git a/internal/api/handlers/management/api_tools.go b/internal/api/handlers/management/api_tools.go index 37a1451b6..a458017df 100644 --- a/internal/api/handlers/management/api_tools.go +++ b/internal/api/handlers/management/api_tools.go @@ -384,22 +384,20 @@ func (h *Handler) resolveMetaToken(ctx context.Context, auth *coreauth.Auth, req } var dcaToken string - if auth.Metadata != nil { - if d, ok := auth.Metadata["dca_token"].(string); ok && strings.TrimSpace(d) != "" { - dcaToken = strings.TrimSpace(d) - } else if t, ok := auth.Metadata["access_token"].(string); ok && strings.HasPrefix(strings.TrimSpace(t), "dca:") { - dcaToken = strings.TrimSpace(t) - } else if k, ok := auth.Metadata["api_key"].(string); ok && strings.HasPrefix(strings.TrimSpace(k), "dca:") { - dcaToken = strings.TrimSpace(k) + if !coreauth.IsConfigAPIKeyAuth(auth) { + if auth.Metadata != nil { + if d, ok := auth.Metadata["dca_token"].(string); ok && strings.TrimSpace(d) != "" { + dcaToken = strings.TrimSpace(d) + } else if t, ok := auth.Metadata["access_token"].(string); ok && strings.HasPrefix(strings.TrimSpace(t), "dca:") { + dcaToken = strings.TrimSpace(t) + } } - } - if dcaToken == "" && auth.Attributes != nil { - if d := strings.TrimSpace(auth.Attributes["dca_token"]); d != "" { - dcaToken = d - } else if t := strings.TrimSpace(auth.Attributes["access_token"]); strings.HasPrefix(t, "dca:") { - dcaToken = t - } else if k := strings.TrimSpace(auth.Attributes["api_key"]); strings.HasPrefix(k, "dca:") { - dcaToken = k + if dcaToken == "" && auth.Attributes != nil { + if d := strings.TrimSpace(auth.Attributes["dca_token"]); d != "" { + dcaToken = d + } else if t := strings.TrimSpace(auth.Attributes["access_token"]); strings.HasPrefix(t, "dca:") { + dcaToken = t + } } } diff --git a/internal/config/config_meta_test.go b/internal/config/config_meta_test.go index 2ae61e60f..f7b25695d 100644 --- a/internal/config/config_meta_test.go +++ b/internal/config/config_meta_test.go @@ -9,13 +9,13 @@ func TestMetaConfigDropsUnusableKeys(t *testing.T) { - base-url: "https://api.meta.ai/v1" - headers: {X-Trace: placeholder} - api-key: " LLM|valid " - - api-key: "dca:recoverable" + - api-key: "dca:requires-oauth-storage" `)) if err != nil { t.Fatal(err) } - if len(cfg.MetaKey) != 2 { - t.Fatalf("got %d keys, want only the two usable credentials", len(cfg.MetaKey)) + if len(cfg.MetaKey) != 1 { + t.Fatalf("got %d keys, want only the 1 valid API key (DCA tokens require OAuth storage)", len(cfg.MetaKey)) } if cfg.MetaKey[0].APIKey != "LLM|valid" || cfg.MetaKey[0].BaseURL != "https://api.meta.ai/v1" { t.Fatalf("valid key not normalized: %#v", cfg.MetaKey[0]) diff --git a/internal/config/config_normalization.go b/internal/config/config_normalization.go index b9eba80e6..836194753 100644 --- a/internal/config/config_normalization.go +++ b/internal/config/config_normalization.go @@ -210,8 +210,8 @@ func sanitizeMetaKeyEntries(entries []MetaKey) []MetaKey { for i := range entries { e := entries[i] e.APIKey = strings.TrimSpace(e.APIKey) - // The native Meta executor requires an API key or a DCA token in this field. - if e.APIKey == "" { + // meta-api-key requires a valid API key. DCA tokens require OAuth storage (auths/*.json). + if e.APIKey == "" || strings.HasPrefix(e.APIKey, "dca:") { continue } e.Prefix = normalizeModelPrefix(e.Prefix) diff --git a/internal/runtime/executor/meta_executor.go b/internal/runtime/executor/meta_executor.go index e2d20105a..6f81d9e72 100644 --- a/internal/runtime/executor/meta_executor.go +++ b/internal/runtime/executor/meta_executor.go @@ -254,7 +254,7 @@ func (e *MetaExecutor) Refresh(ctx context.Context, auth *cliproxyauth.Auth) (*c // ShouldPrepareRequestAuth reports true when a Meta auth has a DCA token but no usable API key. func (e *MetaExecutor) ShouldPrepareRequestAuth(auth *cliproxyauth.Auth) bool { - if auth == nil { + if auth == nil || cliproxyauth.IsConfigAPIKeyAuth(auth) { return false } _, token := metaCreds(auth) @@ -291,6 +291,12 @@ func (e *MetaExecutor) ensureAuth(ctx context.Context, auth *cliproxyauth.Auth) } if token == "" { + if cliproxyauth.IsConfigAPIKeyAuth(auth) { + return nil, statusErr{ + code: http.StatusUnauthorized, + msg: "meta executor: meta-api-key requires a valid API key (DCA tokens require OAuth storage)", + } + } return nil, statusErr{ code: http.StatusUnauthorized, msg: "meta executor: missing API key or access token", @@ -326,7 +332,7 @@ func (e *MetaExecutor) enrichAuth(auth *cliproxyauth.Auth) *cliproxyauth.Auth { } func extractDCAToken(a *cliproxyauth.Auth) string { - if a == nil { + if a == nil || cliproxyauth.IsConfigAPIKeyAuth(a) { return "" } if a.Attributes != nil { @@ -336,9 +342,6 @@ func extractDCAToken(a *cliproxyauth.Auth) string { if t := strings.TrimSpace(a.Attributes["access_token"]); strings.HasPrefix(t, "dca:") { return t } - if k := strings.TrimSpace(a.Attributes["api_key"]); strings.HasPrefix(k, "dca:") { - return k - } } if a.Metadata != nil { if d, ok := a.Metadata["dca_token"].(string); ok && strings.TrimSpace(d) != "" { @@ -347,9 +350,6 @@ func extractDCAToken(a *cliproxyauth.Auth) string { if t, ok := a.Metadata["access_token"].(string); ok && strings.HasPrefix(strings.TrimSpace(t), "dca:") { return strings.TrimSpace(t) } - if k, ok := a.Metadata["api_key"].(string); ok && strings.HasPrefix(strings.TrimSpace(k), "dca:") { - return strings.TrimSpace(k) - } } if a.Storage != nil { if ms, ok := a.Storage.(*metaauth.MetaTokenStorage); ok && ms != nil { diff --git a/internal/runtime/executor/meta_executor_test.go b/internal/runtime/executor/meta_executor_test.go index 3e5a6ae65..3542e9cf3 100644 --- a/internal/runtime/executor/meta_executor_test.go +++ b/internal/runtime/executor/meta_executor_test.go @@ -572,6 +572,17 @@ func TestMetaExecutor_RequestAuthPreparer(t *testing.T) { t.Errorf("ShouldPrepareRequestAuth should be false when dca_token is missing") } + authConfigDCA := &cliproxyauth.Auth{ + Provider: "meta", + Attributes: map[string]string{ + "api_key": "dca:invalid-for-config", + cliproxyauth.AttributeSource: "config:meta[0]", + }, + } + if exec.ShouldPrepareRequestAuth(authConfigDCA) { + t.Errorf("ShouldPrepareRequestAuth should be false for config API key auths") + } + authDCAOnly := &cliproxyauth.Auth{ ID: "meta-prep-test", Provider: "meta",