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() } }