Skip to content

Commit ff2e0db

Browse files
committed
feat(middleware): add email verification rate limit
1 parent 8217e69 commit ff2e0db

2 files changed

Lines changed: 86 additions & 16 deletions

File tree

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package middleware
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/http"
7+
"one-api/common"
8+
"time"
9+
10+
"github.com/gin-gonic/gin"
11+
)
12+
13+
const (
14+
EmailVerificationRateLimitMark = "EV"
15+
EmailVerificationMaxRequests = 2 // 30秒内最多2次
16+
EmailVerificationDuration = 30 // 30秒时间窗口
17+
)
18+
19+
func redisEmailVerificationRateLimiter(c *gin.Context) {
20+
ctx := context.Background()
21+
rdb := common.RDB
22+
key := "emailVerification:" + EmailVerificationRateLimitMark + ":" + c.ClientIP()
23+
24+
listLength, err := rdb.LLen(ctx, key).Result()
25+
if err != nil {
26+
fmt.Println("Redis限流检查失败:", err.Error())
27+
c.Status(http.StatusInternalServerError)
28+
c.Abort()
29+
return
30+
}
31+
32+
if listLength < EmailVerificationMaxRequests {
33+
rdb.LPush(ctx, key, time.Now().Format(timeFormat))
34+
rdb.Expire(ctx, key, time.Duration(EmailVerificationDuration)*time.Second)
35+
c.Next()
36+
return
37+
}
38+
39+
c.JSON(http.StatusTooManyRequests, gin.H{
40+
"success": false,
41+
"message": fmt.Sprintf("发送过于频繁,请等待 %d 秒后再试", EmailVerificationDuration),
42+
})
43+
c.Abort()
44+
}
45+
46+
func memoryEmailVerificationRateLimiter(c *gin.Context) {
47+
key := EmailVerificationRateLimitMark + ":" + c.ClientIP()
48+
49+
if !inMemoryRateLimiter.Request(key, EmailVerificationMaxRequests, EmailVerificationDuration) {
50+
c.JSON(http.StatusTooManyRequests, gin.H{
51+
"success": false,
52+
"message": "发送过于频繁,请稍后再试",
53+
})
54+
c.Abort()
55+
return
56+
}
57+
58+
c.Next()
59+
}
60+
61+
func EmailVerificationRateLimit() gin.HandlerFunc {
62+
return func(c *gin.Context) {
63+
if common.RedisEnabled {
64+
redisEmailVerificationRateLimiter(c)
65+
} else {
66+
inMemoryRateLimiter.Init(common.RateLimitKeyExpirationDuration)
67+
memoryEmailVerificationRateLimiter(c)
68+
}
69+
}
70+
}

router/api-router.go

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ func SetApiRouter(router *gin.Engine) {
2424
//apiRouter.GET("/midjourney", controller.GetMidjourney)
2525
apiRouter.GET("/home_page_content", controller.GetHomePageContent)
2626
apiRouter.GET("/pricing", middleware.TryUserAuth(), controller.GetPricing)
27-
apiRouter.GET("/verification", middleware.CriticalRateLimit(), middleware.TurnstileCheck(), controller.SendEmailVerification)
27+
apiRouter.GET("/verification", middleware.EmailVerificationRateLimit(), middleware.TurnstileCheck(), controller.SendEmailVerification)
2828
apiRouter.GET("/reset_password", middleware.CriticalRateLimit(), middleware.TurnstileCheck(), controller.SendPasswordResetEmail)
2929
apiRouter.POST("/user/reset", middleware.CriticalRateLimit(), controller.ResetPassword)
3030
apiRouter.GET("/oauth/github", middleware.CriticalRateLimit(), controller.GitHubOAuth)
@@ -67,7 +67,7 @@ func SetApiRouter(router *gin.Engine) {
6767
selfRoute.POST("/stripe/amount", controller.RequestStripeAmount)
6868
selfRoute.POST("/aff_transfer", controller.TransferAffQuota)
6969
selfRoute.PUT("/setting", controller.UpdateUserSetting)
70-
70+
7171
// 2FA routes
7272
selfRoute.GET("/2fa/status", controller.Get2FAStatus)
7373
selfRoute.POST("/2fa/setup", controller.Setup2FA)
@@ -86,7 +86,7 @@ func SetApiRouter(router *gin.Engine) {
8686
adminRoute.POST("/manage", controller.ManageUser)
8787
adminRoute.PUT("/", controller.UpdateUser)
8888
adminRoute.DELETE("/:id", controller.DeleteUser)
89-
89+
9090
// Admin 2FA routes
9191
adminRoute.GET("/2fa/stats", controller.Admin2FAStats)
9292
adminRoute.DELETE("/:id/2fa", controller.AdminDisable2FA)
@@ -200,22 +200,22 @@ func SetApiRouter(router *gin.Engine) {
200200
}
201201

202202
vendorRoute := apiRouter.Group("/vendors")
203-
vendorRoute.Use(middleware.AdminAuth())
204-
{
205-
vendorRoute.GET("/", controller.GetAllVendors)
206-
vendorRoute.GET("/search", controller.SearchVendors)
207-
vendorRoute.GET("/:id", controller.GetVendorMeta)
208-
vendorRoute.POST("/", controller.CreateVendorMeta)
209-
vendorRoute.PUT("/", controller.UpdateVendorMeta)
210-
vendorRoute.DELETE("/:id", controller.DeleteVendorMeta)
211-
}
212-
213-
modelsRoute := apiRouter.Group("/models")
203+
vendorRoute.Use(middleware.AdminAuth())
204+
{
205+
vendorRoute.GET("/", controller.GetAllVendors)
206+
vendorRoute.GET("/search", controller.SearchVendors)
207+
vendorRoute.GET("/:id", controller.GetVendorMeta)
208+
vendorRoute.POST("/", controller.CreateVendorMeta)
209+
vendorRoute.PUT("/", controller.UpdateVendorMeta)
210+
vendorRoute.DELETE("/:id", controller.DeleteVendorMeta)
211+
}
212+
213+
modelsRoute := apiRouter.Group("/models")
214214
modelsRoute.Use(middleware.AdminAuth())
215215
{
216216
modelsRoute.GET("/missing", controller.GetMissingModels)
217-
modelsRoute.GET("/", controller.GetAllModelsMeta)
218-
modelsRoute.GET("/search", controller.SearchModelsMeta)
217+
modelsRoute.GET("/", controller.GetAllModelsMeta)
218+
modelsRoute.GET("/search", controller.SearchModelsMeta)
219219
modelsRoute.GET("/:id", controller.GetModelMeta)
220220
modelsRoute.POST("/", controller.CreateModelMeta)
221221
modelsRoute.PUT("/", controller.UpdateModelMeta)

0 commit comments

Comments
 (0)