refactor: replace custom CSRF and rate-limiting with off-the-shelf libraries
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.
This commit is contained in:
clawbot
2026-03-10 10:05:38 -07:00
parent 5c69efb5bc
commit 0829f9a75d
11 changed files with 126 additions and 317 deletions

View File

@@ -40,7 +40,7 @@ func testMiddleware(t *testing.T, env string) (*Middleware, *session.Session) {
SameSite: http.SameSiteLaxMode,
}
sessManager := newTestSession(t, store, cfg, log)
sessManager := newTestSession(t, store, cfg, log, key)
m := &Middleware{
log: log,
@@ -55,9 +55,9 @@ func testMiddleware(t *testing.T, env string) (*Middleware, *session.Session) {
// newTestSession creates a session.Session with a pre-configured cookie store
// for testing. This avoids needing the fx lifecycle and database.
func newTestSession(t *testing.T, store *sessions.CookieStore, cfg *config.Config, log *slog.Logger) *session.Session {
func newTestSession(t *testing.T, store *sessions.CookieStore, cfg *config.Config, log *slog.Logger, key []byte) *session.Session {
t.Helper()
return session.NewForTest(store, cfg, log)
return session.NewForTest(store, cfg, log, key)
}
// --- Logging Middleware Tests ---
@@ -326,7 +326,7 @@ func TestMetricsAuth_ValidCredentials(t *testing.T) {
store := sessions.NewCookieStore(key)
store.Options = &sessions.Options{Path: "/", MaxAge: 86400}
sessManager := session.NewForTest(store, cfg, log)
sessManager := session.NewForTest(store, cfg, log, key)
m := &Middleware{
log: log,
@@ -366,7 +366,7 @@ func TestMetricsAuth_InvalidCredentials(t *testing.T) {
store := sessions.NewCookieStore(key)
store.Options = &sessions.Options{Path: "/", MaxAge: 86400}
sessManager := session.NewForTest(store, cfg, log)
sessManager := session.NewForTest(store, cfg, log, key)
m := &Middleware{
log: log,
@@ -406,7 +406,7 @@ func TestMetricsAuth_NoCredentials(t *testing.T) {
store := sessions.NewCookieStore(key)
store.Options = &sessions.Options{Path: "/", MaxAge: 86400}
sessManager := session.NewForTest(store, cfg, log)
sessManager := session.NewForTest(store, cfg, log, key)
m := &Middleware{
log: log,