Rate limit login keyed by username
This commit is contained in:
parent
a5fc6825b1
commit
108c6ec62f
3 changed files with 27 additions and 6 deletions
|
@ -3,11 +3,13 @@ package auth
|
|||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
_ "embed"
|
||||
|
||||
"dynatron.me/x/stillbox/pkg/config"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/httprate"
|
||||
"github.com/go-chi/jwtauth/v5"
|
||||
)
|
||||
|
||||
|
@ -30,6 +32,7 @@ type Authenticator interface {
|
|||
}
|
||||
|
||||
type Auth struct {
|
||||
rl *httprate.RateLimiter
|
||||
jwt *jwtauth.JWTAuth
|
||||
cfg config.Auth
|
||||
}
|
||||
|
@ -37,6 +40,7 @@ type Auth struct {
|
|||
// NewAuthenticator creates a new Authenticator with the provided config.
|
||||
func NewAuthenticator(cfg config.Auth) *Auth {
|
||||
a := &Auth{
|
||||
rl: httprate.NewRateLimiter(5, time.Minute),
|
||||
cfg: cfg,
|
||||
}
|
||||
a.initJWT()
|
||||
|
|
|
@ -225,6 +225,10 @@ func (a *Auth) routeAuth(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
if a.rl.RespondOnLimit(w, r, creds.Username) {
|
||||
return
|
||||
}
|
||||
|
||||
if creds.Username == "" || creds.Password == "" {
|
||||
http.Error(w, "blank credentials", http.StatusBadRequest)
|
||||
return
|
||||
|
|
|
@ -10,10 +10,7 @@ import (
|
|||
"dynatron.me/x/stillbox/client"
|
||||
"dynatron.me/x/stillbox/internal/version"
|
||||
"dynatron.me/x/stillbox/pkg/config"
|
||||
"dynatron.me/x/stillbox/pkg/database"
|
||||
"dynatron.me/x/stillbox/pkg/talkgroups/tgstore"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/go-chi/httprate"
|
||||
"github.com/go-chi/render"
|
||||
)
|
||||
|
@ -29,8 +26,7 @@ func (s *Server) setupRoutes() {
|
|||
}
|
||||
|
||||
r := s.r
|
||||
r.Use(middleware.WithValue(database.DBCtxKey, s.db))
|
||||
r.Use(middleware.WithValue(tgstore.StoreCtxKey, s.tgs))
|
||||
r.Use(s.WithCtxStores())
|
||||
|
||||
s.installPprof()
|
||||
|
||||
|
@ -47,10 +43,15 @@ func (s *Server) setupRoutes() {
|
|||
s.rateLimit(r)
|
||||
r.Use(render.SetContentType(render.ContentTypeJSON))
|
||||
// public routes
|
||||
s.auth.PublicRoutes(r)
|
||||
s.sources.PublicRoutes(r)
|
||||
})
|
||||
|
||||
r.Group(func(r chi.Router) {
|
||||
// auth routes get rate-limited heavily, but not using middleware
|
||||
r.Use(render.SetContentType(render.ContentTypeJSON))
|
||||
s.auth.PublicRoutes(r)
|
||||
})
|
||||
|
||||
r.Group(func(r chi.Router) {
|
||||
s.rateLimit(r)
|
||||
r.Use(s.auth.VerifyMiddleware())
|
||||
|
@ -61,11 +62,23 @@ func (s *Server) setupRoutes() {
|
|||
})
|
||||
}
|
||||
|
||||
// WithCtxStores is a middleware that installs all stores in the request context.
|
||||
func (s *Server) WithCtxStores() func(next http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
r = r.WithContext(s.addStoresTo(r.Context()))
|
||||
next.ServeHTTP(w, r)
|
||||
}
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) rateLimit(r chi.Router) {
|
||||
if s.conf.RateLimit.Verify() {
|
||||
r.Use(rateLimiter(&s.conf.RateLimit))
|
||||
}
|
||||
}
|
||||
|
||||
func rateLimiter(cfg *config.RateLimit) func(http.Handler) http.Handler {
|
||||
return httprate.LimitByRealIP(cfg.Requests, cfg.Over)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue