feat: Add secure contact form with comprehensive security features

- Add contact form dialog with HTMX integration (hx-post)
- Implement browser-only access middleware (blocks curl/Postman/wget)
- Add rate limiting (5 requests/hour per IP) for contact endpoint
- Implement honeypot and timing-based bot detection
- Add input validation (email format, message length 10-5000 chars)
- Create contact button in desktop and mobile navigation (last position)

Security features:
- Browser-only middleware validates User-Agent, Referer/Origin, HX-Request headers
- Honeypot field returns fake success to fool bots while logging spam
- Timing validation rejects forms submitted < 2 seconds
- All security events logged for monitoring

Documentation:
- docs/SECURITY.md - Comprehensive security documentation
- docs/HACK-CHALLENGE.md - "Try to Hack Me!" challenge for security researchers
- docs/SECURITY-AUDIT-REPORT.md - Full security audit report
- docs/CONTACT-FORM-QUICKSTART.md - Integration guide

Form fields: email (required), name, company, subject, message (required)
This commit is contained in:
juanatsap
2025-11-30 14:31:58 +00:00
parent 19951b6f42
commit 58c1237326
15 changed files with 4929 additions and 66 deletions
+19
View File
@@ -11,6 +11,7 @@ type Config struct {
Server ServerConfig
Template TemplateConfig
Data DataConfig
Email EmailConfig
}
// ServerConfig contains server-specific settings
@@ -33,6 +34,16 @@ type DataConfig struct {
Dir string
}
// EmailConfig contains email/SMTP settings
type EmailConfig struct {
SMTPHost string
SMTPPort string
SMTPUser string
SMTPPassword string
FromEmail string
ContactEmail string
}
// Load creates a new Config with values from environment or defaults
func Load() *Config {
return &Config{
@@ -50,6 +61,14 @@ func Load() *Config {
Data: DataConfig{
Dir: getEnv("DATA_DIR", "data"),
},
Email: EmailConfig{
SMTPHost: getEnv("SMTP_HOST", "smtp.gmail.com"),
SMTPPort: getEnv("SMTP_PORT", "587"),
SMTPUser: getEnv("SMTP_USER", ""),
SMTPPassword: getEnv("SMTP_PASSWORD", ""),
FromEmail: getEnv("SMTP_FROM_EMAIL", ""),
ContactEmail: getEnv("CONTACT_EMAIL", "txeo.msx@gmail.com"),
},
}
}
+8 -1
View File
@@ -66,6 +66,12 @@ func (h *CVHandler) PlainText(w http.ResponseWriter, r *http.Request) {
return
}
// Check icons parameter (default: true)
showIcons := true
if r.URL.Query().Get("icons") == "false" {
showIcons = false
}
// Prepare template data using shared helper (loads CV data)
data, err := h.prepareTemplateData(langCode)
if err != nil {
@@ -74,8 +80,9 @@ func (h *CVHandler) PlainText(w http.ResponseWriter, r *http.Request) {
return
}
// Add base URL for footer
// Add base URL and icons setting
data["BaseURL"] = h.serverAddr
data["Icons"] = showIcons
// Load and parse the plain text template
tmplPath := filepath.Join("templates", "cv-text.txt")
+8 -4
View File
@@ -9,7 +9,7 @@ import (
)
// Setup configures all application routes and middleware
func Setup(cvHandler *handlers.CVHandler, healthHandler *handlers.HealthHandler, contactHandler *handlers.ContactHandler) http.Handler {
func Setup(cvHandler *handlers.CVHandler, healthHandler *handlers.HealthHandler) http.Handler {
mux := http.NewServeMux()
// Shortcut routes for default CV (year-aware) - MUST be before "/" route
@@ -28,10 +28,14 @@ func Setup(cvHandler *handlers.CVHandler, healthHandler *handlers.HealthHandler,
mux.HandleFunc("/toggle/icons", cvHandler.ToggleIcons)
mux.HandleFunc("/toggle/theme", cvHandler.ToggleTheme)
// Contact form endpoint (simple rate limiting)
// Contact form endpoint with full security chain:
// BrowserOnly → RateLimiter → Handler
// This blocks curl/Postman, enforces rate limits, then processes the request
contactRateLimiter := middleware.NewRateLimiter(5, 1*time.Hour)
protectedContactHandler := contactRateLimiter.Middleware(
http.HandlerFunc(contactHandler.Submit),
protectedContactHandler := middleware.BrowserOnly(
contactRateLimiter.Middleware(
http.HandlerFunc(cvHandler.HandleContact),
),
)
mux.Handle("/api/contact", protectedContactHandler)