userRateLimitFactory creates a rate limiter keyed by authenticated user ID instead of client IP, making it resistant to proxy rotation attacks. Must be used AFTER authentication middleware (UserAuth).
(maxRequestNum int, duration int64, mark string)
| 120 | // instead of client IP, making it resistant to proxy rotation attacks. |
| 121 | // Must be used AFTER authentication middleware (UserAuth). |
| 122 | func userRateLimitFactory(maxRequestNum int, duration int64, mark string) func(c *gin.Context) { |
| 123 | if common.RedisEnabled { |
| 124 | return func(c *gin.Context) { |
| 125 | userId := c.GetInt("id") |
| 126 | if userId == 0 { |
| 127 | c.Status(http.StatusUnauthorized) |
| 128 | c.Abort() |
| 129 | return |
| 130 | } |
| 131 | key := fmt.Sprintf("rateLimit:%s:user:%d", mark, userId) |
| 132 | userRedisRateLimiter(c, maxRequestNum, duration, key) |
| 133 | } |
| 134 | } |
| 135 | // It's safe to call multi times. |
| 136 | inMemoryRateLimiter.Init(common.RateLimitKeyExpirationDuration) |
| 137 | return func(c *gin.Context) { |
| 138 | userId := c.GetInt("id") |
| 139 | if userId == 0 { |
| 140 | c.Status(http.StatusUnauthorized) |
| 141 | c.Abort() |
| 142 | return |
| 143 | } |
| 144 | key := fmt.Sprintf("%s:user:%d", mark, userId) |
| 145 | if !inMemoryRateLimiter.Request(key, maxRequestNum, duration) { |
| 146 | c.Status(http.StatusTooManyRequests) |
| 147 | c.Abort() |
| 148 | return |
| 149 | } |
| 150 | } |
| 151 | } |
| 152 | |
| 153 | // userRedisRateLimiter is like redisRateLimiter but accepts a pre-built key |
| 154 | // (to support user-ID-based keys). |
no test coverage detected