commit inicial do projeto
This commit is contained in:
91
internal/api/middleware.go
Normal file
91
internal/api/middleware.go
Normal file
@ -0,0 +1,91 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user