chldapasswd/ratelimit.go
Pierre-Olivier Mercier 2a9eec233a fix(security): add per-IP rate limiting to all authentication endpoints
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>
2026-03-06 15:30:48 +07:00

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
}