Files
dejo-node/internal/api/middleware.go
2025-05-23 10:44:32 -03:00

91 lines
2.3 KiB
Go

package api
import (
"context"
"net"
"net/http"
"strings"
"sync"
"time"
limiter "github.com/ulule/limiter/v3"
memory "github.com/ulule/limiter/v3/drivers/store/memory"
stdmiddleware "github.com/ulule/limiter/v3/drivers/middleware/stdlib"
)
// SybilProtector mantém IPs bloqueados temporariamente
var sybilBlocklist = struct {
mu sync.RWMutex
block map[string]time.Time
}{block: make(map[string]time.Time)}
// isBlocked verifica se um IP está na lista de bloqueio
func isBlocked(ip string) bool {
sybilBlocklist.mu.RLock()
defer sybilBlocklist.mu.RUnlock()
expire, exists := sybilBlocklist.block[ip]
return exists && time.Now().Before(expire)
}
// blockIP adiciona um IP à blocklist por X segundos
func blockIP(ip string, duration time.Duration) {
sybilBlocklist.mu.Lock()
defer sybilBlocklist.mu.Unlock()
sybilBlocklist.block[ip] = time.Now().Add(duration)
}
// extractIP extrai IP puro do RemoteAddr
func extractIP(addr string) string {
ip, _, err := net.SplitHostPort(addr)
if err != nil {
return addr
}
return ip
}
// NewRateLimiterMiddleware cria middleware de rate limiting e proteção anti-Sybil
func NewRateLimiterMiddleware() func(http.Handler) http.Handler {
rate := limiter.Rate{
Period: 1 * time.Second,
Limit: 5,
}
store := memory.NewStore()
limiterInstance := limiter.New(store, rate)
middleware := stdmiddleware.NewMiddleware(limiterInstance)
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Ignorar WebSocket
if strings.HasPrefix(r.URL.Path, "/ws") {
next.ServeHTTP(w, r)
return
}
ip := extractIP(r.RemoteAddr)
if isBlocked(ip) {
w.WriteHeader(http.StatusTooManyRequests)
WriteError(w, http.StatusTooManyRequests, "IP bloqueado temporariamente por abuso")
return
}
ctx := context.WithValue(r.Context(), "real-ip", ip)
rec := &responseRecorder{ResponseWriter: w, status: 200}
middleware.Handler(next).ServeHTTP(rec, r.WithContext(ctx))
if rec.status == http.StatusTooManyRequests {
blockIP(ip, 30*time.Second)
}
})
}
}
// responseRecorder intercepta status code para detectar excesso de requisições
type responseRecorder struct {
http.ResponseWriter
status int
}
func (rr *responseRecorder) WriteHeader(code int) {
rr.status = code
rr.ResponseWriter.WriteHeader(code)
}