Count an IPv4-mapped login client by its IPv4 address (closes #66)
check / check (push) Successful in 2m33s
check / check (push) Successful in 2m33s
A proxy on a dual-stack listener forwards an IPv4 client as ::ffff:a.b.c.d, whose /64 is the same for every IPv4 client, so one client's failed logins refused everyone's. The rate limit key now unmaps the address first. README.md now says that with the default trusted_proxies a client with a private address can choose its counted address through X-Forwarded-For, and that setting trusted_proxies to the proxy's own address closes this. Model: opus-5-5
This commit is contained in:
@@ -4,6 +4,7 @@ package middleware
|
||||
import (
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
basicauth "github.com/99designs/basicauth-go"
|
||||
@@ -94,14 +95,23 @@ func (s *Middleware) ClientIP() func(http.Handler) http.Handler {
|
||||
// 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.
|
||||
// holds whole; an IPv4-mapped address (::ffff:a.b.c.d) is counted as the
|
||||
// IPv4 address it carries, since every such address falls in the same /64.
|
||||
// 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
|
||||
ip := clientip.FromContext(r.Context())
|
||||
|
||||
addr, err := netip.ParseAddr(ip)
|
||||
if err == nil {
|
||||
ip = addr.Unmap().String()
|
||||
}
|
||||
|
||||
return httprate.CanonicalizeIP(ip), nil
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user