package middleware import ( "net/http" "strings" "sync" "time" ) // contactRateLimitEntry tracks rate limiting for contact form per IP type contactRateLimitEntry struct { count int resetTime time.Time } // ContactRateLimiter provides rate limiting specifically for contact form // Allows 5 submissions per hour per IP address type ContactRateLimiter struct { mu sync.RWMutex clients map[string]*contactRateLimitEntry } // NewContactRateLimiter creates a new contact form rate limiter // Default: 5 requests per hour per IP func NewContactRateLimiter() *ContactRateLimiter { rl := &ContactRateLimiter{ clients: make(map[string]*contactRateLimitEntry), } // Cleanup expired entries every 10 minutes go rl.cleanup() return rl } // Middleware returns rate limiting middleware for contact form func (rl *ContactRateLimiter) Middleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Get client IP (handle X-Forwarded-For for proxies) ip := r.Header.Get("X-Forwarded-For") if ip == "" { ip = r.Header.Get("X-Real-IP") } if ip == "" { ip = strings.Split(r.RemoteAddr, ":")[0] } // Extract first IP if multiple IPs in X-Forwarded-For if strings.Contains(ip, ",") { ip = strings.TrimSpace(strings.Split(ip, ",")[0]) } if !rl.allow(ip) { // Check if HTMX request isHTMX := r.Header.Get("HX-Request") != "" if isHTMX { // Return HTMX-friendly error w.Header().Set("Content-Type", "text/html; charset=utf-8") w.WriteHeader(http.StatusTooManyRequests) _, _ = w.Write([]byte(`
You've submitted too many contact forms. Please wait an hour before trying again.