mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-09-04 16:42:20 +00:00
fix: limit anonymous request body (#5244)
* fix: limit anonymous request body (env ANONYMOUS_REQUEST_BODY_LIMIT_KB = 512) * fix: allow disabling anonymous request body limit
This commit is contained in:
@@ -136,6 +136,7 @@ func initConstantEnv() {
|
|||||||
constant.StreamScannerMaxBufferMB = GetEnvOrDefault("STREAM_SCANNER_MAX_BUFFER_MB", 128)
|
constant.StreamScannerMaxBufferMB = GetEnvOrDefault("STREAM_SCANNER_MAX_BUFFER_MB", 128)
|
||||||
// MaxRequestBodyMB 请求体最大大小(解压后),用于防止超大请求/zip bomb导致内存暴涨
|
// MaxRequestBodyMB 请求体最大大小(解压后),用于防止超大请求/zip bomb导致内存暴涨
|
||||||
constant.MaxRequestBodyMB = GetEnvOrDefault("MAX_REQUEST_BODY_MB", 128)
|
constant.MaxRequestBodyMB = GetEnvOrDefault("MAX_REQUEST_BODY_MB", 128)
|
||||||
|
constant.AnonymousRequestBodyLimitKB = GetEnvOrDefault("ANONYMOUS_REQUEST_BODY_LIMIT_KB", 512)
|
||||||
// ForceStreamOption 覆盖请求参数,强制返回usage信息
|
// ForceStreamOption 覆盖请求参数,强制返回usage信息
|
||||||
constant.ForceStreamOption = GetEnvOrDefaultBool("FORCE_STREAM_OPTION", true)
|
constant.ForceStreamOption = GetEnvOrDefaultBool("FORCE_STREAM_OPTION", true)
|
||||||
constant.CountToken = GetEnvOrDefaultBool("CountToken", true)
|
constant.CountToken = GetEnvOrDefaultBool("CountToken", true)
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import "github.com/QuantumNous/new-api/constant"
|
||||||
|
|
||||||
|
const defaultAnonymousRequestBodyLimitKB = 512
|
||||||
|
|
||||||
|
func GetAnonymousRequestBodyLimitBytes() int64 {
|
||||||
|
limitKB := constant.AnonymousRequestBodyLimitKB
|
||||||
|
if limitKB < 0 {
|
||||||
|
limitKB = defaultAnonymousRequestBodyLimitKB
|
||||||
|
}
|
||||||
|
return int64(limitKB) << 10
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ var GetMediaToken bool
|
|||||||
var GetMediaTokenNotStream bool
|
var GetMediaTokenNotStream bool
|
||||||
var UpdateTask bool
|
var UpdateTask bool
|
||||||
var MaxRequestBodyMB int
|
var MaxRequestBodyMB int
|
||||||
|
var AnonymousRequestBodyLimitKB int
|
||||||
var AzureDefaultAPIVersion string
|
var AzureDefaultAPIVersion string
|
||||||
var NotifyLimitCount int
|
var NotifyLimitCount int
|
||||||
var NotificationLimitDurationMinute int
|
var NotificationLimitDurationMinute int
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/QuantumNous/new-api/common"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AnonymousRequestBodyLimit() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
maxBytes := common.GetAnonymousRequestBodyLimitBytes()
|
||||||
|
if maxBytes <= 0 || c.Request.Body == nil {
|
||||||
|
c.Next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
originalBody := c.Request.Body
|
||||||
|
limitedBody, err := readAnonymousRequestBody(originalBody, maxBytes)
|
||||||
|
_ = originalBody.Close()
|
||||||
|
if err != nil {
|
||||||
|
if common.IsRequestBodyTooLargeError(err) {
|
||||||
|
c.AbortWithStatus(http.StatusRequestEntityTooLarge)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.AbortWithStatus(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Request.Body = io.NopCloser(bytes.NewReader(limitedBody))
|
||||||
|
c.Request.ContentLength = int64(len(limitedBody))
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func readAnonymousRequestBody(body io.Reader, maxBytes int64) ([]byte, error) {
|
||||||
|
data, err := io.ReadAll(io.LimitReader(body, maxBytes+1))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if int64(len(data)) > maxBytes {
|
||||||
|
return nil, common.ErrRequestBodyTooLarge
|
||||||
|
}
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
+17
-16
@@ -17,9 +17,10 @@ func SetApiRouter(router *gin.Engine) {
|
|||||||
apiRouter.Use(gzip.Gzip(gzip.DefaultCompression))
|
apiRouter.Use(gzip.Gzip(gzip.DefaultCompression))
|
||||||
apiRouter.Use(middleware.BodyStorageCleanup()) // 清理请求体存储
|
apiRouter.Use(middleware.BodyStorageCleanup()) // 清理请求体存储
|
||||||
apiRouter.Use(middleware.GlobalAPIRateLimit())
|
apiRouter.Use(middleware.GlobalAPIRateLimit())
|
||||||
|
anonymousRequestBodyLimit := middleware.AnonymousRequestBodyLimit()
|
||||||
{
|
{
|
||||||
apiRouter.GET("/setup", controller.GetSetup)
|
apiRouter.GET("/setup", controller.GetSetup)
|
||||||
apiRouter.POST("/setup", controller.PostSetup)
|
apiRouter.POST("/setup", anonymousRequestBodyLimit, controller.PostSetup)
|
||||||
apiRouter.GET("/status", controller.GetStatus)
|
apiRouter.GET("/status", controller.GetStatus)
|
||||||
apiRouter.GET("/uptime/status", controller.GetUptimeKumaStatus)
|
apiRouter.GET("/uptime/status", controller.GetUptimeKumaStatus)
|
||||||
apiRouter.GET("/models", middleware.UserAuth(), controller.DashboardListModels)
|
apiRouter.GET("/models", middleware.UserAuth(), controller.DashboardListModels)
|
||||||
@@ -40,39 +41,39 @@ func SetApiRouter(router *gin.Engine) {
|
|||||||
apiRouter.GET("/rankings", middleware.HeaderNavModuleAuth("rankings"), controller.GetRankings)
|
apiRouter.GET("/rankings", middleware.HeaderNavModuleAuth("rankings"), controller.GetRankings)
|
||||||
apiRouter.GET("/verification", middleware.EmailVerificationRateLimit(), middleware.TurnstileCheck(), controller.SendEmailVerification)
|
apiRouter.GET("/verification", middleware.EmailVerificationRateLimit(), middleware.TurnstileCheck(), controller.SendEmailVerification)
|
||||||
apiRouter.GET("/reset_password", middleware.CriticalRateLimit(), middleware.TurnstileCheck(), controller.SendPasswordResetEmail)
|
apiRouter.GET("/reset_password", middleware.CriticalRateLimit(), middleware.TurnstileCheck(), controller.SendPasswordResetEmail)
|
||||||
apiRouter.POST("/user/reset", middleware.CriticalRateLimit(), controller.ResetPassword)
|
apiRouter.POST("/user/reset", middleware.CriticalRateLimit(), anonymousRequestBodyLimit, controller.ResetPassword)
|
||||||
// OAuth routes - specific routes must come before :provider wildcard
|
// OAuth routes - specific routes must come before :provider wildcard
|
||||||
apiRouter.GET("/oauth/state", middleware.CriticalRateLimit(), controller.GenerateOAuthCode)
|
apiRouter.GET("/oauth/state", middleware.CriticalRateLimit(), controller.GenerateOAuthCode)
|
||||||
apiRouter.POST("/oauth/email/bind", middleware.CriticalRateLimit(), controller.EmailBind)
|
apiRouter.POST("/oauth/email/bind", middleware.CriticalRateLimit(), anonymousRequestBodyLimit, controller.EmailBind)
|
||||||
// Non-standard OAuth (WeChat, Telegram) - keep original routes
|
// Non-standard OAuth (WeChat, Telegram) - keep original routes
|
||||||
apiRouter.GET("/oauth/wechat", middleware.CriticalRateLimit(), controller.WeChatAuth)
|
apiRouter.GET("/oauth/wechat", middleware.CriticalRateLimit(), controller.WeChatAuth)
|
||||||
apiRouter.POST("/oauth/wechat/bind", middleware.CriticalRateLimit(), controller.WeChatBind)
|
apiRouter.POST("/oauth/wechat/bind", middleware.CriticalRateLimit(), anonymousRequestBodyLimit, controller.WeChatBind)
|
||||||
apiRouter.GET("/oauth/telegram/login", middleware.CriticalRateLimit(), controller.TelegramLogin)
|
apiRouter.GET("/oauth/telegram/login", middleware.CriticalRateLimit(), controller.TelegramLogin)
|
||||||
apiRouter.GET("/oauth/telegram/bind", middleware.CriticalRateLimit(), controller.TelegramBind)
|
apiRouter.GET("/oauth/telegram/bind", middleware.CriticalRateLimit(), controller.TelegramBind)
|
||||||
// Standard OAuth providers (GitHub, Discord, OIDC, LinuxDO) - unified route
|
// Standard OAuth providers (GitHub, Discord, OIDC, LinuxDO) - unified route
|
||||||
apiRouter.GET("/oauth/:provider", middleware.CriticalRateLimit(), controller.HandleOAuth)
|
apiRouter.GET("/oauth/:provider", middleware.CriticalRateLimit(), controller.HandleOAuth)
|
||||||
apiRouter.GET("/ratio_config", middleware.CriticalRateLimit(), controller.GetRatioConfig)
|
apiRouter.GET("/ratio_config", middleware.CriticalRateLimit(), controller.GetRatioConfig)
|
||||||
|
|
||||||
apiRouter.POST("/stripe/webhook", controller.StripeWebhook)
|
apiRouter.POST("/stripe/webhook", anonymousRequestBodyLimit, controller.StripeWebhook)
|
||||||
apiRouter.POST("/creem/webhook", controller.CreemWebhook)
|
apiRouter.POST("/creem/webhook", anonymousRequestBodyLimit, controller.CreemWebhook)
|
||||||
apiRouter.POST("/waffo/webhook", controller.WaffoWebhook)
|
apiRouter.POST("/waffo/webhook", anonymousRequestBodyLimit, controller.WaffoWebhook)
|
||||||
// :env separates test vs prod URLs so the operator can register each
|
// :env separates test vs prod URLs so the operator can register each
|
||||||
// in Pancake's matching webhook slot; handler enforces env match.
|
// in Pancake's matching webhook slot; handler enforces env match.
|
||||||
apiRouter.POST("/waffo-pancake/webhook/:env", controller.WaffoPancakeWebhook)
|
apiRouter.POST("/waffo-pancake/webhook/:env", anonymousRequestBodyLimit, controller.WaffoPancakeWebhook)
|
||||||
|
|
||||||
// Universal secure verification routes
|
// Universal secure verification routes
|
||||||
apiRouter.POST("/verify", middleware.UserAuth(), middleware.CriticalRateLimit(), controller.UniversalVerify)
|
apiRouter.POST("/verify", middleware.UserAuth(), middleware.CriticalRateLimit(), controller.UniversalVerify)
|
||||||
|
|
||||||
userRoute := apiRouter.Group("/user")
|
userRoute := apiRouter.Group("/user")
|
||||||
{
|
{
|
||||||
userRoute.POST("/register", middleware.CriticalRateLimit(), middleware.TurnstileCheck(), controller.Register)
|
userRoute.POST("/register", middleware.CriticalRateLimit(), anonymousRequestBodyLimit, middleware.TurnstileCheck(), controller.Register)
|
||||||
userRoute.POST("/login", middleware.CriticalRateLimit(), middleware.TurnstileCheck(), controller.Login)
|
userRoute.POST("/login", middleware.CriticalRateLimit(), anonymousRequestBodyLimit, middleware.TurnstileCheck(), controller.Login)
|
||||||
userRoute.POST("/login/2fa", middleware.CriticalRateLimit(), controller.Verify2FALogin)
|
userRoute.POST("/login/2fa", middleware.CriticalRateLimit(), anonymousRequestBodyLimit, controller.Verify2FALogin)
|
||||||
userRoute.POST("/passkey/login/begin", middleware.CriticalRateLimit(), controller.PasskeyLoginBegin)
|
userRoute.POST("/passkey/login/begin", middleware.CriticalRateLimit(), anonymousRequestBodyLimit, controller.PasskeyLoginBegin)
|
||||||
userRoute.POST("/passkey/login/finish", middleware.CriticalRateLimit(), controller.PasskeyLoginFinish)
|
userRoute.POST("/passkey/login/finish", middleware.CriticalRateLimit(), anonymousRequestBodyLimit, controller.PasskeyLoginFinish)
|
||||||
//userRoute.POST("/tokenlog", middleware.CriticalRateLimit(), controller.TokenLog)
|
//userRoute.POST("/tokenlog", middleware.CriticalRateLimit(), controller.TokenLog)
|
||||||
userRoute.GET("/logout", controller.Logout)
|
userRoute.GET("/logout", controller.Logout)
|
||||||
userRoute.POST("/epay/notify", controller.EpayNotify)
|
userRoute.POST("/epay/notify", anonymousRequestBodyLimit, controller.EpayNotify)
|
||||||
userRoute.GET("/epay/notify", controller.EpayNotify)
|
userRoute.GET("/epay/notify", controller.EpayNotify)
|
||||||
userRoute.GET("/groups", controller.GetUserGroups)
|
userRoute.GET("/groups", controller.GetUserGroups)
|
||||||
|
|
||||||
@@ -176,10 +177,10 @@ func SetApiRouter(router *gin.Engine) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Subscription payment callbacks (no auth)
|
// Subscription payment callbacks (no auth)
|
||||||
apiRouter.POST("/subscription/epay/notify", controller.SubscriptionEpayNotify)
|
apiRouter.POST("/subscription/epay/notify", anonymousRequestBodyLimit, controller.SubscriptionEpayNotify)
|
||||||
apiRouter.GET("/subscription/epay/notify", controller.SubscriptionEpayNotify)
|
apiRouter.GET("/subscription/epay/notify", controller.SubscriptionEpayNotify)
|
||||||
apiRouter.GET("/subscription/epay/return", controller.SubscriptionEpayReturn)
|
apiRouter.GET("/subscription/epay/return", controller.SubscriptionEpayReturn)
|
||||||
apiRouter.POST("/subscription/epay/return", controller.SubscriptionEpayReturn)
|
apiRouter.POST("/subscription/epay/return", anonymousRequestBodyLimit, controller.SubscriptionEpayReturn)
|
||||||
optionRoute := apiRouter.Group("/option")
|
optionRoute := apiRouter.Group("/option")
|
||||||
optionRoute.Use(middleware.RootAuth())
|
optionRoute.Use(middleware.RootAuth())
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user