feat(middleware): add email verification rate limit
This commit is contained in:
70
middleware/email-verification-rate-limit.go
Normal file
70
middleware/email-verification-rate-limit.go
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"one-api/common"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
EmailVerificationRateLimitMark = "EV"
|
||||||
|
EmailVerificationMaxRequests = 2 // 30秒内最多2次
|
||||||
|
EmailVerificationDuration = 30 // 30秒时间窗口
|
||||||
|
)
|
||||||
|
|
||||||
|
func redisEmailVerificationRateLimiter(c *gin.Context) {
|
||||||
|
ctx := context.Background()
|
||||||
|
rdb := common.RDB
|
||||||
|
key := "emailVerification:" + EmailVerificationRateLimitMark + ":" + c.ClientIP()
|
||||||
|
|
||||||
|
listLength, err := rdb.LLen(ctx, key).Result()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Redis限流检查失败:", err.Error())
|
||||||
|
c.Status(http.StatusInternalServerError)
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if listLength < EmailVerificationMaxRequests {
|
||||||
|
rdb.LPush(ctx, key, time.Now().Format(timeFormat))
|
||||||
|
rdb.Expire(ctx, key, time.Duration(EmailVerificationDuration)*time.Second)
|
||||||
|
c.Next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusTooManyRequests, gin.H{
|
||||||
|
"success": false,
|
||||||
|
"message": fmt.Sprintf("发送过于频繁,请等待 %d 秒后再试", EmailVerificationDuration),
|
||||||
|
})
|
||||||
|
c.Abort()
|
||||||
|
}
|
||||||
|
|
||||||
|
func memoryEmailVerificationRateLimiter(c *gin.Context) {
|
||||||
|
key := EmailVerificationRateLimitMark + ":" + c.ClientIP()
|
||||||
|
|
||||||
|
if !inMemoryRateLimiter.Request(key, EmailVerificationMaxRequests, EmailVerificationDuration) {
|
||||||
|
c.JSON(http.StatusTooManyRequests, gin.H{
|
||||||
|
"success": false,
|
||||||
|
"message": "发送过于频繁,请稍后再试",
|
||||||
|
})
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
func EmailVerificationRateLimit() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
if common.RedisEnabled {
|
||||||
|
redisEmailVerificationRateLimiter(c)
|
||||||
|
} else {
|
||||||
|
inMemoryRateLimiter.Init(common.RateLimitKeyExpirationDuration)
|
||||||
|
memoryEmailVerificationRateLimiter(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,7 +24,7 @@ func SetApiRouter(router *gin.Engine) {
|
|||||||
//apiRouter.GET("/midjourney", controller.GetMidjourney)
|
//apiRouter.GET("/midjourney", controller.GetMidjourney)
|
||||||
apiRouter.GET("/home_page_content", controller.GetHomePageContent)
|
apiRouter.GET("/home_page_content", controller.GetHomePageContent)
|
||||||
apiRouter.GET("/pricing", middleware.TryUserAuth(), controller.GetPricing)
|
apiRouter.GET("/pricing", middleware.TryUserAuth(), controller.GetPricing)
|
||||||
apiRouter.GET("/verification", middleware.CriticalRateLimit(), 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(), controller.ResetPassword)
|
||||||
apiRouter.GET("/oauth/github", middleware.CriticalRateLimit(), controller.GitHubOAuth)
|
apiRouter.GET("/oauth/github", middleware.CriticalRateLimit(), controller.GitHubOAuth)
|
||||||
@@ -67,7 +67,7 @@ func SetApiRouter(router *gin.Engine) {
|
|||||||
selfRoute.POST("/stripe/amount", controller.RequestStripeAmount)
|
selfRoute.POST("/stripe/amount", controller.RequestStripeAmount)
|
||||||
selfRoute.POST("/aff_transfer", controller.TransferAffQuota)
|
selfRoute.POST("/aff_transfer", controller.TransferAffQuota)
|
||||||
selfRoute.PUT("/setting", controller.UpdateUserSetting)
|
selfRoute.PUT("/setting", controller.UpdateUserSetting)
|
||||||
|
|
||||||
// 2FA routes
|
// 2FA routes
|
||||||
selfRoute.GET("/2fa/status", controller.Get2FAStatus)
|
selfRoute.GET("/2fa/status", controller.Get2FAStatus)
|
||||||
selfRoute.POST("/2fa/setup", controller.Setup2FA)
|
selfRoute.POST("/2fa/setup", controller.Setup2FA)
|
||||||
@@ -86,7 +86,7 @@ func SetApiRouter(router *gin.Engine) {
|
|||||||
adminRoute.POST("/manage", controller.ManageUser)
|
adminRoute.POST("/manage", controller.ManageUser)
|
||||||
adminRoute.PUT("/", controller.UpdateUser)
|
adminRoute.PUT("/", controller.UpdateUser)
|
||||||
adminRoute.DELETE("/:id", controller.DeleteUser)
|
adminRoute.DELETE("/:id", controller.DeleteUser)
|
||||||
|
|
||||||
// Admin 2FA routes
|
// Admin 2FA routes
|
||||||
adminRoute.GET("/2fa/stats", controller.Admin2FAStats)
|
adminRoute.GET("/2fa/stats", controller.Admin2FAStats)
|
||||||
adminRoute.DELETE("/:id/2fa", controller.AdminDisable2FA)
|
adminRoute.DELETE("/:id/2fa", controller.AdminDisable2FA)
|
||||||
@@ -200,22 +200,22 @@ func SetApiRouter(router *gin.Engine) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
vendorRoute := apiRouter.Group("/vendors")
|
vendorRoute := apiRouter.Group("/vendors")
|
||||||
vendorRoute.Use(middleware.AdminAuth())
|
vendorRoute.Use(middleware.AdminAuth())
|
||||||
{
|
{
|
||||||
vendorRoute.GET("/", controller.GetAllVendors)
|
vendorRoute.GET("/", controller.GetAllVendors)
|
||||||
vendorRoute.GET("/search", controller.SearchVendors)
|
vendorRoute.GET("/search", controller.SearchVendors)
|
||||||
vendorRoute.GET("/:id", controller.GetVendorMeta)
|
vendorRoute.GET("/:id", controller.GetVendorMeta)
|
||||||
vendorRoute.POST("/", controller.CreateVendorMeta)
|
vendorRoute.POST("/", controller.CreateVendorMeta)
|
||||||
vendorRoute.PUT("/", controller.UpdateVendorMeta)
|
vendorRoute.PUT("/", controller.UpdateVendorMeta)
|
||||||
vendorRoute.DELETE("/:id", controller.DeleteVendorMeta)
|
vendorRoute.DELETE("/:id", controller.DeleteVendorMeta)
|
||||||
}
|
}
|
||||||
|
|
||||||
modelsRoute := apiRouter.Group("/models")
|
modelsRoute := apiRouter.Group("/models")
|
||||||
modelsRoute.Use(middleware.AdminAuth())
|
modelsRoute.Use(middleware.AdminAuth())
|
||||||
{
|
{
|
||||||
modelsRoute.GET("/missing", controller.GetMissingModels)
|
modelsRoute.GET("/missing", controller.GetMissingModels)
|
||||||
modelsRoute.GET("/", controller.GetAllModelsMeta)
|
modelsRoute.GET("/", controller.GetAllModelsMeta)
|
||||||
modelsRoute.GET("/search", controller.SearchModelsMeta)
|
modelsRoute.GET("/search", controller.SearchModelsMeta)
|
||||||
modelsRoute.GET("/:id", controller.GetModelMeta)
|
modelsRoute.GET("/:id", controller.GetModelMeta)
|
||||||
modelsRoute.POST("/", controller.CreateModelMeta)
|
modelsRoute.POST("/", controller.CreateModelMeta)
|
||||||
modelsRoute.PUT("/", controller.UpdateModelMeta)
|
modelsRoute.PUT("/", controller.UpdateModelMeta)
|
||||||
|
|||||||
Reference in New Issue
Block a user