Implement sliding window rate limiter to prevent brute-force attacks: - /auth and /login: 20 requests/minute per IP - /change: 10 POST requests/minute per IP - /lost: 5 POST requests/minute per IP (prevents email spam and user enumeration) - /reset: 10 POST requests/minute per IP - /api/v1/aliases: 30 requests/minute per IP Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
63 lines
1.2 KiB
Go
63 lines
1.2 KiB
Go
package main
|
|
|
|
import (
|
|
"net"
|
|
"net/http"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type rateLimiter struct {
|
|
mu sync.Mutex
|
|
counts map[string][]time.Time
|
|
limit int
|
|
window time.Duration
|
|
}
|
|
|
|
func newRateLimiter(limit int, window time.Duration) *rateLimiter {
|
|
return &rateLimiter{
|
|
counts: make(map[string][]time.Time),
|
|
limit: limit,
|
|
window: window,
|
|
}
|
|
}
|
|
|
|
func (rl *rateLimiter) Allow(key string) bool {
|
|
rl.mu.Lock()
|
|
defer rl.mu.Unlock()
|
|
|
|
now := time.Now()
|
|
windowStart := now.Add(-rl.window)
|
|
|
|
timestamps := rl.counts[key]
|
|
filtered := timestamps[:0]
|
|
for _, t := range timestamps {
|
|
if t.After(windowStart) {
|
|
filtered = append(filtered, t)
|
|
}
|
|
}
|
|
|
|
if len(filtered) >= rl.limit {
|
|
rl.counts[key] = filtered
|
|
return false
|
|
}
|
|
|
|
rl.counts[key] = append(filtered, now)
|
|
return true
|
|
}
|
|
|
|
var (
|
|
authLimiter = newRateLimiter(20, time.Minute)
|
|
changeLimiter = newRateLimiter(10, time.Minute)
|
|
lostLimiter = newRateLimiter(5, time.Minute)
|
|
resetLimiter = newRateLimiter(10, time.Minute)
|
|
aliasLimiter = newRateLimiter(30, time.Minute)
|
|
)
|
|
|
|
func remoteIP(r *http.Request) string {
|
|
host, _, err := net.SplitHostPort(r.RemoteAddr)
|
|
if err != nil {
|
|
return r.RemoteAddr
|
|
}
|
|
return host
|
|
}
|