Skip to main content

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_token from /api/v1/csrf-token
  • POST requests include _csrf_token in 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)

  1. frontend/html/bookkeeper-landing.html

    • Bookkeeper registration page
    • ~200 lines
    • Includes honeypot field
  2. frontend/html/business-owner-form.html

    • Lead submission form
    • ~180 lines
    • Includes honeypot field
  3. frontend/html/admin-login.html

    • Admin authentication
    • ~100 lines
    • Minimal layout
  4. frontend/html/verify-lead.html

    • Email verification page
    • ~80 lines
    • JavaScript-based verification

Modified Files (2 files)

  1. 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
  2. frontend/html/app.css

    • Form-specific styles
    • Success/error message styles
    • Honeypot field styles
    • ~50 lines added

Implementation Order

  1. Create HTML pages first (all 4 new files)

    • Easier to test structure/layout
    • Can use placeholder forms initially
    • Verify template system works correctly
  2. Update app.js with security features

    • Add CSRF initialization
    • Add API helper
    • Add form handlers
    • Add error handling
  3. Wire up event listeners

    • Connect forms to handlers
    • Test each form individually
  4. Add styling to app.css

    • Polish form appearance
    • Ensure responsive design
  5. 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:

  1. n8n Workflows - Configure email sending workflows
  2. Bookkeeper Dashboard - Create lead browsing/purchase page
  3. Admin Dashboard - Create lead approval interface
  4. Production Deployment - Deploy frontend to CDN/VPS
  5. SSL/HTTPS - Enable secure cookies (CSRF_SECURE=true)
  6. Monitoring - Track form submissions, errors, bot attempts

Questions to Resolve

Before implementation, please confirm:

  1. 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-status on page load.

  2. 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)
  3. Form Validation: Should we use:

    • HTML5 validation only (browser default)
    • Custom JavaScript validation (more control)
    • Both (HTML5 + enhanced JS validation)
  4. Success Redirects: After registration/login, where should users go?

    • Registration → /bookkeeper-dashboard.html
    • Bookkeeper Login → /bookkeeper-dashboard.html
    • Admin Login → /admin-dashboard.html
  5. 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
  6. Password Requirements: Should we display password requirements on the form?

    • If yes, what are the requirements? (min length, special chars, etc.)
  7. Optional Fields: Should optional fields be marked visually?

    • Example: "Service Area (optional)"
    • Or assume users understand unlabeled fields are optional

Next Steps

  1. Review this plan - Identify any issues or changes needed
  2. Answer questions above - Clarify any uncertainties
  3. Approve or modify plan - Make changes before implementation
  4. Begin implementation - Execute in phases
  5. Test thoroughly - Follow testing checklist
  6. Document issues - Track any problems found

Ready to proceed when you approve!