All checks were successful
check / check (push) Successful in 4s
Replace custom CSRF middleware with gorilla/csrf and custom rate-limiting middleware with go-chi/httprate, as requested in code review. CSRF changes: - Replace session-based CSRF tokens with gorilla/csrf cookie-based double-submit pattern (HMAC-authenticated cookies) - Keep same form field name (csrf_token) for template compatibility - Keep same route exclusions (webhook/API routes) - In dev mode, mark requests as plaintext HTTP to skip Referer check Rate limiting changes: - Replace custom token-bucket rate limiter with httprate sliding-window counter (per-IP, 5 POST requests/min on login endpoint) - Remove custom IP extraction (httprate.KeyByRealIP handles X-Forwarded-For, X-Real-IP, True-Client-IP) - Remove custom cleanup goroutine (httprate manages its own state) Kept as-is: - SSRF prevention code (internal/delivery/ssrf.go) — application-specific - CSRFToken() wrapper function — handlers unchanged Updated README security section and architecture overview to reflect library choices.
49 lines
1.4 KiB
Go
49 lines
1.4 KiB
Go
package middleware
|
|
|
|
import (
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/go-chi/httprate"
|
|
)
|
|
|
|
const (
|
|
// loginRateLimit is the maximum number of login attempts per interval.
|
|
loginRateLimit = 5
|
|
|
|
// loginRateInterval is the time window for the rate limit.
|
|
loginRateInterval = 1 * time.Minute
|
|
)
|
|
|
|
// LoginRateLimit returns middleware that enforces per-IP rate limiting
|
|
// on login attempts using go-chi/httprate. Only POST requests are
|
|
// rate-limited; GET requests (rendering the login form) pass through
|
|
// unaffected. When the rate limit is exceeded, a 429 Too Many Requests
|
|
// response is returned. IP extraction honours X-Forwarded-For,
|
|
// X-Real-IP, and True-Client-IP headers for reverse-proxy setups.
|
|
func (m *Middleware) LoginRateLimit() func(http.Handler) http.Handler {
|
|
limiter := httprate.Limit(
|
|
loginRateLimit,
|
|
loginRateInterval,
|
|
httprate.WithKeyFuncs(httprate.KeyByRealIP),
|
|
httprate.WithLimitHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
m.log.Warn("login rate limit exceeded",
|
|
"path", r.URL.Path,
|
|
)
|
|
http.Error(w, "Too many login attempts. Please try again later.", http.StatusTooManyRequests)
|
|
})),
|
|
)
|
|
|
|
return func(next http.Handler) http.Handler {
|
|
limited := limiter(next)
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
// Only rate-limit POST requests (actual login attempts)
|
|
if r.Method != http.MethodPost {
|
|
next.ServeHTTP(w, r)
|
|
return
|
|
}
|
|
limited.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
}
|