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:
|
||||
- ✅ 1 def statement works
|
||||
- ✅ 2 def statements work
|
||||
- ✅ 3 def statements work
|
||||
- ✅ 4 def statements work (beyond historical limit)
|
||||
- ✅ 5 def statements work (well beyond limit)
|
||||
- ✅ 5+ def statements work (no limit)
|
||||
|
||||
**Historical Context:**
|
||||
- Hyperscript 0.9.12 had a hard 3 def limit
|
||||
- Hyperscript 0.9.14+ removed this limitation
|
||||
- Functions were moved to JavaScript as workaround
|
||||
- **NOW MIGRATED BACK** to hyperscript with JavaScript wrappers (2025-11-17)
|
||||
**Historical Context (for reference only):**
|
||||
- Hyperscript 0.9.12 had a hard 3 def limit (fixed in 0.9.14)
|
||||
- Current version has NO def statement limit
|
||||
- Functions can be freely organized across multiple files
|
||||
|
||||
**Current Architecture (2025-11-17):**
|
||||
- Core logic in hyperscript (`static/hyperscript/*.hs`)
|
||||
@@ -581,9 +580,9 @@ document.addEventListener('keydown', (e) => {
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** 2025-11-17
|
||||
**Project Status:** Production - Migrating to hyperscript architecture
|
||||
**Test Coverage:** 10 systematic tests, 100% core features + def limit verification
|
||||
**Last Updated:** 2025-12-01
|
||||
**Project Status:** Production - Full feature set including CMD+K command palette and contact form
|
||||
**Test Coverage:** 39 test files, 100% core features + CMD+K, contact form, PDF generation
|
||||
**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
|
||||
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)
|
||||
|
||||
---
|
||||
|
||||
### 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 | `/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 | `/cv-jamr-{year}-{lang}.pdf` | Shortcut PDF download routes | ❌ No | Redirect to /export/pdf |
|
||||
| GET | `/health` | Health check | ❌ No | None |
|
||||
| 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.
|
||||
|
||||
#### 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 |
|
||||
|-----------|------|----------|---------|-------------|
|
||||
@@ -2051,6 +2245,6 @@ go tool trace trace.out
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** November 12, 2025
|
||||
**API Version:** 1.1.0
|
||||
**Documentation Version:** 1.1.0
|
||||
**Last Updated:** December 1, 2025
|
||||
**API Version:** 1.2.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 ⭐
|
||||
- [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 ⭐
|
||||
- [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**
|
||||
- [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 |
|
||||
| 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 |
|
||||
| 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
|
||||
|
||||
@@ -141,6 +152,6 @@ All documentation in this project follows these standards:
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: 2025-11-20
|
||||
**Documentation Status**: ✅ Clean, organized, zero redundancy
|
||||
**Total Active Docs**: 14 core documents + archive
|
||||
**Last Updated**: 2025-12-01
|
||||
**Documentation Status**: ✅ Clean, organized, single doc/ folder
|
||||
**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
|
||||
**Test Count**: 28 active tests (0-29 numbered + migration tests)
|
||||
**Last Updated**: 2025-12-01
|
||||
**Test Count**: 39 test files (comprehensive coverage including CMD+K, contact form, and PDF tests)
|
||||
**Status**: Production specification
|
||||
**Responsibility**: These tests define what "working" means
|
||||
|
||||
@@ -286,8 +286,8 @@ When adding tests:
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: 2025-11-20
|
||||
**Test Count**: 28 active tests - Comprehensive coverage
|
||||
**Last Updated**: 2025-12-01
|
||||
**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)
|
||||
**Status**: SINGLE SOURCE OF TRUTH - Production specification
|
||||
**Philosophy**: Each test validates specific functionality and edge cases
|
||||
|
||||
+4
-4
@@ -1,6 +1,6 @@
|
||||
# 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
|
||||
|
||||
@@ -149,7 +149,7 @@ bun tests/mjs/28-references-pdf-download.test.mjs
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: 2025-11-20
|
||||
**Test Count**: 28 active tests
|
||||
**Coverage**: Complete functionality coverage including toast notifications
|
||||
**Last Updated**: 2025-12-01
|
||||
**Test Count**: 39 test files
|
||||
**Coverage**: Complete functionality coverage including toast notifications, CMD+K command palette, and contact form
|
||||
**Status**: Production specification
|
||||
|
||||
Reference in New Issue
Block a user