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 // receiverRateInterval is the time window for the webhook // receiver rate limit. The configured limit is expressed in // requests per minute. receiverRateInterval = 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) }) } } // ReceiverRateLimit returns middleware that rate-limits the // public webhook receiver endpoint per client IP per request // path (the path contains the entrypoint UUID, so each sender // is limited per entrypoint without affecting other senders or // other entrypoints). The limit is Config.ReceiverRateLimit // requests per minute. Requests over the limit receive a 429; // httprate adds the Retry-After header (RFC 6585). IP // extraction honours X-Forwarded-For, X-Real-IP, and // True-Client-IP headers for reverse-proxy setups. func (m *Middleware) ReceiverRateLimit() func(http.Handler) http.Handler { return httprate.Limit( m.params.Config.ReceiverRateLimit, receiverRateInterval, httprate.WithKeyFuncs( httprate.KeyByRealIP, httprate.KeyByEndpoint, ), httprate.WithLimitHandler(http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) { m.log.Warn( "webhook receiver rate limit exceeded", "path", r.URL.Path, ) http.Error( w, "Too many requests. "+ "Please slow down.", http.StatusTooManyRequests, ) }, )), ) }