Files
tamakyi e3e59e7c69 chore: baseline snapshot before moderation-strictness feature
Repo .git was damaged (refs/ missing, index truncated, object store near-empty).
Repaired via git init (rebuilt refs), stale index preserved as .git/index.corrupt-20260910.bak,
then full worktree committed as a rollback baseline.
2026-09-10 10:56:27 +08:00

88 lines
2.4 KiB
Go

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
}
}
}
}
}