2026-05-31 21:52:56 +03:00
|
|
|
package middleware
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"log/slog"
|
|
|
|
|
"net/http"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"warpbox.dev/backend/libs/services"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func Bans(logger *slog.Logger, bans *services.BanService, trustedProxies []string) Middleware {
|
|
|
|
|
return func(next http.Handler) http.Handler {
|
|
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
ip := services.ClientIP(r.RemoteAddr, r.Header.Get("X-Forwarded-For"), r.Header.Get("X-Real-IP"), trustedProxies)
|
|
|
|
|
r = services.WithClientIP(r, ip)
|
|
|
|
|
now := time.Now().UTC()
|
|
|
|
|
|
|
|
|
|
if bans != nil {
|
|
|
|
|
if matched, ok, err := bans.Match(ip, now); err != nil {
|
|
|
|
|
logger.Error("ban match failed", "source", "ban", "severity", "error", "code", 5001, "ip", ip, "error", err.Error())
|
|
|
|
|
} else if ok {
|
|
|
|
|
logger.Warn("banned request blocked", "source", "ban", "severity", "warn", "code", 4030, "ip", ip, "ban_id", matched.Ban.ID, "target", matched.Ban.Normalized, "path", r.URL.Path)
|
|
|
|
|
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
|
|
|
|
w.WriteHeader(http.StatusForbidden)
|
|
|
|
|
_, _ = w.Write([]byte("forbidden\n"))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-01 00:20:18 +03:00
|
|
|
settings, err := bans.Settings()
|
|
|
|
|
if err != nil {
|
|
|
|
|
logger.Error("ban settings load failed", "source", "ban", "severity", "error", "code", 5004, "ip", ip, "error", err.Error())
|
|
|
|
|
next.ServeHTTP(w, r)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if !settings.AutoBanEnabled {
|
|
|
|
|
next.ServeHTTP(w, r)
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-05-31 21:52:56 +03:00
|
|
|
if pattern, err := bans.MaliciousPattern(r.URL.Path); err != nil {
|
|
|
|
|
logger.Error("malicious path check failed", "source", "ban", "severity", "error", "code", 5002, "ip", ip, "error", err.Error())
|
|
|
|
|
} else if pattern != "" {
|
2026-06-01 00:20:18 +03:00
|
|
|
if result, err := bans.RecordAbuse(ip, services.AbuseKindMaliciousPath, r.URL.Path, settings.MaliciousPathThreshold, now); err != nil {
|
2026-05-31 21:52:56 +03:00
|
|
|
logger.Error("malicious path event failed", "source", "ban", "severity", "error", "code", 5003, "ip", ip, "path", r.URL.Path, "error", err.Error())
|
|
|
|
|
} else if result.Enabled {
|
|
|
|
|
logger.Warn("malicious path requested", "source", "ban", "severity", "warn", "code", 4302, "ip", ip, "path", r.URL.Path, "pattern", pattern, "count", result.Event.Count)
|
|
|
|
|
if result.Triggered {
|
|
|
|
|
logger.Warn("ip auto-banned for malicious path", "source", "ban", "severity", "warn", "code", 4303, "ip", ip, "ban_id", result.Ban.ID, "path", r.URL.Path)
|
|
|
|
|
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
|
|
|
|
w.WriteHeader(http.StatusForbidden)
|
|
|
|
|
_, _ = w.Write([]byte("forbidden\n"))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
next.ServeHTTP(w, r)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|