fix gateway loopback validation chains

This commit is contained in:
2026-05-11 08:48:02 +08:00
parent ff666b1ece
commit ca7e76e815
42 changed files with 1641 additions and 129 deletions
+63
View File
@@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"net/http"
"strconv"
"strings"
"time"
@@ -468,6 +469,10 @@ func (s *Server) estimatePricing(w http.ResponseWriter, r *http.Request) {
writeError(w, http.StatusBadRequest, "model is required")
return
}
if !apiKeyScopeAllowed(user, kind) {
writeError(w, http.StatusForbidden, "api key scope does not allow this capability")
return
}
estimate, err := s.runner.Estimate(r.Context(), kind, model, body, user)
if err != nil {
if errors.Is(err, store.ErrNoModelCandidate) {
@@ -509,6 +514,10 @@ func (s *Server) createTask(kind string, compatible bool) http.Handler {
writeError(w, http.StatusBadRequest, "model is required")
return
}
if !apiKeyScopeAllowed(user, kind) {
writeError(w, http.StatusForbidden, "api key scope does not allow this capability")
return
}
task, err := s.store.CreateTask(r.Context(), store.CreateTaskInput{
Kind: kind,
@@ -567,6 +576,36 @@ func (s *Server) createTask(kind string, compatible bool) http.Handler {
})
}
func apiKeyScopeAllowed(user *auth.User, kind string) bool {
if user == nil || strings.TrimSpace(user.APIKeyID) == "" || len(user.APIKeyScopes) == 0 {
return true
}
required := scopeForTaskKind(kind)
for _, scope := range user.APIKeyScopes {
scope = strings.TrimSpace(strings.ToLower(scope))
if scope == "*" || scope == "all" || scope == required {
return true
}
if required == "chat" && (scope == "text" || scope == "text_generate") {
return true
}
}
return false
}
func scopeForTaskKind(kind string) string {
switch kind {
case "chat.completions", "responses":
return "chat"
case "images.generations", "images.edits":
return "image"
case "videos.generations":
return "video"
default:
return kind
}
}
func statusFromRunError(err error) int {
switch {
case errors.Is(err, store.ErrNoModelCandidate):
@@ -578,6 +617,30 @@ func statusFromRunError(err error) int {
}
}
func (s *Server) listTasks(w http.ResponseWriter, r *http.Request) {
user, ok := auth.UserFromContext(r.Context())
if !ok {
writeError(w, http.StatusUnauthorized, "unauthorized")
return
}
limit := 50
if raw := strings.TrimSpace(r.URL.Query().Get("limit")); raw != "" {
parsed, err := strconv.Atoi(raw)
if err != nil || parsed <= 0 {
writeError(w, http.StatusBadRequest, "invalid limit")
return
}
limit = parsed
}
tasks, err := s.store.ListTasks(r.Context(), user, limit)
if err != nil {
s.logger.Error("list tasks failed", "error", err)
writeError(w, http.StatusInternalServerError, "list tasks failed")
return
}
writeJSON(w, http.StatusOK, map[string]any{"items": tasks})
}
func boolValue(body map[string]any, key string) bool {
value, _ := body[key].(bool)
return value