package middleware import ( "net/http" "opencatd-open/internal/store" "strings" "github.com/gin-gonic/gin" "gorm.io/gorm" ) // keyPrefixLen 是 key_prefix 列的截断长度,必须与 api.go:459 的 keyValue[:12] 一致。 // 真实 key 为 sk-ot- + 48 位 hex(54 字符),故 12 位足够唯一。 const keyPrefixLen = 12 func AuthLLM(db *gorm.DB) gin.HandlerFunc { return func(c *gin.Context) { key := extractAPIKey(c.GetHeader("Authorization")) // 区分「没传」和「传了但不对」,便于排查客户端配置。 if strings.TrimSpace(c.GetHeader("Authorization")) == "" { unauthorized(c, "未提供认证信息") return } // 长度不足时直接拒绝:避免下方 authToken[:12] 越界 panic 打崩进程。 if len(key) < keyPrefixLen { unauthorized(c, "无效的API密钥") return } var apiKey store.APIKey if err := db.Where("key_prefix = ? AND status = ?", key[:keyPrefixLen], store.KeyStatusActive).First(&apiKey).Error; err != nil { unauthorized(c, "无效的API密钥") return } // Verify full key hash if apiKey.KeyHash != store.HashAPIKey(key) { unauthorized(c, "无效的API密钥") return } c.Set("api_key", &apiKey) c.Set("user_id", apiKey.UserID) c.Next() } } // extractAPIKey 从 Authorization 头取 Bearer token,兼容无 "Bearer " 前缀的直传。 func extractAPIKey(auth string) string { auth = strings.TrimSpace(auth) if strings.HasPrefix(auth, "Bearer ") { return strings.TrimSpace(auth[len("Bearer "):]) } return auth } func unauthorized(c *gin.Context, message string) { c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ "error": map[string]interface{}{ "message": message, "type": "invalid_request_error", }, }) }