package middleware import ( "net/http" "sync" "time" "aggios-app/backend/internal/config" ) type rateLimiter struct { mu sync.Mutex attempts map[string][]time.Time maxAttempts int } func newRateLimiter(maxAttempts int) *rateLimiter { rl := &rateLimiter{ attempts: make(map[string][]time.Time), maxAttempts: maxAttempts, } // Clean old entries every minute go func() { ticker := time.NewTicker(1 * time.Minute) defer ticker.Stop() for range ticker.C { rl.cleanup() } }() return rl } func (rl *rateLimiter) cleanup() { rl.mu.Lock() defer rl.mu.Unlock() now := time.Now() for ip, attempts := range rl.attempts { var valid []time.Time for _, t := range attempts { if now.Sub(t) < time.Minute { valid = append(valid, t) } } if len(valid) == 0 { delete(rl.attempts, ip) } else { rl.attempts[ip] = valid } } } func (rl *rateLimiter) isAllowed(ip string) bool { rl.mu.Lock() defer rl.mu.Unlock() now := time.Now() attempts := rl.attempts[ip] // Filter attempts within the last minute var validAttempts []time.Time for _, t := range attempts { if now.Sub(t) < time.Minute { validAttempts = append(validAttempts, t) } } if len(validAttempts) >= rl.maxAttempts { return false } validAttempts = append(validAttempts, now) rl.attempts[ip] = validAttempts return true } // RateLimit limits requests per IP address func RateLimit(cfg *config.Config) func(http.Handler) http.Handler { limiter := newRateLimiter(cfg.Security.MaxAttemptsPerMin) return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ip := r.RemoteAddr if !limiter.isAllowed(ip) { http.Error(w, "Too many requests", http.StatusTooManyRequests) return } next.ServeHTTP(w, r) }) } }