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