91 lines
2.3 KiB
Go
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)
|
|
} |