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:
@@ -2,6 +2,7 @@ package server
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
sentryhttp "github.com/getsentry/sentry-go/http"
|
||||
"github.com/go-chi/chi/v5"
|
||||
@@ -12,6 +13,10 @@ import (
|
||||
"sneak.berlin/go/pixa/internal/static"
|
||||
)
|
||||
|
||||
// LoginAttemptsPerMinute is how many login attempts (POST /) one client may
|
||||
// make per minute; the next is refused with 429 Too Many Requests.
|
||||
const LoginAttemptsPerMinute = 5
|
||||
|
||||
// SetupRoutes configures all HTTP routes.
|
||||
func (s *Server) SetupRoutes() {
|
||||
s.router = chi.NewRouter()
|
||||
@@ -50,11 +55,14 @@ func (s *Server) SetupRoutes() {
|
||||
// token cookie is independent of the session cookie, so it also
|
||||
// covers the login POST, where no session exists yet. LimitBody caps
|
||||
// the POST body ahead of CSRF, which reads its token from that body.
|
||||
// The login POST is rate limited per client after both, so every
|
||||
// attempt that reaches the signing key comparison is counted.
|
||||
s.router.Group(func(r chi.Router) {
|
||||
r.Use(s.h.LimitBody(handlers.MaxFormBytes))
|
||||
r.Use(s.h.CSRF())
|
||||
r.Get("/", s.h.HandleRoot())
|
||||
r.Post("/", s.h.HandleRoot())
|
||||
r.With(s.mw.RateLimit(LoginAttemptsPerMinute, time.Minute)).
|
||||
Post("/", s.h.HandleRoot())
|
||||
r.Post("/generate", s.h.HandleGenerateURL())
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user