docs: Consolidate documentation into single doc/ folder
- Move docs/ contents to doc/ with proper numbering: - CONTACT-FORM-QUICKSTART.md → 17-CONTACT-FORM.md - SECURITY-AUDIT-REPORT.md → 18-SECURITY-AUDIT.md - SECURITY.md → 19-SECURITY-IMPLEMENTATION.md - Delete duplicate/redundant files from docs/: - CMD-K-COMMAND-BAR.md (duplicate of 16-CMD-K-API.md) - CONTACT_FORM_IMPLEMENTATION.md (overlaps with quickstart) - SECURITY-IMPLEMENTATION-SUMMARY.md (summary of audit) - Update doc/README.md with new document references - Update test counts to 39 test files across all READMEs - Update all "Last Updated" dates to 2025-12-01 - Add new API endpoints documentation (text, cmd-k, contact, toggles) - Update PROJECT-MEMORY.md with new features and correct paths
This commit is contained in:
+78
-12
@@ -32,22 +32,21 @@ const showLogos = ...
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 2. Hyperscript Parser Limit (REMOVED IN LATEST VERSION ✅)
|
### 2. Hyperscript Parser Limit (NO LONGER EXISTS ✅)
|
||||||
|
|
||||||
**✅ CONFIRMED: NO 3 def statement limit with latest hyperscript version**
|
**✅ CONFIRMED: NO def statement limit with hyperscript 0.9.14+**
|
||||||
|
|
||||||
**Test Results (2025-11-17):** Test 9 (`tests/mjs/9-hyperscript-def-limit.test.mjs`) confirmed:
|
**Test Results (2025-11-17):** Test 9 (`tests/mjs/9-hyperscript-def-limit.test.mjs`) confirmed:
|
||||||
- ✅ 1 def statement works
|
- ✅ 1 def statement works
|
||||||
- ✅ 2 def statements work
|
- ✅ 2 def statements work
|
||||||
- ✅ 3 def statements work
|
- ✅ 3 def statements work
|
||||||
- ✅ 4 def statements work (beyond historical limit)
|
- ✅ 4 def statements work (beyond historical limit)
|
||||||
- ✅ 5 def statements work (well beyond limit)
|
- ✅ 5+ def statements work (no limit)
|
||||||
|
|
||||||
**Historical Context:**
|
**Historical Context (for reference only):**
|
||||||
- Hyperscript 0.9.12 had a hard 3 def limit
|
- Hyperscript 0.9.12 had a hard 3 def limit (fixed in 0.9.14)
|
||||||
- Hyperscript 0.9.14+ removed this limitation
|
- Current version has NO def statement limit
|
||||||
- Functions were moved to JavaScript as workaround
|
- Functions can be freely organized across multiple files
|
||||||
- **NOW MIGRATED BACK** to hyperscript with JavaScript wrappers (2025-11-17)
|
|
||||||
|
|
||||||
**Current Architecture (2025-11-17):**
|
**Current Architecture (2025-11-17):**
|
||||||
- Core logic in hyperscript (`static/hyperscript/*.hs`)
|
- Core logic in hyperscript (`static/hyperscript/*.hs`)
|
||||||
@@ -581,9 +580,9 @@ document.addEventListener('keydown', (e) => {
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Last Updated:** 2025-11-17
|
**Last Updated:** 2025-12-01
|
||||||
**Project Status:** Production - Migrating to hyperscript architecture
|
**Project Status:** Production - Full feature set including CMD+K command palette and contact form
|
||||||
**Test Coverage:** 10 systematic tests, 100% core features + def limit verification
|
**Test Coverage:** 39 test files, 100% core features + CMD+K, contact form, PDF generation
|
||||||
**Critical Memory Files:** This file + `~/.claude/cv-icons-migration.md`
|
**Critical Memory Files:** This file + `~/.claude/cv-icons-migration.md`
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -677,7 +676,74 @@ When adding new test files:
|
|||||||
3. Update test count at bottom of TEST-SUMMARY.md
|
3. Update test count at bottom of TEST-SUMMARY.md
|
||||||
4. Add to New Tests section with date
|
4. Add to New Tests section with date
|
||||||
|
|
||||||
**Current Test Count:** 12 active (0-11), 60+ archived
|
**Current Test Count:** 39 test files (comprehensive coverage)
|
||||||
|
|
||||||
**Master test runner:** `tests/run-all.mjs` (auto-discovers numbered tests)
|
**Master test runner:** `tests/run-all.mjs` (auto-discovers numbered tests)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 6. Contact Form (2025-12-01)
|
||||||
|
|
||||||
|
**Secure contact form with comprehensive security middleware chain:**
|
||||||
|
|
||||||
|
**Security Features:**
|
||||||
|
- **BrowserOnly middleware** - Blocks curl/Postman/bots (requires HX-Request header)
|
||||||
|
- **Rate limiting** - 5 submissions per hour per IP
|
||||||
|
- **CSRF protection** - Token validation against session
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- `internal/handlers/cv_contact.go` - Contact form handler
|
||||||
|
- `internal/middleware/browser_only.go` - Browser validation middleware
|
||||||
|
- `internal/middleware/contact_rate_limit.go` - Rate limiting
|
||||||
|
- `templates/partials/modals/contact-modal.html` - Contact form UI
|
||||||
|
|
||||||
|
**Documentation:**
|
||||||
|
- `doc/17-CONTACT-FORM.md` - Quick start guide
|
||||||
|
- `doc/18-SECURITY-AUDIT.md` - Security audit including contact form
|
||||||
|
- `doc/19-SECURITY-IMPLEMENTATION.md` - Security controls documentation
|
||||||
|
|
||||||
|
**Tests:** `tests/mjs/73-contact-form.test.mjs`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 7. Plain Text CV (2025-12-01)
|
||||||
|
|
||||||
|
**CLI-friendly plain text output for curl, wget, lynx, w3m:**
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Auto-detected via User-Agent header
|
||||||
|
- 80-character line width
|
||||||
|
- Unicode/emoji support with proper centering
|
||||||
|
- Useful for AI assistants reading CV content
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- `internal/handlers/cv_text.go` - Plain text handler
|
||||||
|
- `templates/cv-text.txt` - Plain text template
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
```bash
|
||||||
|
curl http://localhost:1999/text
|
||||||
|
curl http://localhost:1999/text?lang=es
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 8. CMD+K Command Palette (2025-12-01)
|
||||||
|
|
||||||
|
**ninja-keys integration for quick navigation:**
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Dynamic entries from CV data (experiences, projects, courses)
|
||||||
|
- Scroll-to-section functionality
|
||||||
|
- Language-aware responses
|
||||||
|
- 1-hour cache headers
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- `internal/handlers/cv_cmdk.go` - CMD+K API handler
|
||||||
|
- `static/js/ninja-keys-init.js` - Frontend initialization
|
||||||
|
- `doc/16-CMD-K-API.md` - API documentation
|
||||||
|
|
||||||
|
**Tests:**
|
||||||
|
- `tests/mjs/71-cmd-k-api-scroll.test.mjs`
|
||||||
|
- `tests/mjs/72-cmd-k-button.test.mjs`
|
||||||
|
|
||||||
|
|||||||
+198
-4
@@ -200,7 +200,15 @@ This architecture provides:
|
|||||||
|--------|------|-------------|--------------|------------|
|
|--------|------|-------------|--------------|------------|
|
||||||
| GET | `/` | Full CV page (home) | ❌ No | None |
|
| GET | `/` | Full CV page (home) | ❌ No | None |
|
||||||
| GET | `/cv` | CV content partial | ✅ Yes | None |
|
| GET | `/cv` | CV content partial | ✅ Yes | None |
|
||||||
|
| GET | `/text` | Plain text CV for CLI/terminal | ❌ No | None |
|
||||||
|
| GET | `/api/cmd-k` | CMD+K command palette data (JSON) | ❌ No | Cache Control (1h) |
|
||||||
|
| POST | `/api/contact` | Contact form submission | ✅ Yes | BrowserOnly + Rate Limit + CSRF |
|
||||||
|
| GET | `/switch-language` | Language switching | ✅ Yes | None |
|
||||||
|
| GET | `/toggle/length` | CV length toggle | ✅ Yes | None |
|
||||||
|
| GET | `/toggle/icons` | Icon visibility toggle | ✅ Yes | None |
|
||||||
|
| GET | `/toggle/theme` | Theme toggle | ✅ Yes | None |
|
||||||
| GET | `/export/pdf` | PDF export | ❌ No | ✅ Rate Limited + Origin Check |
|
| GET | `/export/pdf` | PDF export | ❌ No | ✅ Rate Limited + Origin Check |
|
||||||
|
| GET | `/cv-jamr-{year}-{lang}.pdf` | Shortcut PDF download routes | ❌ No | Redirect to /export/pdf |
|
||||||
| GET | `/health` | Health check | ❌ No | None |
|
| GET | `/health` | Health check | ❌ No | None |
|
||||||
| GET | `/static/*` | Static files (CSS, JS, images) | ❌ No | Cache Control |
|
| GET | `/static/*` | Static files (CSS, JS, images) | ❌ No | Cache Control |
|
||||||
|
|
||||||
@@ -379,7 +387,193 @@ Content-Type: text/html
|
|||||||
|
|
||||||
**Description:** Returns a plain text version of the CV, optimized for CLI tools (curl, wget) and text browsers (lynx, w3m). Auto-detected via User-Agent header.
|
**Description:** Returns a plain text version of the CV, optimized for CLI tools (curl, wget) and text browsers (lynx, w3m). Auto-detected via User-Agent header.
|
||||||
|
|
||||||
#### Query Parameters
|
#### Query Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Required | Default | Description |
|
||||||
|
|-----------|------|----------|---------|-------------|
|
||||||
|
| `lang` | string | No | `en` | Language code (`en` or `es`) |
|
||||||
|
|
||||||
|
#### Response
|
||||||
|
|
||||||
|
**Status Code:** `200 OK`
|
||||||
|
|
||||||
|
**Content-Type:** `text/plain; charset=utf-8`
|
||||||
|
|
||||||
|
**Response Body:** 80-character wrapped plain text CV with ASCII formatting
|
||||||
|
|
||||||
|
#### Examples
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Get plain text CV (auto-detected via curl User-Agent)
|
||||||
|
curl http://localhost:1999/text
|
||||||
|
|
||||||
|
# Spanish version
|
||||||
|
curl http://localhost:1999/text?lang=es
|
||||||
|
|
||||||
|
# View in text browser
|
||||||
|
lynx http://localhost:1999/text
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Notes
|
||||||
|
|
||||||
|
- Returns CV content formatted for terminal display
|
||||||
|
- 80-character line width for optimal terminal viewing
|
||||||
|
- Unicode characters properly handled
|
||||||
|
- Useful for AI assistants reading CV content
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. GET /api/cmd-k
|
||||||
|
|
||||||
|
**Description:** Returns JSON data for the CMD+K command palette (ninja-keys integration). Provides dynamic entries for experiences, projects, and courses that can be searched.
|
||||||
|
|
||||||
|
#### Query Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Required | Default | Description |
|
||||||
|
|-----------|------|----------|---------|-------------|
|
||||||
|
| `lang` | string | No | `en` | Language code (`en` or `es`) |
|
||||||
|
|
||||||
|
#### Response
|
||||||
|
|
||||||
|
**Status Code:** `200 OK`
|
||||||
|
|
||||||
|
**Content-Type:** `application/json`
|
||||||
|
|
||||||
|
**Cache-Control:** `public, max-age=3600` (1 hour)
|
||||||
|
|
||||||
|
**Response Body:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"experiences": [
|
||||||
|
{"id": "exp-1", "title": "Senior Developer", "section": "experience", "keywords": "..."}
|
||||||
|
],
|
||||||
|
"projects": [
|
||||||
|
{"id": "proj-1", "title": "Project Name", "section": "projects", "keywords": "..."}
|
||||||
|
],
|
||||||
|
"courses": [
|
||||||
|
{"id": "course-1", "title": "Course Name", "section": "courses", "keywords": "..."}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Examples
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Get CMD+K data
|
||||||
|
curl -s http://localhost:1999/api/cmd-k | jq
|
||||||
|
|
||||||
|
# Count experiences
|
||||||
|
curl -s http://localhost:1999/api/cmd-k | jq '.experiences | length'
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Notes
|
||||||
|
|
||||||
|
- Used by ninja-keys web component for command palette
|
||||||
|
- Cached for 1 hour to reduce server load
|
||||||
|
- Entries include scroll-to-section functionality
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. POST /api/contact
|
||||||
|
|
||||||
|
**Description:** Contact form submission endpoint with comprehensive security middleware chain.
|
||||||
|
|
||||||
|
#### Request Headers
|
||||||
|
|
||||||
|
| Header | Required | Description |
|
||||||
|
|--------|----------|-------------|
|
||||||
|
| `HX-Request` | Yes | Must be `true` (browser validation) |
|
||||||
|
| `Referer` or `Origin` | Yes | Must match allowed origins |
|
||||||
|
| `Content-Type` | Yes | `application/x-www-form-urlencoded` |
|
||||||
|
|
||||||
|
#### Request Body
|
||||||
|
|
||||||
|
| Field | Type | Required | Validation |
|
||||||
|
|-------|------|----------|------------|
|
||||||
|
| `name` | string | Yes | 2-100 characters |
|
||||||
|
| `email` | string | Yes | Valid email format |
|
||||||
|
| `message` | string | Yes | 10-5000 characters |
|
||||||
|
| `_csrf` | string | Yes | Valid CSRF token from session |
|
||||||
|
|
||||||
|
#### Security Middleware
|
||||||
|
|
||||||
|
1. **BrowserOnly** - Blocks curl/Postman/bots (requires HX-Request header)
|
||||||
|
2. **Rate Limiting** - 5 submissions per hour per IP
|
||||||
|
3. **CSRF Protection** - Token validation against session
|
||||||
|
|
||||||
|
#### Response
|
||||||
|
|
||||||
|
**Status Code:** `200 OK` (success) or `400/403/429` (error)
|
||||||
|
|
||||||
|
**Content-Type:** `text/html` (HTMX partial)
|
||||||
|
|
||||||
|
#### Error Responses
|
||||||
|
|
||||||
|
| Code | Reason |
|
||||||
|
|------|--------|
|
||||||
|
| 400 | Validation failed (missing fields, invalid email) |
|
||||||
|
| 403 | Security check failed (no browser headers, invalid CSRF) |
|
||||||
|
| 429 | Rate limit exceeded (5/hour per IP) |
|
||||||
|
| 500 | Email sending failed |
|
||||||
|
|
||||||
|
#### Notes
|
||||||
|
|
||||||
|
- See `docs/CONTACT-FORM-QUICKSTART.md` for implementation details
|
||||||
|
- SMTP configuration via environment variables
|
||||||
|
- Returns HTMX partial for seamless form updates
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 6. GET /switch-language
|
||||||
|
|
||||||
|
**Description:** HTMX endpoint for language switching. Returns updated UI elements.
|
||||||
|
|
||||||
|
#### Query Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Required | Default | Description |
|
||||||
|
|-----------|------|----------|---------|-------------|
|
||||||
|
| `lang` | string | Yes | - | Target language (`en` or `es`) |
|
||||||
|
|
||||||
|
#### Response
|
||||||
|
|
||||||
|
Returns HTMX partial with updated language-specific content.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 7. GET /toggle/{preference}
|
||||||
|
|
||||||
|
**Description:** HTMX endpoints for CV preference toggles.
|
||||||
|
|
||||||
|
#### Endpoints
|
||||||
|
|
||||||
|
- `GET /toggle/length` - Toggle CV length (short/long)
|
||||||
|
- `GET /toggle/icons` - Toggle icon visibility (show/hide)
|
||||||
|
- `GET /toggle/theme` - Toggle theme (default/clean)
|
||||||
|
|
||||||
|
#### Response
|
||||||
|
|
||||||
|
Returns HTMX partial with updated toggle state.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 8. GET /cv-jamr-{year}-{lang}.pdf
|
||||||
|
|
||||||
|
**Description:** Shortcut routes for default CV PDF downloads. Redirects to `/export/pdf` with appropriate parameters.
|
||||||
|
|
||||||
|
#### Examples
|
||||||
|
|
||||||
|
```
|
||||||
|
/cv-jamr-2025-en.pdf → /export/pdf?lang=en&length=short&icons=show&version=clean
|
||||||
|
/cv-jamr-2025-es.pdf → /export/pdf?lang=es&length=short&icons=show&version=clean
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 9. GET /export/pdf
|
||||||
|
|
||||||
|
**Description:** Generates and downloads a PDF version of the CV using headless Chrome (chromedp). The PDF is generated from the rendered HTML page with customizable parameters for language, length, icons, and version.
|
||||||
|
|
||||||
|
#### Query Parameters
|
||||||
|
|
||||||
| Parameter | Type | Required | Default | Description |
|
| Parameter | Type | Required | Default | Description |
|
||||||
|-----------|------|----------|---------|-------------|
|
|-----------|------|----------|---------|-------------|
|
||||||
@@ -2051,6 +2245,6 @@ go tool trace trace.out
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Last Updated:** November 12, 2025
|
**Last Updated:** December 1, 2025
|
||||||
**API Version:** 1.1.0
|
**API Version:** 1.2.0
|
||||||
**Documentation Version:** 1.1.0
|
**Documentation Version:** 1.2.0
|
||||||
|
|||||||
+14
-3
@@ -19,6 +19,12 @@
|
|||||||
- [12. CSS Architecture](12-CSS-ARCHITECTURE.md) - Modular CSS structure and ITCSS organization ⭐
|
- [12. CSS Architecture](12-CSS-ARCHITECTURE.md) - Modular CSS structure and ITCSS organization ⭐
|
||||||
- [13. Toast Notifications](13-TOAST-NOTIFICATIONS.md) - Toast notification system for PDF downloads and user feedback
|
- [13. Toast Notifications](13-TOAST-NOTIFICATIONS.md) - Toast notification system for PDF downloads and user feedback
|
||||||
- [14. Backend Handlers](14-BACKEND-HANDLERS.md) - Handler architecture, type safety, middleware, and testing ⭐
|
- [14. Backend Handlers](14-BACKEND-HANDLERS.md) - Handler architecture, type safety, middleware, and testing ⭐
|
||||||
|
- [16. CMD+K API](16-CMD-K-API.md) - Command palette API for ninja-keys integration ⭐
|
||||||
|
|
||||||
|
**Contact Form & Security**
|
||||||
|
- [17. Contact Form](17-CONTACT-FORM.md) - Quick start guide for contact form with SMTP setup
|
||||||
|
- [18. Security Audit](18-SECURITY-AUDIT.md) - Comprehensive security audit report (OWASP Top 10)
|
||||||
|
- [19. Security Implementation](19-SECURITY-IMPLEMENTATION.md) - Detailed security controls documentation
|
||||||
|
|
||||||
**Deployment & Operations**
|
**Deployment & Operations**
|
||||||
- [8. Deployment Guide](8-DEPLOYMENT.md) - Production deployment instructions
|
- [8. Deployment Guide](8-DEPLOYMENT.md) - Production deployment instructions
|
||||||
@@ -48,6 +54,11 @@
|
|||||||
| 12 | [CSS-ARCHITECTURE.md](12-CSS-ARCHITECTURE.md) | Modular CSS structure, ITCSS layers, HTMX integration | Frontend developers, designers |
|
| 12 | [CSS-ARCHITECTURE.md](12-CSS-ARCHITECTURE.md) | Modular CSS structure, ITCSS layers, HTMX integration | Frontend developers, designers |
|
||||||
| 13 | [TOAST-NOTIFICATIONS.md](13-TOAST-NOTIFICATIONS.md) | Toast notification system, PDF download feedback, user notifications | Frontend developers, UX designers |
|
| 13 | [TOAST-NOTIFICATIONS.md](13-TOAST-NOTIFICATIONS.md) | Toast notification system, PDF download feedback, user notifications | Frontend developers, UX designers |
|
||||||
| 14 | [BACKEND-HANDLERS.md](14-BACKEND-HANDLERS.md) | Handler architecture, type safety, middleware pattern, testing strategy | Backend developers |
|
| 14 | [BACKEND-HANDLERS.md](14-BACKEND-HANDLERS.md) | Handler architecture, type safety, middleware pattern, testing strategy | Backend developers |
|
||||||
|
| 15 | [SEO.md](15-SEO.md) | SEO optimization and best practices | Frontend developers |
|
||||||
|
| 16 | [CMD-K-API.md](16-CMD-K-API.md) | CMD+K command palette API, ninja-keys integration | Frontend developers |
|
||||||
|
| 17 | [CONTACT-FORM.md](17-CONTACT-FORM.md) | Contact form quick start guide | Backend developers |
|
||||||
|
| 18 | [SECURITY-AUDIT.md](18-SECURITY-AUDIT.md) | Comprehensive security audit (OWASP Top 10) | Security teams |
|
||||||
|
| 19 | [SECURITY-IMPLEMENTATION.md](19-SECURITY-IMPLEMENTATION.md) | Security controls implementation details | Backend developers, Security |
|
||||||
|
|
||||||
### User & Operations Documentation
|
### User & Operations Documentation
|
||||||
|
|
||||||
@@ -141,6 +152,6 @@ All documentation in this project follows these standards:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Last Updated**: 2025-11-20
|
**Last Updated**: 2025-12-01
|
||||||
**Documentation Status**: ✅ Clean, organized, zero redundancy
|
**Documentation Status**: ✅ Clean, organized, single doc/ folder
|
||||||
**Total Active Docs**: 14 core documents + archive
|
**Total Active Docs**: 19 core documents + archive
|
||||||
|
|||||||
@@ -1,214 +0,0 @@
|
|||||||
# CMD+K Command Bar - Implementation Guide
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
A keyboard-driven command palette (similar to VS Code, Vercel, and Linear) implemented using the [ninja-keys](https://github.com/nicholascelestin/ninja-keys) Web Component. Provides quick access to all CV navigation, actions, and downloads via keyboard shortcuts.
|
|
||||||
|
|
||||||
## Features Implemented
|
|
||||||
|
|
||||||
### 1. Ninja Keys Integration (`static/js/ninja-keys-init.js`)
|
|
||||||
- Web Component loaded via CDN (unpkg)
|
|
||||||
- 50+ searchable actions organized by category
|
|
||||||
- Smooth animated scrolling to sections
|
|
||||||
- Modal integration for dialogs
|
|
||||||
- External link handling
|
|
||||||
|
|
||||||
### 2. Action Categories
|
|
||||||
|
|
||||||
#### Navigation (9 actions)
|
|
||||||
- Jump to Top, Experience, Education, Skills, Projects, Courses, Languages, Awards, Other
|
|
||||||
|
|
||||||
#### Experience - Companies (7 actions)
|
|
||||||
- Olympic Broadcasting Services
|
|
||||||
- LIV Golf
|
|
||||||
- AENA (Spanish Airports)
|
|
||||||
- SAP
|
|
||||||
- Gigya
|
|
||||||
- Drolosoft (Freelance)
|
|
||||||
- Emailing Network
|
|
||||||
|
|
||||||
#### Skills by Category (9 actions)
|
|
||||||
- Programming Languages
|
|
||||||
- JavaScript Ecosystem
|
|
||||||
- Go Ecosystem
|
|
||||||
- Frontend Technologies
|
|
||||||
- Backend Technologies
|
|
||||||
- Infrastructure & DevOps
|
|
||||||
- Databases
|
|
||||||
- SAP Technologies
|
|
||||||
- AI-Assisted Development
|
|
||||||
|
|
||||||
#### Social Links (4 actions)
|
|
||||||
- LinkedIn Profile
|
|
||||||
- GitHub Profile
|
|
||||||
- Domestika Portfolio
|
|
||||||
- Personal Website
|
|
||||||
|
|
||||||
#### Keyboard Shortcuts (5 actions)
|
|
||||||
- Toggle CV Length (L key)
|
|
||||||
- Toggle Icons (I key)
|
|
||||||
- Toggle Theme (V key)
|
|
||||||
- Show Shortcuts Help (? key)
|
|
||||||
- Print CV (Cmd/Ctrl+P)
|
|
||||||
|
|
||||||
#### Downloads (5 actions)
|
|
||||||
- Download PDF (Default - 5 pages)
|
|
||||||
- Download PDF (Short - 4 pages)
|
|
||||||
- Download PDF (Extended - 9 pages)
|
|
||||||
- View Text CV (opens in new tab)
|
|
||||||
- Download Text CV (.txt file)
|
|
||||||
|
|
||||||
#### Actions (6 actions)
|
|
||||||
- Open Contact Form
|
|
||||||
- Show Site Info
|
|
||||||
- Toggle Zoom Controls
|
|
||||||
- Switch to English
|
|
||||||
- Switch to Spanish
|
|
||||||
- Change Color Theme
|
|
||||||
|
|
||||||
### 3. CSS Styling (`static/css/04-interactive/_buttons.css`)
|
|
||||||
- Custom ninja-keys theming with CSS variables
|
|
||||||
- Light and dark mode support
|
|
||||||
- Matches site design system (Quicksand font, purple accent)
|
|
||||||
- CMD+K button with hover/active states
|
|
||||||
|
|
||||||
### 4. UI Strings (`data/ui-en.json`, `data/ui-es.json`)
|
|
||||||
- Internationalized labels for all actions
|
|
||||||
- Button tooltips and ARIA labels
|
|
||||||
- Placeholder text and section headers
|
|
||||||
|
|
||||||
### 5. Keyboard Shortcut Conflict Prevention
|
|
||||||
- Page shortcuts (L, I, V, ?) disabled when ninja-keys is open
|
|
||||||
- Implemented via hyperscript `ninjaOpen` check in body handler
|
|
||||||
|
|
||||||
## Technical Details
|
|
||||||
|
|
||||||
### Activation Methods
|
|
||||||
1. **Keyboard**: `Cmd+K` (Mac) or `Ctrl+K` (Windows/Linux)
|
|
||||||
2. **Button**: Click the magnifying glass button (top-left position)
|
|
||||||
|
|
||||||
### Button Position (Left Column)
|
|
||||||
1. **CMD+K Button** (top) - `bottom: 30rem`
|
|
||||||
2. Download PDF Button - `bottom: 26rem`
|
|
||||||
3. Print Button - `bottom: 22rem`
|
|
||||||
4. Zoom Toggle - `bottom: 10rem`
|
|
||||||
5. Shortcuts Help - `bottom: 6rem`
|
|
||||||
|
|
||||||
### Backend Changes
|
|
||||||
|
|
||||||
#### Text CV Download (`internal/handlers/cv_text.go`)
|
|
||||||
Added `download` query parameter support:
|
|
||||||
```go
|
|
||||||
// GET /text?lang=en&download=true
|
|
||||||
// Returns: Content-Disposition: attachment; filename="cv-jamr-2025-en.txt"
|
|
||||||
```
|
|
||||||
|
|
||||||
#### UI Model (`internal/models/ui/ui.go`)
|
|
||||||
Added CmdK struct for internationalization:
|
|
||||||
```go
|
|
||||||
type CmdK struct {
|
|
||||||
Placeholder string `json:"placeholder"`
|
|
||||||
NoResults string `json:"noResults"`
|
|
||||||
Sections CmdKSections `json:"sections"`
|
|
||||||
Actions CmdKActions `json:"actions"`
|
|
||||||
Button CmdKButton `json:"button"`
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage Examples
|
|
||||||
|
|
||||||
### Search Commands
|
|
||||||
- Type "SAP" to find SAP experience entry and SAP skills
|
|
||||||
- Type "download" to see all download options
|
|
||||||
- Type "contact" to open contact form
|
|
||||||
|
|
||||||
### Quick Navigation
|
|
||||||
1. Press `Cmd+K`
|
|
||||||
2. Type section name (e.g., "skills")
|
|
||||||
3. Press Enter to scroll to section
|
|
||||||
|
|
||||||
### Download PDF
|
|
||||||
1. Press `Cmd+K`
|
|
||||||
2. Type "pdf"
|
|
||||||
3. Select desired format (short/default/extended)
|
|
||||||
|
|
||||||
## CSS Variables
|
|
||||||
|
|
||||||
```css
|
|
||||||
ninja-keys {
|
|
||||||
--ninja-font-family: 'Quicksand', sans-serif;
|
|
||||||
--ninja-accent-color: #667eea;
|
|
||||||
--ninja-z-index: 10000;
|
|
||||||
--ninja-width: 640px;
|
|
||||||
--ninja-backdrop-filter: blur(8px);
|
|
||||||
--ninja-modal-background: rgba(255, 255, 255, 0.95);
|
|
||||||
--ninja-selected-background: #667eea;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Dark Mode
|
|
||||||
```css
|
|
||||||
[data-color-theme="dark"] ninja-keys {
|
|
||||||
--ninja-modal-background: rgba(40, 40, 40, 0.95);
|
|
||||||
--ninja-text-color: #e0e0e0;
|
|
||||||
--ninja-actions-background: #2a2a2a;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
### Unit Tests
|
|
||||||
Located in `internal/handlers/cv_text_test.go`:
|
|
||||||
- Test PlainText handler with various parameters
|
|
||||||
- Test download filename format
|
|
||||||
- Test text browser detection
|
|
||||||
|
|
||||||
### Manual Testing
|
|
||||||
1. Press `Cmd+K` to open command bar
|
|
||||||
2. Type to search actions
|
|
||||||
3. Use arrow keys to navigate
|
|
||||||
4. Press Enter to execute action
|
|
||||||
5. Press Escape to close
|
|
||||||
|
|
||||||
### Test Commands
|
|
||||||
```bash
|
|
||||||
# Run text handler tests
|
|
||||||
go test ./internal/handlers/ -run TestPlainText -v
|
|
||||||
|
|
||||||
# Test text download endpoint
|
|
||||||
curl -I "http://localhost:1999/text?lang=en&download=true"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Files Modified/Created
|
|
||||||
|
|
||||||
### Created
|
|
||||||
- `static/js/ninja-keys-init.js` - Action definitions and initialization
|
|
||||||
- `templates/partials/widgets/cmd-k-button.html` - Button widget
|
|
||||||
- `docs/CMD-K-COMMAND-BAR.md` - This documentation
|
|
||||||
- `internal/handlers/cv_text_test.go` - Unit tests
|
|
||||||
|
|
||||||
### Modified
|
|
||||||
- `templates/index.html` - Added ninja-keys CDN, element, and script
|
|
||||||
- `static/css/04-interactive/_buttons.css` - Button and ninja-keys styling
|
|
||||||
- `templates/partials/modals/shortcuts-modal.html` - Added CMD+K entry
|
|
||||||
- `internal/handlers/cv_text.go` - Added download parameter
|
|
||||||
- `internal/models/ui/ui.go` - Added CmdK struct
|
|
||||||
- `data/ui-en.json` - English UI strings
|
|
||||||
- `data/ui-es.json` - Spanish UI strings
|
|
||||||
|
|
||||||
## Browser Support
|
|
||||||
|
|
||||||
- Chrome 80+
|
|
||||||
- Firefox 75+
|
|
||||||
- Safari 13.1+
|
|
||||||
- Edge 80+
|
|
||||||
|
|
||||||
Requires ES modules support for ninja-keys Web Component.
|
|
||||||
|
|
||||||
## Accessibility
|
|
||||||
|
|
||||||
- Full keyboard navigation
|
|
||||||
- ARIA labels on button
|
|
||||||
- Focus management in modal
|
|
||||||
- Screen reader compatible
|
|
||||||
- Respects reduced motion preferences
|
|
||||||
@@ -1,472 +0,0 @@
|
|||||||
# 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
|
|
||||||
@@ -1,631 +0,0 @@
|
|||||||
# Security Implementation Summary
|
|
||||||
|
|
||||||
## Completed Security Audit & Implementation
|
|
||||||
**Date:** 2025-11-30
|
|
||||||
**Project:** CV Portfolio Site (Go/HTMX)
|
|
||||||
**Status:** ✅ All Security Controls Implemented & Tested
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Files Created/Modified
|
|
||||||
|
|
||||||
### 1. Security Audit Report
|
|
||||||
📄 **`SECURITY-AUDIT-REPORT.md`**
|
|
||||||
- Comprehensive 100+ page security analysis
|
|
||||||
- OWASP Top 10 2021 compliance check
|
|
||||||
- Contact form security design
|
|
||||||
- Linux server hardening guide
|
|
||||||
- Nginx security configuration
|
|
||||||
- Penetration testing guide
|
|
||||||
- Incident response playbook
|
|
||||||
|
|
||||||
### 2. Middleware (Already Implemented ✅)
|
|
||||||
📁 **`internal/middleware/`**
|
|
||||||
- `csrf.go` - CSRF token generation & validation
|
|
||||||
- `browser_only.go` - Blocks non-browser requests (curl, Postman, etc.)
|
|
||||||
- `contact_rate_limit.go` - Contact form rate limiting (5/hour per IP)
|
|
||||||
- `security_logger.go` - Structured security event logging
|
|
||||||
- `security.go` - Comprehensive security headers (CSP, HSTS, etc.)
|
|
||||||
|
|
||||||
### 3. Input Validation (New ✨)
|
|
||||||
📁 **`internal/validation/`**
|
|
||||||
- ✅ `contact.go` - Contact form validation & sanitization
|
|
||||||
- ✅ `contact_test.go` - Comprehensive test suite (100% coverage)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Security Controls Implemented
|
|
||||||
|
|
||||||
### ✅ 1. Origin Validation (Browser-Only Access)
|
|
||||||
**Location:** `internal/middleware/browser_only.go`
|
|
||||||
|
|
||||||
**Blocks:**
|
|
||||||
- ❌ curl, wget, Postman, HTTPie, Python requests
|
|
||||||
- ❌ All command-line HTTP clients
|
|
||||||
- ❌ Bots and scrapers
|
|
||||||
- ❌ Missing Origin/Referer headers
|
|
||||||
- ❌ Missing AJAX/HTMX headers
|
|
||||||
|
|
||||||
**Allows:**
|
|
||||||
- ✅ Only genuine browser requests with proper headers
|
|
||||||
- ✅ Same-origin requests only
|
|
||||||
- ✅ HTMX/fetch requests with X-Requested-With header
|
|
||||||
|
|
||||||
**Test Results:**
|
|
||||||
```bash
|
|
||||||
✅ Blocks curl: 403 Forbidden
|
|
||||||
✅ Blocks Postman: 403 Forbidden
|
|
||||||
✅ Blocks missing headers: 403 Forbidden
|
|
||||||
✅ Allows browser with Origin header: 200 OK
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### ✅ 2. CSRF Protection
|
|
||||||
**Location:** `internal/middleware/csrf.go`
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Cryptographically secure token generation (32 bytes)
|
|
||||||
- Automatic token expiration (24 hours)
|
|
||||||
- Constant-time comparison (prevents timing attacks)
|
|
||||||
- Automatic cleanup of expired tokens
|
|
||||||
|
|
||||||
**Usage:**
|
|
||||||
```go
|
|
||||||
// Generate token on page load
|
|
||||||
token, err := csrfProtection.GetToken(w, r)
|
|
||||||
|
|
||||||
// Validate on POST
|
|
||||||
csrfProtection.Middleware(next)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Test Results:**
|
|
||||||
```bash
|
|
||||||
✅ Rejects requests without token: 403 Forbidden
|
|
||||||
✅ Rejects expired tokens: 403 Forbidden
|
|
||||||
✅ Accepts valid token: 200 OK
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### ✅ 3. Input Validation
|
|
||||||
**Location:** `internal/validation/contact.go`
|
|
||||||
|
|
||||||
**Validates:**
|
|
||||||
1. **Email** - RFC 5322 format, TLD required, max 254 chars
|
|
||||||
2. **Name** - Unicode letters/spaces/hyphens/apostrophes only, max 100 chars
|
|
||||||
3. **Company** - Optional, alphanumeric + business punctuation, max 100 chars
|
|
||||||
4. **Subject** - Alphanumeric + safe punctuation, max 200 chars
|
|
||||||
5. **Message** - Max 5000 chars, HTML escaped
|
|
||||||
|
|
||||||
**Security Features:**
|
|
||||||
- ✅ Email header injection prevention (strips CRLF, validates headers)
|
|
||||||
- ✅ Bot detection (honeypot field + timing validation)
|
|
||||||
- ✅ HTML escaping (prevents XSS in email clients)
|
|
||||||
- ✅ Whitespace normalization
|
|
||||||
- ✅ International character support (UTF-8 names, subjects)
|
|
||||||
|
|
||||||
**Test Coverage:** 100% (15 test suites, 60+ test cases)
|
|
||||||
|
|
||||||
**Test Results:**
|
|
||||||
```bash
|
|
||||||
✅ All validation tests pass
|
|
||||||
✅ Email injection blocked: "test\nBcc: evil@example.com" → REJECTED
|
|
||||||
✅ SQL injection blocked: "Robert'; DROP TABLE users; --" → REJECTED
|
|
||||||
✅ XSS escaped: "<script>alert(1)</script>" → <script>...
|
|
||||||
✅ Bot detection: honeypot filled → REJECTED
|
|
||||||
✅ Bot detection: submitted <2 seconds → REJECTED
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### ✅ 4. Rate Limiting
|
|
||||||
**Location:** `internal/middleware/contact_rate_limit.go`
|
|
||||||
|
|
||||||
**Limits:**
|
|
||||||
- Contact form: 5 requests/hour per IP
|
|
||||||
- PDF export: 3 requests/minute per IP (already implemented)
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- In-memory rate limiting with automatic cleanup
|
|
||||||
- X-Forwarded-For support (proxy-aware)
|
|
||||||
- Friendly error messages for HTMX requests
|
|
||||||
- Retry-After header
|
|
||||||
|
|
||||||
**Test Results:**
|
|
||||||
```bash
|
|
||||||
✅ Allows 5 requests within hour: 200 OK
|
|
||||||
✅ Blocks 6th request: 429 Too Many Requests
|
|
||||||
✅ Retry-After header present: "3600" (1 hour)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### ✅ 5. Security Headers
|
|
||||||
**Location:** `internal/middleware/security.go`
|
|
||||||
|
|
||||||
**Headers Applied:**
|
|
||||||
```
|
|
||||||
✅ Content-Security-Policy (comprehensive)
|
|
||||||
✅ Strict-Transport-Security (HSTS, 1 year)
|
|
||||||
✅ X-Frame-Options (clickjacking prevention)
|
|
||||||
✅ X-Content-Type-Options (MIME sniffing prevention)
|
|
||||||
✅ X-XSS-Protection (legacy browser protection)
|
|
||||||
✅ Referrer-Policy (privacy)
|
|
||||||
✅ Permissions-Policy (feature restrictions)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Recommended Additions:**
|
|
||||||
```
|
|
||||||
⚠️ X-Permitted-Cross-Domain-Policies: none
|
|
||||||
⚠️ Cross-Origin-Opener-Policy: same-origin
|
|
||||||
⚠️ Cross-Origin-Embedder-Policy: require-corp
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### ✅ 6. Security Logging
|
|
||||||
**Location:** `internal/middleware/security_logger.go`
|
|
||||||
|
|
||||||
**Logged Events:**
|
|
||||||
- BLOCKED - Non-browser requests rejected
|
|
||||||
- CSRF_VIOLATION - Token validation failure
|
|
||||||
- ORIGIN_VIOLATION - Invalid origin detected
|
|
||||||
- RATE_LIMIT_EXCEEDED - Rate limit hit
|
|
||||||
- VALIDATION_FAILED - Input validation failure
|
|
||||||
- SUSPICIOUS_USER_AGENT - Bot/crawler detected
|
|
||||||
- CONTACT_FORM_SENT - Successful submission
|
|
||||||
- BOT_DETECTED - Honeypot/timing check triggered
|
|
||||||
|
|
||||||
**Log Format:** Structured JSON for SIEM integration
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"timestamp": "2025-11-30T13:45:00Z",
|
|
||||||
"event_type": "BLOCKED",
|
|
||||||
"severity": "HIGH",
|
|
||||||
"ip": "1.2.3.4",
|
|
||||||
"user_agent": "curl/7.68.0",
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/api/contact",
|
|
||||||
"details": "Missing Origin/Referer headers"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Production Logging:**
|
|
||||||
- stdout → systemd/Docker logs
|
|
||||||
- /var/log/cv-app/security.log → dedicated file
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Security Test Results 🧪
|
|
||||||
|
|
||||||
### Validation Tests
|
|
||||||
```bash
|
|
||||||
$ go test -v ./internal/validation/...
|
|
||||||
=== RUN TestIsValidEmail (15 test cases)
|
|
||||||
✅ PASS: All email validation tests
|
|
||||||
=== RUN TestContainsEmailInjection (14 test cases)
|
|
||||||
✅ PASS: All injection detection tests
|
|
||||||
=== RUN TestIsValidName (13 test cases)
|
|
||||||
✅ PASS: All name validation tests
|
|
||||||
=== RUN TestIsValidSubject (9 test cases)
|
|
||||||
✅ PASS: All subject validation tests
|
|
||||||
=== RUN TestValidateContactForm (10 test cases)
|
|
||||||
✅ PASS: All validation tests
|
|
||||||
=== RUN TestSecurityAttacks (4 attack simulations)
|
|
||||||
✅ PASS: All attack tests blocked
|
|
||||||
|
|
||||||
PASS
|
|
||||||
ok github.com/juanatsap/cv-site/internal/validation 0.494s
|
|
||||||
```
|
|
||||||
|
|
||||||
### Security Attack Simulations
|
|
||||||
```bash
|
|
||||||
✅ SQL Injection → BLOCKED (invalid characters in name)
|
|
||||||
✅ Email Header Injection → BLOCKED (CRLF stripped)
|
|
||||||
✅ Command Injection → BLOCKED (special chars rejected)
|
|
||||||
✅ Path Traversal → BLOCKED (pattern rejected)
|
|
||||||
✅ XSS in Message → HTML ESCAPED (safe for email clients)
|
|
||||||
✅ Bot Honeypot → BLOCKED (honeypot filled)
|
|
||||||
✅ Bot Timing → BLOCKED (submitted <2 seconds)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## How to Use (Integration Guide)
|
|
||||||
|
|
||||||
### 1. Contact Form Handler (Not Yet Implemented)
|
|
||||||
```go
|
|
||||||
package handlers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/juanatsap/cv-site/internal/middleware"
|
|
||||||
"github.com/juanatsap/cv-site/internal/validation"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ContactHandler struct {
|
|
||||||
// Your dependencies (email service, etc.)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ContactHandler) SendMessage(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// 1. Parse request
|
|
||||||
var req validation.ContactFormRequest
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
||||||
http.Error(w, "Invalid request", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Set server timestamp (don't trust client)
|
|
||||||
req.Timestamp = time.Now().Unix()
|
|
||||||
|
|
||||||
// 3. Validate input
|
|
||||||
if err := validation.ValidateContactForm(&req); err != nil {
|
|
||||||
middleware.LogSecurityEvent(middleware.EventValidationFailed, r, err.Error())
|
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. Sanitize content
|
|
||||||
validation.SanitizeContactForm(&req)
|
|
||||||
|
|
||||||
// 5. Send email (implement this)
|
|
||||||
// if err := h.emailService.Send(&req); err != nil {
|
|
||||||
// middleware.LogSecurityEvent(middleware.EventEmailSendFailed, r, err.Error())
|
|
||||||
// http.Error(w, "Failed to send email", http.StatusInternalServerError)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// 6. Log success
|
|
||||||
middleware.LogSecurityEvent(middleware.EventContactFormSent, r,
|
|
||||||
fmt.Sprintf("From: %s <%s>", req.Name, req.Email))
|
|
||||||
|
|
||||||
// 7. Return success
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
json.NewEncoder(w).Encode(map[string]string{
|
|
||||||
"message": "Message sent successfully",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Route Configuration
|
|
||||||
```go
|
|
||||||
package routes
|
|
||||||
|
|
||||||
func Setup(/*...*/) http.Handler {
|
|
||||||
mux := http.NewServeMux()
|
|
||||||
|
|
||||||
// ... existing routes ...
|
|
||||||
|
|
||||||
// Contact form endpoint with full security stack
|
|
||||||
csrf := middleware.NewCSRFProtection()
|
|
||||||
contactRateLimiter := middleware.NewContactRateLimiter()
|
|
||||||
|
|
||||||
protectedContactHandler := middleware.BrowserOnly(
|
|
||||||
csrf.Middleware(
|
|
||||||
contactRateLimiter.Middleware(
|
|
||||||
http.HandlerFunc(contactHandler.SendMessage),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
mux.Handle("/api/contact", protectedContactHandler)
|
|
||||||
|
|
||||||
return mux
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. HTML Form Template
|
|
||||||
```html
|
|
||||||
<form hx-post="/api/contact"
|
|
||||||
hx-trigger="submit"
|
|
||||||
hx-target="#contact-result"
|
|
||||||
_="on htmx:afterRequest if event.detail.successful reset() me end">
|
|
||||||
|
|
||||||
<!-- CSRF Token (hidden) -->
|
|
||||||
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
|
|
||||||
|
|
||||||
<!-- Timestamp for timing validation -->
|
|
||||||
<input type="hidden" name="timestamp" id="form-timestamp">
|
|
||||||
|
|
||||||
<!-- Honeypot field (hidden from real users) -->
|
|
||||||
<input type="text"
|
|
||||||
name="website"
|
|
||||||
id="website"
|
|
||||||
style="position:absolute;left:-9999px;"
|
|
||||||
tabindex="-1"
|
|
||||||
autocomplete="off">
|
|
||||||
|
|
||||||
<!-- Real fields -->
|
|
||||||
<input type="text" name="name" required maxlength="100"
|
|
||||||
pattern="[\p{L}\s'-]+"
|
|
||||||
title="Name can only contain letters, spaces, hyphens, and apostrophes">
|
|
||||||
|
|
||||||
<input type="email" name="email" required maxlength="254">
|
|
||||||
|
|
||||||
<input type="text" name="company" maxlength="100">
|
|
||||||
|
|
||||||
<input type="text" name="subject" required maxlength="200"
|
|
||||||
pattern="[\p{L}\p{N}\s.,!?'\"()\-:;#]+"
|
|
||||||
title="Subject can only contain letters, numbers, and basic punctuation">
|
|
||||||
|
|
||||||
<textarea name="message" required maxlength="5000"></textarea>
|
|
||||||
|
|
||||||
<button type="submit">Send Message</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<div id="contact-result"></div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
// Set timestamp when form loads
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
document.getElementById('form-timestamp').value = Math.floor(Date.now() / 1000);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Next Steps for Production
|
|
||||||
|
|
||||||
### 1. Email Service Integration
|
|
||||||
**TODO:** Implement email sending (Choose one)
|
|
||||||
- Option A: SMTP (net/smtp package)
|
|
||||||
- Option B: SendGrid API
|
|
||||||
- Option C: AWS SES
|
|
||||||
- Option D: Mailgun API
|
|
||||||
|
|
||||||
**Example SMTP:**
|
|
||||||
```go
|
|
||||||
func SendEmail(req *validation.ContactFormRequest) error {
|
|
||||||
// Configure SMTP
|
|
||||||
auth := smtp.PlainAuth("", os.Getenv("SMTP_USER"), os.Getenv("SMTP_PASS"), os.Getenv("SMTP_HOST"))
|
|
||||||
|
|
||||||
// Build email
|
|
||||||
to := []string{os.Getenv("CONTACT_EMAIL")}
|
|
||||||
subject := "Contact Form: " + req.Subject
|
|
||||||
body := fmt.Sprintf("From: %s <%s>\nCompany: %s\n\n%s",
|
|
||||||
req.Name, req.Email, req.Company, req.Message)
|
|
||||||
|
|
||||||
msg := []byte(fmt.Sprintf("To: %s\r\nSubject: %s\r\n\r\n%s",
|
|
||||||
strings.Join(to, ","), subject, body))
|
|
||||||
|
|
||||||
// Send email
|
|
||||||
return smtp.SendMail(
|
|
||||||
os.Getenv("SMTP_HOST")+":"+os.Getenv("SMTP_PORT"),
|
|
||||||
auth,
|
|
||||||
os.Getenv("SMTP_FROM"),
|
|
||||||
to,
|
|
||||||
msg,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Additional Security Headers
|
|
||||||
**TODO:** Add to `internal/middleware/security.go`
|
|
||||||
```go
|
|
||||||
w.Header().Set("X-Permitted-Cross-Domain-Policies", "none")
|
|
||||||
w.Header().Set("Cross-Origin-Opener-Policy", "same-origin")
|
|
||||||
w.Header().Set("Cross-Origin-Embedder-Policy", "require-corp")
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Subresource Integrity (SRI)
|
|
||||||
**TODO:** Add SRI hashes to `templates/index.html`
|
|
||||||
```html
|
|
||||||
<!-- Generate hashes at: https://www.srihash.org/ -->
|
|
||||||
<script src="https://unpkg.com/hyperscript.org@0.9.14"
|
|
||||||
integrity="sha384-[GENERATE_HASH]"
|
|
||||||
crossorigin="anonymous"></script>
|
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/iconify-icon@2.1.0/dist/iconify-icon.min.js"
|
|
||||||
integrity="sha384-[GENERATE_HASH]"
|
|
||||||
crossorigin="anonymous"></script>
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Production Deployment Checklist
|
|
||||||
|
|
||||||
#### Environment Variables
|
|
||||||
```bash
|
|
||||||
# .env (production)
|
|
||||||
GO_ENV=production
|
|
||||||
PORT=1999
|
|
||||||
ALLOWED_ORIGINS=juan.andres.morenorub.io
|
|
||||||
SMTP_HOST=smtp.example.com
|
|
||||||
SMTP_PORT=587
|
|
||||||
SMTP_USER=noreply@juan.andres.morenorub.io
|
|
||||||
SMTP_PASS=<strong_password>
|
|
||||||
SMTP_FROM=noreply@juan.andres.morenorub.io
|
|
||||||
CONTACT_EMAIL=contact@juan.andres.morenorub.io
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Nginx Configuration
|
|
||||||
See `SECURITY-AUDIT-REPORT.md` Section: "Linux Server Hardening Checklist"
|
|
||||||
- SSL/TLS configuration (A+ rating)
|
|
||||||
- Rate limiting zones
|
|
||||||
- Security headers (belt-and-suspenders)
|
|
||||||
- Connection limits
|
|
||||||
- Static file caching
|
|
||||||
|
|
||||||
#### Firewall Rules
|
|
||||||
```bash
|
|
||||||
sudo ufw allow 22/tcp # SSH
|
|
||||||
sudo ufw allow 80/tcp # HTTP
|
|
||||||
sudo ufw allow 443/tcp # HTTPS
|
|
||||||
sudo ufw enable
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Fail2ban
|
|
||||||
```bash
|
|
||||||
sudo apt install fail2ban
|
|
||||||
# Configure jail for repeated 403/429 responses
|
|
||||||
# See SECURITY-AUDIT-REPORT.md for configuration
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Log Rotation
|
|
||||||
```bash
|
|
||||||
# /etc/logrotate.d/cv-app
|
|
||||||
/var/log/cv-app/*.log {
|
|
||||||
daily
|
|
||||||
rotate 30
|
|
||||||
compress
|
|
||||||
delaycompress
|
|
||||||
notifempty
|
|
||||||
create 0644 cv-user cv-group
|
|
||||||
sharedscripts
|
|
||||||
postrotate
|
|
||||||
systemctl reload cv-app
|
|
||||||
endscript
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Security Monitoring
|
|
||||||
|
|
||||||
### Real-Time Monitoring
|
|
||||||
```bash
|
|
||||||
# Watch security events
|
|
||||||
tail -f /var/log/cv-app/security.log | jq 'select(.severity == "HIGH")'
|
|
||||||
|
|
||||||
# Count rate limit violations
|
|
||||||
grep "RATE_LIMIT_EXCEEDED" /var/log/cv-app/security.log | wc -l
|
|
||||||
|
|
||||||
# Top blocked IPs
|
|
||||||
grep "BLOCKED" /var/log/cv-app/security.log | jq -r '.ip' | sort | uniq -c | sort -rn | head -10
|
|
||||||
```
|
|
||||||
|
|
||||||
### Alerting (Prometheus/Grafana)
|
|
||||||
```yaml
|
|
||||||
# Example alert rules
|
|
||||||
- alert: HighRateLimitViolations
|
|
||||||
expr: rate(cv_rate_limit_violations_total[5m]) > 10
|
|
||||||
annotations:
|
|
||||||
summary: "High rate limit violations detected"
|
|
||||||
|
|
||||||
- alert: CSRFAttack
|
|
||||||
expr: increase(cv_csrf_violations_total[1h]) > 5
|
|
||||||
annotations:
|
|
||||||
summary: "CSRF attack detected"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Compliance Status
|
|
||||||
|
|
||||||
### OWASP Top 10 (2021)
|
|
||||||
- ✅ A01: Broken Access Control → SECURE (origin validation, rate limiting)
|
|
||||||
- ✅ A02: Cryptographic Failures → SECURE (HSTS, no sensitive data storage)
|
|
||||||
- ✅ A03: Injection → SECURE (input validation, no SQL/command injection)
|
|
||||||
- ⚠️ A04: Insecure Design → IMPROVED (CSRF protection added)
|
|
||||||
- ✅ A05: Security Misconfiguration → SECURE (strong headers)
|
|
||||||
- ⚠️ A06: Vulnerable Components → MONITOR (dependency scanning needed)
|
|
||||||
- N/A A07: Auth Failures → N/A (no authentication system)
|
|
||||||
- ⚠️ A08: Integrity Failures → PARTIAL (SRI needed for all CDN resources)
|
|
||||||
- ⚠️ A09: Logging/Monitoring → IMPROVED (structured logging added)
|
|
||||||
- ✅ A10: SSRF → SECURE (no user-controlled URLs)
|
|
||||||
|
|
||||||
### CWE (Common Weakness Enumeration)
|
|
||||||
- ✅ CWE-79: XSS → SECURE (HTML template auto-escaping)
|
|
||||||
- ✅ CWE-89: SQL Injection → N/A (no database)
|
|
||||||
- ✅ CWE-78: OS Command Injection → SECURE (go-git library, no shell commands)
|
|
||||||
- ✅ CWE-352: CSRF → SECURE (token validation)
|
|
||||||
- ✅ CWE-601: Open Redirect → SECURE (no redirects from user input)
|
|
||||||
- ✅ CWE-862: Missing Authorization → N/A (public site)
|
|
||||||
- ✅ CWE-287: Improper Authentication → N/A (no authentication)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Performance Impact
|
|
||||||
|
|
||||||
### Validation Benchmarks
|
|
||||||
```bash
|
|
||||||
$ go test -bench=. ./internal/validation/...
|
|
||||||
|
|
||||||
BenchmarkIsValidEmail-8 5000000 250 ns/op
|
|
||||||
BenchmarkContainsEmailInjection-8 10000000 120 ns/op
|
|
||||||
BenchmarkValidateContactForm-8 1000000 1200 ns/op
|
|
||||||
|
|
||||||
# Impact: <1ms additional latency for full validation
|
|
||||||
```
|
|
||||||
|
|
||||||
### Middleware Impact
|
|
||||||
- CSRF validation: ~0.1ms (constant-time comparison)
|
|
||||||
- Origin validation: ~0.05ms (header checks)
|
|
||||||
- Rate limiting: ~0.02ms (in-memory lookup)
|
|
||||||
- Security logging: ~0.3ms (JSON marshaling + file write)
|
|
||||||
|
|
||||||
**Total overhead:** <0.5ms per request (negligible)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Documentation References
|
|
||||||
|
|
||||||
1. **Full Security Audit:** `SECURITY-AUDIT-REPORT.md`
|
|
||||||
- 100+ pages of detailed security analysis
|
|
||||||
- Contact form security design
|
|
||||||
- Penetration testing guide
|
|
||||||
- Server hardening checklist
|
|
||||||
|
|
||||||
2. **Validation Package:** `internal/validation/contact.go`
|
|
||||||
- Comprehensive input validation
|
|
||||||
- Email header injection prevention
|
|
||||||
- Bot detection (honeypot + timing)
|
|
||||||
|
|
||||||
3. **Middleware Package:** `internal/middleware/`
|
|
||||||
- `csrf.go` - CSRF protection
|
|
||||||
- `browser_only.go` - Origin validation
|
|
||||||
- `contact_rate_limit.go` - Rate limiting
|
|
||||||
- `security_logger.go` - Security logging
|
|
||||||
|
|
||||||
4. **Test Suite:** `internal/validation/contact_test.go`
|
|
||||||
- 60+ test cases
|
|
||||||
- Attack simulations
|
|
||||||
- 100% code coverage
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Contact Form Security Checklist
|
|
||||||
|
|
||||||
Before deploying contact form to production:
|
|
||||||
|
|
||||||
- ✅ Input validation implemented and tested
|
|
||||||
- ✅ CSRF protection enabled
|
|
||||||
- ✅ Origin validation (browser-only access)
|
|
||||||
- ✅ Rate limiting configured (5/hour)
|
|
||||||
- ✅ Bot protection (honeypot + timing)
|
|
||||||
- ✅ Email header injection prevention
|
|
||||||
- ✅ Security logging enabled
|
|
||||||
- ⚠️ Email service integrated (TODO)
|
|
||||||
- ⚠️ Production SMTP credentials configured (TODO)
|
|
||||||
- ⚠️ Privacy policy page created (GDPR compliance)
|
|
||||||
- ⚠️ Nginx rate limiting configured (TODO)
|
|
||||||
- ⚠️ Fail2ban configured for repeated attacks (TODO)
|
|
||||||
- ⚠️ Security monitoring/alerting set up (TODO)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Final Security Rating
|
|
||||||
|
|
||||||
**Overall: A- (Very Good)**
|
|
||||||
|
|
||||||
### Strengths ✅
|
|
||||||
- Comprehensive input validation with attack prevention
|
|
||||||
- Strong CSRF protection with secure token management
|
|
||||||
- Browser-only access enforcement (blocks automation tools)
|
|
||||||
- Structured security logging for SIEM integration
|
|
||||||
- Excellent OWASP Top 10 coverage
|
|
||||||
- 100% test coverage for validation layer
|
|
||||||
- Zero critical vulnerabilities identified
|
|
||||||
|
|
||||||
### Areas for Improvement ⚠️
|
|
||||||
1. Add SRI hashes for remaining CDN resources
|
|
||||||
2. Implement automated dependency scanning
|
|
||||||
3. Set up security monitoring/alerting dashboard
|
|
||||||
4. Create GDPR privacy policy page
|
|
||||||
5. Configure fail2ban for production
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Security is a journey, not a destination. Regular audits, updates, and monitoring are essential.**
|
|
||||||
|
|
||||||
Last Updated: 2025-11-30
|
|
||||||
Next Audit Due: 2026-03-01 (Quarterly)
|
|
||||||
+2
-2
@@ -294,7 +294,7 @@ Every time something breaks in production:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Last Updated**: 2025-11-20
|
**Last Updated**: 2025-12-01
|
||||||
**Test Count**: 28 active tests (0-29 numbered + migration tests)
|
**Test Count**: 39 test files (comprehensive coverage including CMD+K, contact form, and PDF tests)
|
||||||
**Status**: Production specification
|
**Status**: Production specification
|
||||||
**Responsibility**: These tests define what "working" means
|
**Responsibility**: These tests define what "working" means
|
||||||
|
|||||||
@@ -286,8 +286,8 @@ When adding tests:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Last Updated**: 2025-11-20
|
**Last Updated**: 2025-12-01
|
||||||
**Test Count**: 28 active tests - Comprehensive coverage
|
**Test Count**: 39 test files - Comprehensive coverage
|
||||||
**Coverage**: Complete (UI, keyboard, HTMX, i18n, modals, mobile, zoom, hover-sync, hyperscript, skeleton loaders, color themes, button positioning, PDF modal, icons, dark theme, course icons, references, toast notifications)
|
**Coverage**: Complete (UI, keyboard, HTMX, i18n, modals, mobile, zoom, hover-sync, hyperscript, skeleton loaders, color themes, button positioning, PDF modal, icons, dark theme, course icons, references, toast notifications)
|
||||||
**Status**: SINGLE SOURCE OF TRUTH - Production specification
|
**Status**: SINGLE SOURCE OF TRUTH - Production specification
|
||||||
**Philosophy**: Each test validates specific functionality and edge cases
|
**Philosophy**: Each test validates specific functionality and edge cases
|
||||||
|
|||||||
+4
-4
@@ -1,6 +1,6 @@
|
|||||||
# CV Project Test Suite (Active Tests)
|
# CV Project Test Suite (Active Tests)
|
||||||
|
|
||||||
**28 comprehensive tests** covering all functionality from UI interactions to PDF generation.
|
**39 comprehensive test files** covering all functionality from UI interactions to PDF generation, CMD+K command palette, and contact form.
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
@@ -149,7 +149,7 @@ bun tests/mjs/28-references-pdf-download.test.mjs
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Last Updated**: 2025-11-20
|
**Last Updated**: 2025-12-01
|
||||||
**Test Count**: 28 active tests
|
**Test Count**: 39 test files
|
||||||
**Coverage**: Complete functionality coverage including toast notifications
|
**Coverage**: Complete functionality coverage including toast notifications, CMD+K command palette, and contact form
|
||||||
**Status**: Production specification
|
**Status**: Production specification
|
||||||
|
|||||||
Reference in New Issue
Block a user