package middleware import ( "net/http" "time" "github.com/go-chi/httprate" ) const ( // loginRateLimit is the maximum number of login attempts // per interval. loginRateLimit = 5 // loginRateInterval is the time window for the rate limit. loginRateInterval = 1 * time.Minute ) // LoginRateLimit returns middleware that enforces per-IP rate // limiting on login attempts using go-chi/httprate. Only POST // requests are rate-limited; GET requests (rendering the login // form) pass through unaffected. When the rate limit is exceeded, // a 429 Too Many Requests response is returned. IP extraction // honours X-Forwarded-For, X-Real-IP, and True-Client-IP headers // for reverse-proxy setups. func (m *Middleware) LoginRateLimit() func(http.Handler) http.Handler { limiter := httprate.Limit( loginRateLimit, loginRateInterval, httprate.WithKeyFuncs(httprate.KeyByRealIP), httprate.WithLimitHandler(http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) { m.log.Warn("login rate limit exceeded", "path", r.URL.Path, ) http.Error( w, "Too many login attempts. "+ "Please try again later.", http.StatusTooManyRequests, ) }, )), ) return func(next http.Handler) http.Handler { limited := limiter(next) return http.HandlerFunc(func( w http.ResponseWriter, r *http.Request, ) { // Only rate-limit POST requests (actual login // attempts) if r.Method != http.MethodPost { next.ServeHTTP(w, r) return } limited.ServeHTTP(w, r) }) } }