Implementation Plan
Frontend Security Integration - Implementation Plan
Overview
This document outlines the complete plan for integrating security features (CSRF protection, honeypot fields, email verification) into the frontend using the existing web app template system.
Based on: FRONTEND_NEXT_STEPS.md
Estimated Total Time: ~2.5 hours
Target: MVP-ready frontend with full security integration
Current State Analysis
✅ What We Have
- Modular web app template system (
template/template.css,template/template.js) - Base application structure (
app.js,app.css) - Example pages (
index.html,dashboard.html) - CSS variable system for theming
- Dark/light mode support
- Responsive design built-in
❌ What's Missing
- Bookkeeper landing/registration page
- Business owner lead submission form
- Admin login page
- Email verification page
- CSRF token integration
- Honeypot field implementation
- Security-aware error handling
- Form submission logic
Implementation Phases
Phase 1: Create Missing HTML Pages (30 minutes)
1.1 Bookkeeper Landing Page
File: frontend/html/bookkeeper-landing.html
Purpose: Bookkeeper registration and founding member signup
Features:
- Uses web app template (header + footer, no sidebars)
- Registration form with fields:
- Email (required)
- Password (required)
- Business name (required)
- Phone (optional)
- Website (optional)
- Service area (optional)
- Certifications (optional)
- Years of experience (optional)
- Specializations (optional)
- Honeypot field:
website_url(hidden, auto-fails if filled) - Founding member badge/banner (shows spots remaining)
- Link to login page for existing users
- Success message redirects to dashboard
Template Configuration:
data-header="true"
data-nav="false"
data-left-sidebar="false"
data-right-sidebar="false"
data-footer="true"
data-app-title="Bookkeeper Registration"
data-app-icon="fa-user-plus"
1.2 Business Owner Lead Form
File: frontend/html/business-owner-form.html
Purpose: Business owners submit leads for bookkeeping services
Features:
- Clean, conversion-focused layout
- Lead submission form with fields:
- Business name (required)
- Contact name (required)
- Email (required)
- Phone (required)
- Business type (select: sole_proprietor, llc, corporation, etc.)
- Industry (optional)
- Monthly revenue range (select)
- Current bookkeeping situation (select)
- Urgency (select: asap, within_month, within_quarter, exploring)
- Honeypot field:
website(hidden, auto-fails if filled) - Success message mentions email verification
- Rate limiting: 5 submissions per hour per IP
Template Configuration:
data-header="true"
data-nav="false"
data-left-sidebar="false"
data-right-sidebar="false"
data-footer="true"
data-app-title="Get Matched with a Bookkeeper"
data-app-icon="fa-handshake"
1.3 Admin Login Page
File: frontend/html/admin-login.html
Purpose: Admin authentication
Features:
- Minimal layout (header only)
- Simple login form:
- Email (required)
- Password (required)
- Rate limiting: 10 attempts per minute
- Redirects to admin dashboard on success
- Error message for invalid credentials
Template Configuration:
data-header="true"
data-nav="false"
data-left-sidebar="false"
data-right-sidebar="false"
data-footer="false"
data-app-title="Admin Login"
data-app-icon="fa-lock"
1.4 Email Verification Page
File: frontend/html/verify-lead.html
Purpose: Email verification for lead submissions
Features:
- Minimal layout (just header + card)
- Extracts token from URL:
/verify-lead?token={token} - Calls
/api/v1/leads/verify-email?token={token} - Shows verification status:
- ✅ Success: "Email verified! Your lead is now pending review."
- ❌ Failure: "Invalid or expired link. Please submit again."
- No form, just status display
Template Configuration:
data-header="true"
data-nav="false"
data-left-sidebar="false"
data-right-sidebar="false"
data-footer="false"
data-app-title="Email Verification"
data-app-icon="fa-envelope-circle-check"
Phase 2: Core Security Integration (45 minutes)
2.1 CSRF Token Management
File: frontend/html/app.js
Add to Configuration Section:
const CONFIG = {
appName: 'Bookkeeper Platform',
version: '1.0.0',
apiBase: '/api/v1',
csrfToken: null // Global CSRF token storage
};
Add CSRF Initialization Function:
/**
* Initialize CSRF token from backend
* Must be called on page load for all pages with forms
*/
async function initializeCSRF() {
try {
const response = await fetch('/api/v1/csrf-token', {
credentials: 'include' // Important: send/receive cookies
});
if (!response.ok) {
throw new Error('Failed to fetch CSRF token');
}
const data = await response.json();
CONFIG.csrfToken = data.csrf_token;
console.log('✓ CSRF token initialized');
return true;
} catch (error) {
console.error('✗ CSRF initialization failed:', error);
return false;
}
}
Update initApp() to call CSRF initialization:
async function initApp() {
console.log('Initializing app...');
// Initialize CSRF token for form pages
const pageNeedsCSRF = document.querySelector('form');
if (pageNeedsCSRF) {
await initializeCSRF();
}
setupEventListeners();
appState.initialized = true;
console.log('App initialized successfully');
}
2.2 API Helper Function
Add to Utility Functions Section:
/**
* Make API calls with automatic CSRF token injection
* @param {string} endpoint - API endpoint (e.g., '/auth/login')
* @param {object} options - Fetch options
* @returns {Promise<Response>} - Fetch response
*/
async function apiCall(endpoint, options = {}) {
// Get JWT token from localStorage if exists
const jwtToken = localStorage.getItem('auth_token');
// Build headers
const headers = {
'Content-Type': 'application/json',
...(jwtToken && { 'Authorization': `Bearer ${jwtToken}` }),
...options.headers
};
// Add CSRF token to POST requests
if (options.method === 'POST' && CONFIG.csrfToken && options.body) {
const body = JSON.parse(options.body);
body._csrf_token = CONFIG.csrfToken;
options.body = JSON.stringify(body);
}
// Make the request
const response = await fetch(`${CONFIG.apiBase}${endpoint}`, {
...options,
headers,
credentials: 'include' // Important: send cookies
});
return response;
}
2.3 Form Handler Functions
Bookkeeper Registration:
/**
* Register a new bookkeeper
* @param {object} formData - Registration form data
* @returns {Promise<object>} - User data with token
*/
async function registerBookkeeper(formData) {
// Ensure CSRF token is loaded
if (!CONFIG.csrfToken) {
throw new Error('Security token not loaded. Please refresh the page.');
}
// Check honeypot field (should be empty)
const honeypot = document.getElementById('website_url');
if (honeypot && honeypot.value !== '') {
console.error('Honeypot triggered - bot detected');
throw new Error('Invalid submission');
}
const response = await apiCall('/auth/register', {
method: 'POST',
body: JSON.stringify({
email: formData.email,
password: formData.password,
business_name: formData.business_name,
phone: formData.phone,
website: formData.website,
service_area: formData.service_area,
certifications: formData.certifications,
years_experience: formData.years_experience,
specializations: formData.specializations
// Note: CSRF token added automatically by apiCall()
// Note: DO NOT include website_url (honeypot)
})
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || 'Registration failed');
}
const data = await response.json();
// Store JWT token
localStorage.setItem('auth_token', data.access_token);
return data;
}
Login:
/**
* Login bookkeeper or admin
* @param {object} formData - Login credentials
* @returns {Promise<object>} - User data with token
*/
async function login(formData) {
// Ensure CSRF token is loaded
if (!CONFIG.csrfToken) {
throw new Error('Security token not loaded. Please refresh the page.');
}
const response = await apiCall('/auth/login', {
method: 'POST',
body: JSON.stringify({
email: formData.email,
password: formData.password
// CSRF token added automatically
})
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || 'Login failed');
}
const data = await response.json();
// Store JWT token
localStorage.setItem('auth_token', data.access_token);
return data;
}
Lead Submission:
/**
* Submit a business owner lead
* @param {object} formData - Lead form data
* @returns {Promise<object>} - Lead data
*/
async function submitLead(formData) {
// Ensure CSRF token is loaded
if (!CONFIG.csrfToken) {
throw new Error('Security token not loaded. Please refresh the page.');
}
// Check honeypot field (should be empty)
const honeypot = document.getElementById('website');
if (honeypot && honeypot.value !== '') {
console.error('Honeypot triggered - bot detected');
throw new Error('Invalid submission');
}
const response = await apiCall('/leads', {
method: 'POST',
body: JSON.stringify({
business_name: formData.business_name,
contact_name: formData.contact_name,
email: formData.email,
phone: formData.phone,
business_type: formData.business_type,
industry: formData.industry,
monthly_revenue_range: formData.monthly_revenue_range,
current_bookkeeping_situation: formData.current_bookkeeping_situation,
urgency: formData.urgency
// CSRF token added automatically
// DO NOT include website (honeypot)
})
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || 'Submission failed');
}
return response.json();
}
Phase 3: Error Handling & User Feedback (30 minutes)
3.1 Comprehensive Error Handler
Add to Utility Functions:
/**
* Handle API errors with user-friendly messages
* @param {Error} error - Error object
* @param {string} context - Context (e.g., 'registration', 'login')
*/
function handleAPIError(error, context = 'request') {
console.error(`${context} error:`, error);
const message = error.message || 'An error occurred';
// CSRF token expired - reload page
if (message.includes('CSRF') || message.includes('csrf')) {
alert('Security token expired. The page will reload.');
window.location.reload();
return;
}
// Rate limiting
if (message.includes('Rate limit') || message.includes('rate limit')) {
alert('Too many requests. Please try again later.');
return;
}
// Honeypot triggered (bot detected) - fail silently
if (message.includes('Invalid submission')) {
console.error('Bot detected - honeypot triggered');
// Don't show error to bots
return;
}
// Email verification required
if (message.includes('verify') || message.includes('verification')) {
alert(`Please check your email to verify your ${context}.`);
return;
}
// Generic error
alert(`${context} failed: ${message}`);
}
3.2 Success Message Handler
Add to UI Update Functions:
/**
* Show success message to user
* @param {string} message - Success message
* @param {boolean} autoRedirect - Redirect after showing message
* @param {string} redirectUrl - URL to redirect to
*/
function showSuccess(message, autoRedirect = false, redirectUrl = null) {
// Use template loading utility for consistent UI
const container = document.querySelector('main') || document.body;
// Create success message element
const successDiv = document.createElement('div');
successDiv.className = 'card';
successDiv.style.marginTop = 'var(--spacing-lg)';
successDiv.style.padding = 'var(--spacing-lg)';
successDiv.style.background = 'var(--status-online)';
successDiv.style.color = 'white';
successDiv.innerHTML = `
<h3>✓ Success!</h3>
<p>${message}</p>
`;
container.appendChild(successDiv);
// Auto-redirect if specified
if (autoRedirect && redirectUrl) {
setTimeout(() => {
window.location.href = redirectUrl;
}, 2000);
}
}
3.3 Event Handlers with Error Handling
Registration Form Handler:
/**
* Handle registration form submission
* @param {Event} event - Form submit event
*/
async function handleRegistrationSubmit(event) {
event.preventDefault();
// Show loading state
TemplateLoading.show('registration-form', 'Creating your account...');
try {
// Collect form data
const formData = {
email: document.getElementById('email').value,
password: document.getElementById('password').value,
business_name: document.getElementById('business_name').value,
phone: document.getElementById('phone')?.value || null,
website: document.getElementById('website')?.value || null,
service_area: document.getElementById('service_area')?.value || null,
certifications: document.getElementById('certifications')?.value || null,
years_experience: parseInt(document.getElementById('years_experience')?.value) || null,
specializations: document.getElementById('specializations')?.value || null
};
// Register user
const result = await registerBookkeeper(formData);
// Hide loading
TemplateLoading.hide('registration-form');
// Show success and redirect
showSuccess(
`Welcome, ${result.business_name}! Redirecting to your dashboard...`,
true,
'/bookkeeper-dashboard.html'
);
} catch (error) {
TemplateLoading.hide('registration-form');
handleAPIError(error, 'Registration');
}
}
Login Form Handler:
/**
* Handle login form submission
* @param {Event} event - Form submit event
*/
async function handleLoginSubmit(event) {
event.preventDefault();
TemplateLoading.show('login-form', 'Logging in...');
try {
const formData = {
email: document.getElementById('email').value,
password: document.getElementById('password').value
};
const result = await login(formData);
TemplateLoading.hide('login-form');
// Redirect based on role
const redirectUrl = result.role === 'admin'
? '/admin-dashboard.html'
: '/bookkeeper-dashboard.html';
showSuccess(
`Welcome back, ${result.business_name || result.email}!`,
true,
redirectUrl
);
} catch (error) {
TemplateLoading.hide('login-form');
handleAPIError(error, 'Login');
}
}
Lead Submission Handler:
/**
* Handle lead submission form
* @param {Event} event - Form submit event
*/
async function handleLeadSubmit(event) {
event.preventDefault();
TemplateLoading.show('lead-form', 'Submitting your request...');
try {
const formData = {
business_name: document.getElementById('business_name').value,
contact_name: document.getElementById('contact_name').value,
email: document.getElementById('email').value,
phone: document.getElementById('phone').value,
business_type: document.getElementById('business_type').value,
industry: document.getElementById('industry')?.value || null,
monthly_revenue_range: document.getElementById('monthly_revenue_range').value,
current_bookkeeping_situation: document.getElementById('current_bookkeeping_situation').value,
urgency: document.getElementById('urgency').value
};
const result = await submitLead(formData);
TemplateLoading.hide('lead-form');
// Check if email verification is required
const emailVerificationEnabled = result.status === 'pending_verification';
if (emailVerificationEnabled) {
showSuccess(
`Thank you for your submission! We've sent a verification email to ${formData.email}.
Please check your inbox and click the link to verify your email address.`
);
} else {
showSuccess(
`Thank you for your submission! Your request is now pending review.
We'll be in touch shortly!`
);
}
// Reset form
event.target.reset();
} catch (error) {
TemplateLoading.hide('lead-form');
handleAPIError(error, 'Lead submission');
}
}
Phase 4: Testing & Documentation (45 minutes)
4.1 Manual Testing Checklist
CSRF Protection:
- CSRF token loads on all form pages (check console log)
- Network tab shows
Set-Cookie: csrf_tokenfrom/api/v1/csrf-token - POST requests include
_csrf_tokenin body - Forms fail without CSRF token (test by commenting out initialization)
- Page reloads when CSRF expires
Honeypot Fields:
- Honeypot fields are invisible to users
- Honeypot fields have
tabindex="-1"(keyboard can't reach) - Filling honeypot via console triggers silent failure
- Regular submissions work when honeypot is empty
Rate Limiting:
- Lead form: 5 submissions per hour limit enforced
- Login: 10 attempts per minute limit enforced
- Error message is user-friendly
- Counter resets after time window
Email Verification:
- Lead submission shows email verification message
- Verification email arrives (check spam folder)
- Clicking link loads
/verify-lead?token=... - Success message shows on valid token
- Error message shows on invalid/expired token
General Functionality:
- Registration creates account and redirects to dashboard
- Login works for bookkeepers and admins
- Login redirects based on role (admin vs bookkeeper)
- Form validation works (HTML5 + custom)
- Error messages are clear and helpful
- Loading states show during requests
- Forms work in light and dark themes
- Forms are responsive on mobile
4.2 Browser DevTools Testing
Network Tab:
1. Load bookkeeper-landing.html
2. Check for GET /api/v1/csrf-token → 200 OK
3. Check response has {"csrf_token": "..."}
4. Check Set-Cookie header includes csrf_token
5. Fill out registration form
6. Submit form
7. Check POST /api/v1/auth/register → 200 OK
8. Check request body includes "_csrf_token"
9. Check Cookie header includes csrf_token
Console Tab:
1. Should see "✓ CSRF token initialized"
2. Should NOT see any errors
3. Fill honeypot: document.getElementById('website_url').value = 'bot'
4. Submit form
5. Should see "Bot detected - honeypot triggered"
Application Tab:
1. Cookies → localhost
2. Should see csrf_token cookie
3. Should see HttpOnly flag
4. Local Storage → localhost
5. After login, should see auth_token
4.3 Configuration Testing
Disable CSRF (Development Mode):
# backend/.env
REQUIRE_CSRF=false
# Restart backend
cd backend && docker-compose restart backend
Test that Swagger UI at /docs now works for manual API testing.
Disable Email Verification:
# backend/.env
REQUIRE_EMAIL_VERIFICATION=false
# Restart backend
cd backend && docker-compose restart backend
Test that leads are immediately approved without email verification step.
File Changes Summary
New Files Created (4 files)
-
frontend/html/bookkeeper-landing.html- Bookkeeper registration page
- ~200 lines
- Includes honeypot field
-
frontend/html/business-owner-form.html- Lead submission form
- ~180 lines
- Includes honeypot field
-
frontend/html/admin-login.html- Admin authentication
- ~100 lines
- Minimal layout
-
frontend/html/verify-lead.html- Email verification page
- ~80 lines
- JavaScript-based verification
Modified Files (2 files)
-
frontend/html/app.js- Add CSRF initialization
- Add API helper function
- Add form handlers (register, login, submitLead)
- Add error handling
- Add success messages
- Add event listeners
- ~400 lines added
-
frontend/html/app.css- Form-specific styles
- Success/error message styles
- Honeypot field styles
- ~50 lines added
Implementation Order
Recommended Sequence
-
Create HTML pages first (all 4 new files)
- Easier to test structure/layout
- Can use placeholder forms initially
- Verify template system works correctly
-
Update app.js with security features
- Add CSRF initialization
- Add API helper
- Add form handlers
- Add error handling
-
Wire up event listeners
- Connect forms to handlers
- Test each form individually
-
Add styling to app.css
- Polish form appearance
- Ensure responsive design
-
Test end-to-end
- Follow testing checklist
- Fix any issues found
- Document any gotchas
Backend Configuration Requirements
Environment Variables
The frontend assumes these backend settings:
# Required for CSRF
REQUIRE_CSRF=true
CSRF_TOKEN_EXPIRE_MINUTES=60
# Optional for email verification
REQUIRE_EMAIL_VERIFICATION=true
EMAIL_VERIFICATION_TOKEN_EXPIRE_HOURS=24
# CORS must allow frontend origin
CORS_ORIGINS=["http://localhost:8080"]
FRONTEND_URL=http://localhost:8080
# n8n webhooks (for emails)
N8N_BASE_URL=http://localhost:5678
N8N_WEBHOOK_LEAD_QUALIFICATION=/webhook/lead-qualification
N8N_WEBHOOK_LEAD_PURCHASED=/webhook/lead-purchased
N8N_WEBHOOK_BOOKKEEPER_WELCOME=/webhook/bookkeeper-welcome
API Endpoints Used
The frontend will call these endpoints:
GET /api/v1/csrf-token ← Fetch CSRF token
POST /api/v1/auth/register ← Bookkeeper registration
POST /api/v1/auth/login ← Login (bookkeeper/admin)
POST /api/v1/leads ← Submit lead
GET /api/v1/leads/verify-email?token=... ← Verify email
GET /api/v1/auth/founding-member-status ← Check spots remaining
Success Criteria
MVP is Complete When:
✅ All 4 new HTML pages exist and use template system correctly ✅ CSRF tokens load on all form pages ✅ Registration works and creates bookkeeper accounts ✅ Login works and redirects based on role ✅ Lead submission works and shows email verification message ✅ Honeypot fields block bot submissions silently ✅ Rate limiting blocks excessive requests ✅ Email verification flow works end-to-end ✅ Error messages are user-friendly ✅ All forms work on mobile ✅ All forms work in light/dark themes ✅ No console errors in browser DevTools
Post-Implementation Tasks
After frontend is complete:
- n8n Workflows - Configure email sending workflows
- Bookkeeper Dashboard - Create lead browsing/purchase page
- Admin Dashboard - Create lead approval interface
- Production Deployment - Deploy frontend to CDN/VPS
- SSL/HTTPS - Enable secure cookies (
CSRF_SECURE=true) - Monitoring - Track form submissions, errors, bot attempts
Questions to Resolve
Before implementation, please confirm:
-
Founding Member Display: Should the registration page show "X of 50 spots remaining" banner? If so, we need to call
/api/v1/auth/founding-member-statuson page load. -
Login Page Location: Should there be separate login pages for:
- Bookkeepers:
/bookkeeper-login.html - Admins:
/admin-login.html - Or one unified login:
/login.html(auto-redirects based on role)
- Bookkeepers:
-
Form Validation: Should we use:
- HTML5 validation only (browser default)
- Custom JavaScript validation (more control)
- Both (HTML5 + enhanced JS validation)
-
Success Redirects: After registration/login, where should users go?
- Registration →
/bookkeeper-dashboard.html - Bookkeeper Login →
/bookkeeper-dashboard.html - Admin Login →
/admin-dashboard.html
- Registration →
-
Lead Submission Success: After submitting lead, should we:
- Show success message and keep form visible
- Hide form and only show success message
- Redirect to a "thank you" page
-
Password Requirements: Should we display password requirements on the form?
- If yes, what are the requirements? (min length, special chars, etc.)
-
Optional Fields: Should optional fields be marked visually?
- Example: "Service Area (optional)"
- Or assume users understand unlabeled fields are optional
Next Steps
- Review this plan - Identify any issues or changes needed
- Answer questions above - Clarify any uncertainties
- Approve or modify plan - Make changes before implementation
- Begin implementation - Execute in phases
- Test thoroughly - Follow testing checklist
- Document issues - Track any problems found
Ready to proceed when you approve!