Rate limit login attempts per client address (closes #66)
check / check (push) Successful in 3m3s
check / check (push) Successful in 3m3s
POST / had no limit, so the signing key could be guessed at no cost. It is now limited to LoginAttemptsPerMinute (5) attempts per minute per client by a new RateLimit middleware on github.com/go-chi/httprate. It counts by the address the ClientIP middleware resolved through trusted_proxies, an IPv6 client by its /64, and answers an attempt over the limit with 429 and Retry-After. It runs after the body-size and CSRF checks, so every attempt that reaches the key comparison is counted. The image routes can reuse it. README states the limit; TODO narrows the per-IP item to the image routes. Model: opus-5-5
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
||||
basicauth "github.com/99designs/basicauth-go"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/go-chi/cors"
|
||||
"github.com/go-chi/httprate"
|
||||
metrics "github.com/slok/go-http-metrics/metrics/prometheus"
|
||||
ghmm "github.com/slok/go-http-metrics/middleware"
|
||||
"github.com/slok/go-http-metrics/middleware/std"
|
||||
@@ -88,6 +89,22 @@ func (s *Middleware) ClientIP() func(http.Handler) http.Handler {
|
||||
}
|
||||
}
|
||||
|
||||
// RateLimit returns a middleware that limits each client to requestLimit
|
||||
// requests per window and refuses a request over the limit with 429 Too Many
|
||||
// Requests and a Retry-After header. Clients are told apart by the address
|
||||
// the ClientIP middleware stored in the request context, so ClientIP must
|
||||
// run first. An IPv6 client is counted by its /64, which one client usually
|
||||
// holds whole. Counts are kept only for the current and the previous
|
||||
// window, so memory stays bounded.
|
||||
func (s *Middleware) RateLimit(
|
||||
requestLimit int, window time.Duration,
|
||||
) func(http.Handler) http.Handler {
|
||||
return httprate.LimitBy(requestLimit, window,
|
||||
func(r *http.Request) (string, error) {
|
||||
return httprate.CanonicalizeIP(clientip.FromContext(r.Context())), nil
|
||||
})
|
||||
}
|
||||
|
||||
type loggingResponseWriter struct {
|
||||
http.ResponseWriter
|
||||
|
||||
|
||||
Reference in New Issue
Block a user