refactor: replace custom CSRF and rate-limiting with off-the-shelf libraries
All checks were successful
check / check (push) Successful in 4s
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:
@@ -39,6 +39,7 @@ type SessionParams struct {
|
||||
// Session manages encrypted session storage
|
||||
type Session struct {
|
||||
store *sessions.CookieStore
|
||||
key []byte // raw 32-byte auth key, also used for CSRF cookie signing
|
||||
log *slog.Logger
|
||||
config *config.Config
|
||||
}
|
||||
@@ -79,6 +80,7 @@ func New(lc fx.Lifecycle, params SessionParams) (*Session, error) {
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
}
|
||||
|
||||
s.key = keyBytes
|
||||
s.store = store
|
||||
s.log.Info("session manager initialized")
|
||||
return nil
|
||||
@@ -93,6 +95,12 @@ func (s *Session) Get(r *http.Request) (*sessions.Session, error) {
|
||||
return s.store.Get(r, SessionName)
|
||||
}
|
||||
|
||||
// GetKey returns the raw 32-byte authentication key used for session
|
||||
// encryption. This key is also suitable for CSRF cookie signing.
|
||||
func (s *Session) GetKey() []byte {
|
||||
return s.key
|
||||
}
|
||||
|
||||
// Save saves the session
|
||||
func (s *Session) Save(r *http.Request, w http.ResponseWriter, sess *sessions.Session) error {
|
||||
return sess.Save(r, w)
|
||||
|
||||
@@ -34,7 +34,7 @@ func testSession(t *testing.T) *Session {
|
||||
}
|
||||
log := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelDebug}))
|
||||
|
||||
return NewForTest(store, cfg, log)
|
||||
return NewForTest(store, cfg, log, key)
|
||||
}
|
||||
|
||||
// --- Get and Save Tests ---
|
||||
|
||||
@@ -9,10 +9,13 @@ import (
|
||||
|
||||
// NewForTest creates a Session with a pre-configured cookie store for use
|
||||
// in tests. This bypasses the fx lifecycle and database dependency, allowing
|
||||
// middleware and handler tests to use real session functionality.
|
||||
func NewForTest(store *sessions.CookieStore, cfg *config.Config, log *slog.Logger) *Session {
|
||||
// middleware and handler tests to use real session functionality. The key
|
||||
// parameter is the raw 32-byte authentication key used for session encryption
|
||||
// and CSRF cookie signing.
|
||||
func NewForTest(store *sessions.CookieStore, cfg *config.Config, log *slog.Logger, key []byte) *Session {
|
||||
return &Session{
|
||||
store: store,
|
||||
key: key,
|
||||
config: cfg,
|
||||
log: log,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user