fix(meta): require OAuth storage for DCA tokens and reject them in config meta-api-key

This commit is contained in:
Kenny
2026-09-05 14:02:18 +00:00
parent d09042a548
commit 1144ae707b
5 changed files with 37 additions and 28 deletions

View File

@@ -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
}
}
}

View File

@@ -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])

View File

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

View File

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

View File

@@ -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",