473 lines
13 KiB
Markdown
473 lines
13 KiB
Markdown
|
|
# 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
|