feat: add per-IP rate limiting to login endpoint
All checks were successful
check / check (push) Successful in 1m15s
All checks were successful
check / check (push) Successful in 1m15s
Add a token-bucket rate limiter (golang.org/x/time/rate) that limits login attempts per client IP on POST /api/v1/login. Returns 429 Too Many Requests with a Retry-After header when the limit is exceeded. Configurable via LOGIN_RATE_LIMIT (requests/sec, default 1) and LOGIN_RATE_BURST (burst size, default 5). Stale per-IP entries are automatically cleaned up every 10 minutes. Only the login endpoint is rate-limited per sneak's instruction — session creation and registration use hashcash proof-of-work instead.
This commit is contained in:
@@ -16,6 +16,7 @@ import (
|
||||
"git.eeqj.de/sneak/neoirc/internal/hashcash"
|
||||
"git.eeqj.de/sneak/neoirc/internal/healthcheck"
|
||||
"git.eeqj.de/sneak/neoirc/internal/logger"
|
||||
"git.eeqj.de/sneak/neoirc/internal/ratelimit"
|
||||
"go.uber.org/fx"
|
||||
)
|
||||
|
||||
@@ -41,6 +42,7 @@ type Handlers struct {
|
||||
hc *healthcheck.Healthcheck
|
||||
broker *broker.Broker
|
||||
hashcashVal *hashcash.Validator
|
||||
loginLimiter *ratelimit.Limiter
|
||||
cancelCleanup context.CancelFunc
|
||||
}
|
||||
|
||||
@@ -54,12 +56,23 @@ func New(
|
||||
resource = "neoirc"
|
||||
}
|
||||
|
||||
loginRate := params.Config.LoginRateLimit
|
||||
if loginRate <= 0 {
|
||||
loginRate = ratelimit.DefaultRate
|
||||
}
|
||||
|
||||
loginBurst := params.Config.LoginRateBurst
|
||||
if loginBurst <= 0 {
|
||||
loginBurst = ratelimit.DefaultBurst
|
||||
}
|
||||
|
||||
hdlr := &Handlers{ //nolint:exhaustruct // cancelCleanup set in startCleanup
|
||||
params: ¶ms,
|
||||
log: params.Logger.Get(),
|
||||
hc: params.Healthcheck,
|
||||
broker: broker.New(),
|
||||
hashcashVal: hashcash.NewValidator(resource),
|
||||
params: ¶ms,
|
||||
log: params.Logger.Get(),
|
||||
hc: params.Healthcheck,
|
||||
broker: broker.New(),
|
||||
hashcashVal: hashcash.NewValidator(resource),
|
||||
loginLimiter: ratelimit.New(loginRate, loginBurst),
|
||||
}
|
||||
|
||||
lifecycle.Append(fx.Hook{
|
||||
@@ -151,6 +164,10 @@ func (hdlr *Handlers) stopCleanup() {
|
||||
if hdlr.cancelCleanup != nil {
|
||||
hdlr.cancelCleanup()
|
||||
}
|
||||
|
||||
if hdlr.loginLimiter != nil {
|
||||
hdlr.loginLimiter.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
func (hdlr *Handlers) cleanupLoop(ctx context.Context) {
|
||||
|
||||
Reference in New Issue
Block a user