package route import ( "net" "net/http" "strings" "github.com/flamego/flamego" "github.com/sirupsen/logrus" "github.com/tamakyi/TamaBox/internal/conf" "github.com/tamakyi/TamaBox/internal/shared" ) // ipBlacklistMiddleware returns a Flamego handler that checks the client's IP // against a configurable blacklist. When a blacklisted IP is detected, the // request is redirected (HTTP 302) to the configured redirect URL. // // Supports exact IP matching and CIDR notation (e.g. "192.168.1.0/24"). // The client IP is resolved by checking X-Real-IP, X-Forwarded-For, then // falling back to the raw RemoteAddr. func ipBlacklistMiddleware() flamego.Handler { raw := strings.TrimSpace(conf.Security.IPBlacklist) if raw == "" { return func(ctx flamego.Context) {} } // Pre-parse all entries into exact IPs and CIDR networks. type cidrEntry struct{ ipNet *net.IPNet } exact := make(map[string]bool) cidrs := make([]cidrEntry, 0) for _, entry := range strings.Split(raw, ",") { entry = strings.TrimSpace(entry) if entry == "" { continue } // Try CIDR first. if _, ipNet, err := net.ParseCIDR(entry); err == nil { cidrs = append(cidrs, cidrEntry{ipNet: ipNet}) logrus.Infof("IP blacklist: CIDR %s added", entry) continue } // Fall back to exact IP. if ip := net.ParseIP(entry); ip != nil { exact[ip.String()] = true logrus.Infof("IP blacklist: exact IP %s added", ip.String()) continue } logrus.Warnf("IP blacklist: skipping invalid entry %q", entry) } if len(exact) == 0 && len(cidrs) == 0 { return func(ctx flamego.Context) {} } redirectURL := strings.TrimSpace(conf.Security.IPBlacklistRedirectURL) if redirectURL == "" { logrus.Warn("IP blacklist is configured but ip_blacklist_redirect_url is empty") } return func(ctx flamego.Context) { clientIP := shared.ResolveClientIP(ctx.Request().Request) if clientIP == "" { return } // Exact match first (O(1)). if exact[clientIP] { logrus.WithField("ip", clientIP).Info("IP blacklist: exact match, redirecting") ctx.Redirect(redirectURL, http.StatusFound) return } // CIDR match (O(n), n is typically small). parsed := net.ParseIP(clientIP) if parsed != nil { for _, e := range cidrs { if e.ipNet.Contains(parsed) { logrus.WithField("ip", clientIP).Infof("IP blacklist: CIDR %s match, redirecting", e.ipNet) ctx.Redirect(redirectURL, http.StatusFound) return } } } } }