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 } 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 } 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 != "" { if result, err := bans.RecordAbuse(ip, services.AbuseKindMaliciousPath, r.URL.Path, settings.MaliciousPathThreshold, now); err != nil { 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) }) } }