Files
chat_room/internal/middleware/rate_limit_middleware.go
T

71 lines
1.9 KiB
Go
Raw Normal View History

package middleware
import (
"net/http"
"sync"
"time"
"github.com/gin-gonic/gin"
"dating-app/internal/utils"
)
type bucket struct {
tokens float64
lastSeenAt time.Time
}
// RateLimiter یک rate limiter ساده‌ی token-bucket به‌ازای هر IP؛ برای جلوگیری
// از سوءاستفاده از اندپوینت‌های حساس مثل درخواست OTP (که هزینه‌ی پیامک داره
// و هدف مناسبی برای اسپم/brute-force هست). maxTokens یعنی سقف درخواست
// پشت‌سرهم مجاز، refillPerSecond یعنی سرعت شارژ دوباره‌ی سطل.
func RateLimiter(maxTokens, refillPerSecond float64) gin.HandlerFunc {
var mu sync.Mutex
buckets := make(map[string]*bucket)
// سطل‌های راکد (IP هایی که مدتیه درخواست نزدن) رو دوره‌ای پاک می‌کنه تا
// حافظه با تعداد IP های دیده‌شده بی‌نهایت رشد نکنه.
go func() {
ticker := time.NewTicker(10 * time.Minute)
defer ticker.Stop()
for range ticker.C {
cutoff := time.Now().Add(-10 * time.Minute)
mu.Lock()
for k, b := range buckets {
if b.lastSeenAt.Before(cutoff) {
delete(buckets, k)
}
}
mu.Unlock()
}
}()
return func(c *gin.Context) {
key := c.ClientIP()
now := time.Now()
mu.Lock()
b, ok := buckets[key]
if !ok {
b = &bucket{tokens: maxTokens, lastSeenAt: now}
buckets[key] = b
}
elapsed := now.Sub(b.lastSeenAt).Seconds()
b.tokens = min(maxTokens, b.tokens+elapsed*refillPerSecond)
b.lastSeenAt = now
allowed := b.tokens >= 1
if allowed {
b.tokens--
}
mu.Unlock()
if !allowed {
utils.Fail(c, http.StatusTooManyRequests, "درخواست‌های زیادی فرستادی؛ کمی صبر کن و دوباره امتحان کن", nil)
c.Abort()
return
}
c.Next()
}
}