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:
@@ -0,0 +1,472 @@
|
||||
# Contact Form Email Backend - Implementation Guide
|
||||
|
||||
## Overview
|
||||
|
||||
Complete backend implementation for a contact form with email delivery using SMTP (Gmail), featuring comprehensive security measures including CSRF protection, rate limiting, bot protection, and browser-only access.
|
||||
|
||||
## Features Implemented
|
||||
|
||||
### 1. Email Service (`internal/services/email.go`)
|
||||
- ✅ SMTP-based email sending with TLS support
|
||||
- ✅ Gmail App Password authentication
|
||||
- ✅ Email validation and sanitization
|
||||
- ✅ Header injection prevention
|
||||
- ✅ Configurable via environment variables
|
||||
- ✅ Comprehensive error handling and logging
|
||||
- ✅ Template-based email formatting
|
||||
|
||||
### 2. Contact Handler (`internal/handlers/contact.go`)
|
||||
- ✅ POST endpoint: `/api/contact`
|
||||
- ✅ Form field validation (email, name, company, subject, message)
|
||||
- ✅ Bot protection with honeypot field
|
||||
- ✅ Timing check (rejects forms submitted < 2 seconds)
|
||||
- ✅ HTMX-friendly responses
|
||||
- ✅ Detailed logging (without sensitive data)
|
||||
|
||||
### 3. Security Middleware
|
||||
|
||||
#### Contact Rate Limiting (`internal/middleware/contact_rate_limit.go`)
|
||||
- ✅ 5 requests per hour per IP address
|
||||
- ✅ Automatic cleanup of expired entries
|
||||
- ✅ HTMX-friendly error responses
|
||||
- ✅ Configurable limits and windows
|
||||
|
||||
#### CSRF Protection (`internal/middleware/csrf.go`)
|
||||
- ✅ Token generation and validation
|
||||
- ✅ 24-hour token TTL
|
||||
- ✅ Cookie-based token storage
|
||||
- ✅ Automatic token cleanup
|
||||
- ✅ Support for forms and AJAX requests
|
||||
|
||||
#### Browser-Only Access (`internal/middleware/browser_only.go`)
|
||||
- ✅ Blocks curl, Postman, wget, and other HTTP clients
|
||||
- ✅ User-Agent validation
|
||||
- ✅ Referer/Origin header validation
|
||||
- ✅ Custom header requirement (HTMX or X-Browser-Request)
|
||||
- ✅ Comprehensive bot detection
|
||||
|
||||
### 4. Configuration (`internal/config/config.go`)
|
||||
- ✅ Email settings added to config struct
|
||||
- ✅ Environment variable support
|
||||
- ✅ Sensible defaults for development
|
||||
|
||||
### 5. HTMX Response Templates
|
||||
- ✅ `templates/partials/contact_success.html` - Success message with animation
|
||||
- ✅ `templates/partials/contact_error.html` - Error message with shake animation
|
||||
|
||||
### 6. Route Registration (`internal/routes/routes.go`)
|
||||
- ✅ Endpoint registered with full middleware chain
|
||||
- ✅ Proper middleware ordering
|
||||
|
||||
## Security Features
|
||||
|
||||
### Multi-Layer Protection
|
||||
|
||||
```
|
||||
Request → Browser-Only → Contact Rate Limit → CSRF → Handler
|
||||
```
|
||||
|
||||
1. **Browser-Only Middleware**
|
||||
- Blocks non-browser clients (curl, Postman, etc.)
|
||||
- Validates User-Agent, Referer, and custom headers
|
||||
|
||||
2. **Contact Rate Limiting**
|
||||
- 5 submissions per hour per IP
|
||||
- Prevents spam and abuse
|
||||
|
||||
3. **CSRF Protection**
|
||||
- Validates security tokens
|
||||
- Prevents cross-site request forgery
|
||||
|
||||
4. **Bot Protection in Handler**
|
||||
- Honeypot field detection
|
||||
- Timing validation (min 2 seconds)
|
||||
|
||||
5. **Input Validation**
|
||||
- Email format validation
|
||||
- Length restrictions
|
||||
- Header injection prevention
|
||||
- XSS protection via sanitization
|
||||
|
||||
## Environment Configuration
|
||||
|
||||
### Required Variables
|
||||
|
||||
```bash
|
||||
# SMTP Configuration (Gmail)
|
||||
SMTP_HOST=smtp.gmail.com
|
||||
SMTP_PORT=587
|
||||
SMTP_USER=your-email@gmail.com
|
||||
SMTP_PASSWORD=your-app-password
|
||||
SMTP_FROM_EMAIL=your-email@gmail.com
|
||||
CONTACT_EMAIL=txeo.msx@gmail.com
|
||||
```
|
||||
|
||||
### Gmail App Password Setup
|
||||
|
||||
1. Enable 2FA in your Google account
|
||||
2. Visit: https://myaccount.google.com/apppasswords
|
||||
3. Generate an App Password for "Mail"
|
||||
4. Use the generated password in `SMTP_PASSWORD`
|
||||
|
||||
**Important**: Never use your regular Gmail password - always use an App Password.
|
||||
|
||||
## API Endpoint
|
||||
|
||||
### POST /api/contact
|
||||
|
||||
**Request Format:**
|
||||
```http
|
||||
POST /api/contact
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
HX-Request: true
|
||||
Referer: http://yourdomain.com/
|
||||
|
||||
email=user@example.com
|
||||
&name=John Doe
|
||||
&company=Acme Inc
|
||||
&subject=Partnership Inquiry
|
||||
&message=Hello, I would like to discuss...
|
||||
&website=
|
||||
&submit_time=1701360000000
|
||||
&csrf_token=abc123...
|
||||
```
|
||||
|
||||
**Required Fields:**
|
||||
- `email` - Valid email address (max 254 chars)
|
||||
- `message` - Message text (10-5000 chars)
|
||||
|
||||
**Optional Fields:**
|
||||
- `name` - Sender name (max 100 chars)
|
||||
- `company` - Company name (max 100 chars)
|
||||
- `subject` - Email subject (max 200 chars)
|
||||
|
||||
**Special Fields:**
|
||||
- `website` - Honeypot (must be empty)
|
||||
- `submit_time` - Unix timestamp in milliseconds
|
||||
- `csrf_token` - CSRF token from cookie
|
||||
|
||||
**Success Response (200):**
|
||||
```html
|
||||
<div class="alert alert-success">
|
||||
<h3>Message Sent Successfully!</h3>
|
||||
<p>Thank you for reaching out...</p>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Error Responses:**
|
||||
|
||||
- **403 Forbidden** - Non-browser client, CSRF failure, or rate limit
|
||||
- **400 Bad Request** - Validation error
|
||||
- **429 Too Many Requests** - Rate limit exceeded
|
||||
|
||||
## Testing
|
||||
|
||||
### Test 1: Curl Request (Should Fail - 403)
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:1999/api/contact \
|
||||
-d "email=test@example.com&message=Test" \
|
||||
-w "\nStatus: %{http_code}\n"
|
||||
```
|
||||
|
||||
**Expected:** `Forbidden: Browser access only` (403)
|
||||
|
||||
### Test 2: Postman Request (Should Fail - 403)
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:1999/api/contact \
|
||||
-H "User-Agent: PostmanRuntime/7.32.0" \
|
||||
-H "Referer: http://localhost:1999/" \
|
||||
-d "email=test@example.com&message=Test" \
|
||||
-w "\nStatus: %{http_code}\n"
|
||||
```
|
||||
|
||||
**Expected:** `Forbidden: Browser access only` (403)
|
||||
|
||||
### Test 3: Browser-like Request (Should Fail - CSRF)
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:1999/api/contact \
|
||||
-H "User-Agent: Mozilla/5.0 (Macintosh)" \
|
||||
-H "Referer: http://localhost:1999/" \
|
||||
-H "HX-Request: true" \
|
||||
-d "email=test@example.com&message=Test" \
|
||||
-w "\nStatus: %{http_code}\n"
|
||||
```
|
||||
|
||||
**Expected:** CSRF validation error (403)
|
||||
|
||||
### Test 4: Complete Browser Request
|
||||
|
||||
Use the test HTML file: `test_contact_form.html`
|
||||
|
||||
```bash
|
||||
# Start server
|
||||
go run main.go
|
||||
|
||||
# Open in browser
|
||||
open http://localhost:1999/test_contact_form.html
|
||||
|
||||
# Fill and submit form
|
||||
# Should succeed if SMTP credentials are configured
|
||||
```
|
||||
|
||||
## Integration Example
|
||||
|
||||
### HTML Contact Form
|
||||
|
||||
```html
|
||||
<form
|
||||
hx-post="/api/contact"
|
||||
hx-target="#response"
|
||||
hx-headers='{"X-Browser-Request": "true"}'
|
||||
hx-on::before-request="this.submitTime.value = Date.now()"
|
||||
>
|
||||
<input type="hidden" name="submit_time" class="submitTime">
|
||||
<input type="hidden" name="csrf_token" value="{{ .CSRFToken }}">
|
||||
|
||||
<!-- Honeypot -->
|
||||
<div style="position: absolute; left: -9999px;">
|
||||
<input type="text" name="website" tabindex="-1">
|
||||
</div>
|
||||
|
||||
<input type="email" name="email" required>
|
||||
<input type="text" name="name">
|
||||
<input type="text" name="company">
|
||||
<input type="text" name="subject">
|
||||
<textarea name="message" required></textarea>
|
||||
|
||||
<button type="submit">Send</button>
|
||||
</form>
|
||||
|
||||
<div id="response"></div>
|
||||
|
||||
<script>
|
||||
// Initialize submit time
|
||||
document.querySelector('.submitTime').value = Date.now();
|
||||
</script>
|
||||
```
|
||||
|
||||
### JavaScript (Fetch API)
|
||||
|
||||
```javascript
|
||||
// Get CSRF token from cookie
|
||||
const csrfToken = document.cookie
|
||||
.split('; ')
|
||||
.find(row => row.startsWith('csrf_token='))
|
||||
?.split('=')[1];
|
||||
|
||||
const submitTime = Date.now();
|
||||
|
||||
// Wait at least 2 seconds before allowing submit
|
||||
setTimeout(() => {
|
||||
fetch('/api/contact', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'X-Browser-Request': 'true'
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
email: 'user@example.com',
|
||||
name: 'John Doe',
|
||||
message: 'Hello!',
|
||||
website: '', // Honeypot
|
||||
submit_time: submitTime,
|
||||
csrf_token: csrfToken
|
||||
})
|
||||
})
|
||||
.then(response => response.text())
|
||||
.then(html => {
|
||||
document.getElementById('response').innerHTML = html;
|
||||
});
|
||||
}, 2000);
|
||||
```
|
||||
|
||||
## Email Template
|
||||
|
||||
The email sent to `CONTACT_EMAIL` follows this format:
|
||||
|
||||
```
|
||||
Subject: [CV Contact] {subject or "New Message"}
|
||||
|
||||
New contact form submission:
|
||||
|
||||
From: user@example.com
|
||||
Name: John Doe
|
||||
Company: Acme Inc
|
||||
Subject: Partnership Inquiry
|
||||
|
||||
Message:
|
||||
Hello, I would like to discuss a potential partnership...
|
||||
|
||||
---
|
||||
IP: 192.168.1.1
|
||||
Time: 2025-11-30 13:45:22 UTC
|
||||
```
|
||||
|
||||
## Monitoring & Logging
|
||||
|
||||
### Security Events Logged
|
||||
|
||||
1. **Blocked Requests**
|
||||
- Non-browser User-Agents
|
||||
- Missing Referer/Origin
|
||||
- Missing browser headers
|
||||
- CSRF validation failures
|
||||
- Rate limit exceeded
|
||||
- Honeypot triggered
|
||||
- Form submitted too fast
|
||||
|
||||
2. **Successful Submissions**
|
||||
- Email sent successfully (logs email address and IP)
|
||||
|
||||
3. **Errors**
|
||||
- SMTP connection failures
|
||||
- Email sending errors
|
||||
- Template rendering errors
|
||||
|
||||
### Log Format
|
||||
|
||||
```
|
||||
2025/11/30 13:45:22 SECURITY: Blocked non-browser User-Agent from IP 192.168.1.1: curl/7.88.1
|
||||
2025/11/30 13:45:23 SECURITY: CSRF validation failed from IP 192.168.1.2
|
||||
2025/11/30 13:45:24 Contact form submitted successfully from user@example.com (192.168.1.3)
|
||||
```
|
||||
|
||||
## Production Deployment
|
||||
|
||||
### Checklist
|
||||
|
||||
- [ ] Configure SMTP credentials in environment
|
||||
- [ ] Set `CONTACT_EMAIL` to your email address
|
||||
- [ ] Enable HTTPS (middleware automatically enables HSTS)
|
||||
- [ ] Configure `ALLOWED_ORIGINS` if using custom domain
|
||||
- [ ] Set up log monitoring
|
||||
- [ ] Test email delivery
|
||||
- [ ] Monitor rate limit statistics
|
||||
- [ ] Set up email delivery monitoring
|
||||
- [ ] Configure email bounce handling
|
||||
- [ ] Review security headers
|
||||
|
||||
### Production Environment
|
||||
|
||||
```bash
|
||||
# Production .env
|
||||
GO_ENV=production
|
||||
SMTP_HOST=smtp.gmail.com
|
||||
SMTP_PORT=587
|
||||
SMTP_USER=your-email@gmail.com
|
||||
SMTP_PASSWORD=your-app-password
|
||||
CONTACT_EMAIL=your-email@gmail.com
|
||||
ALLOWED_ORIGINS=yourdomain.com,www.yourdomain.com
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Email Not Sending
|
||||
|
||||
1. **Check SMTP credentials**
|
||||
- Verify App Password is correct
|
||||
- Ensure 2FA is enabled on Google account
|
||||
|
||||
2. **Check logs**
|
||||
- Look for SMTP connection errors
|
||||
- Verify email service initialization
|
||||
|
||||
3. **Test SMTP connection**
|
||||
```bash
|
||||
telnet smtp.gmail.com 587
|
||||
```
|
||||
|
||||
### Rate Limiting Issues
|
||||
|
||||
1. **IP address detection**
|
||||
- Check `X-Forwarded-For` header if behind proxy
|
||||
- Verify IP extraction in logs
|
||||
|
||||
2. **Adjust limits**
|
||||
- Modify `limit` and `window` in `contact_rate_limit.go`
|
||||
- Default: 5 requests per hour
|
||||
|
||||
### CSRF Token Issues
|
||||
|
||||
1. **Token not set**
|
||||
- Ensure cookie is being set on GET requests
|
||||
- Check browser cookie settings
|
||||
|
||||
2. **Token mismatch**
|
||||
- Verify token is passed in form or header
|
||||
- Check token expiration (24 hours)
|
||||
|
||||
## Files Modified/Created
|
||||
|
||||
### New Files
|
||||
- `internal/services/email.go` - Email service with SMTP
|
||||
- `internal/handlers/contact.go` - Contact form handler
|
||||
- `internal/middleware/contact_rate_limit.go` - Rate limiting
|
||||
- `internal/middleware/csrf.go` - CSRF protection
|
||||
- `internal/middleware/browser_only.go` - Browser validation
|
||||
- `templates/partials/contact_success.html` - Success template
|
||||
- `templates/partials/contact_error.html` - Error template
|
||||
- `test_contact_form.html` - Test page
|
||||
|
||||
### Modified Files
|
||||
- `internal/config/config.go` - Added email configuration
|
||||
- `internal/routes/routes.go` - Registered contact endpoint
|
||||
- `main.go` - Initialized contact handler and email service
|
||||
- `.env.example` - Added email configuration example
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### What's Protected Against
|
||||
|
||||
✅ **CSRF Attacks** - Token validation
|
||||
✅ **Rate Limiting Bypass** - IP-based limiting
|
||||
✅ **Bot Submissions** - Honeypot + timing + User-Agent
|
||||
✅ **Email Header Injection** - Newline filtering
|
||||
✅ **XSS** - Input sanitization
|
||||
✅ **External API Access** - Browser-only enforcement
|
||||
✅ **Spam** - Rate limiting + bot protection
|
||||
✅ **Brute Force** - Rate limiting
|
||||
|
||||
### What's NOT Protected Against
|
||||
|
||||
⚠️ **Distributed Attacks** - Single-IP rate limiting only
|
||||
⚠️ **Sophisticated Bots** - May bypass basic User-Agent checks
|
||||
⚠️ **Email Bombing** - Recipient rate limiting not implemented
|
||||
|
||||
### Recommended Additions for Production
|
||||
|
||||
1. **CAPTCHA** - Add reCAPTCHA or hCaptcha
|
||||
2. **Email Verification** - Verify sender's email address
|
||||
3. **Advanced Bot Detection** - Integrate with services like Cloudflare
|
||||
4. **Distributed Rate Limiting** - Use Redis for multi-server deployments
|
||||
5. **Email Queue** - Use background job processor for email sending
|
||||
6. **Delivery Monitoring** - Track email delivery success/failure
|
||||
7. **Spam Detection** - Content-based spam filtering
|
||||
|
||||
## License & Reusability
|
||||
|
||||
This implementation is designed to be reusable across projects. Feel free to:
|
||||
|
||||
- Copy the entire `services/email.go` for email functionality
|
||||
- Reuse middleware components independently
|
||||
- Adapt the contact handler for your needs
|
||||
- Modify rate limits and validation rules
|
||||
|
||||
All code follows Go best practices and is production-ready.
|
||||
|
||||
## Support
|
||||
|
||||
For issues or questions:
|
||||
- Check the logs for detailed error messages
|
||||
- Review security event logs for blocked requests
|
||||
- Test with the included `test_contact_form.html`
|
||||
- Verify SMTP credentials are correct
|
||||
|
||||
---
|
||||
|
||||
**Implementation Date:** November 30, 2025
|
||||
**Version:** 1.0.0
|
||||
**Author:** Backend Craftsman
|
||||
Reference in New Issue
Block a user