Skip to main content

Frontend Next Steps

Frontend Implementation - Security Integration

Status

Backend: ✅ Complete Frontend: 🔄 Needs Integration

The backend security layers are fully implemented and ready. The frontend needs these updates to work with the new security system.

Required Frontend Changes

Protected Endpoints That Need CSRF Tokens

The following endpoints now require CSRF tokens:

  • Registration: /api/v1/auth/register (also has honeypot field)
  • Login: /api/v1/auth/login (also has rate limiting: 10/minute)
  • Lead Submission: /api/v1/leads (also has honeypot, rate limiting: 5/hour, email verification)

1. Fetch CSRF Tokens (15 minutes)

File: frontend/html/app.js

Add this code:

// Global CSRF token storage
window.csrfToken = null;

// Fetch CSRF token on page load
async function initializeForm() {
    try {
        const response = await fetch('/api/v1/csrf-token', {
            credentials: 'include'  // Important: include cookies
        });
        const data = await response.json();
        window.csrfToken = data.csrf_token;
        console.log('CSRF token loaded');
    } catch (error) {
        console.error('Failed to load CSRF token:', error);
    }
}

// Call on page load
window.addEventListener('DOMContentLoaded', initializeForm);

Note: This function should be called on ALL pages that have forms (registration, login, lead submission).

2. Include CSRF Token in ALL Form Submissions (20 minutes)

File: frontend/html/app.js

Update your registration, login, and lead submission functions:

Registration Function

async function registerBookkeeper(formData) {
    // Ensure CSRF token is loaded
    if (!window.csrfToken) {
        throw new Error('Security token not loaded. Please refresh the page.');
    }

    const response = await fetch('/api/v1/auth/register', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        credentials: 'include',  // Important: include cookies
        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,
            _csrf_token: window.csrfToken  // ← Add this line
            // Note: DO NOT include website_url (honeypot field)
        })
    });

    if (!response.ok) {
        const error = await response.json();
        throw new Error(error.detail || 'Registration failed');
    }

    return response.json();
}

Login Function

async function login(formData) {
    // Ensure CSRF token is loaded
    if (!window.csrfToken) {
        throw new Error('Security token not loaded. Please refresh the page.');
    }

    const response = await fetch('/api/v1/auth/login', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        credentials: 'include',  // Important: include cookies
        body: JSON.stringify({
            email: formData.email,
            password: formData.password,
            _csrf_token: window.csrfToken  // ← Add this line
        })
    });

    if (!response.ok) {
        const error = await response.json();
        throw new Error(error.detail || 'Login failed');
    }

    return response.json();
}

Lead Submission Function

async function submitLead(formData) {
    // Ensure CSRF token is loaded
    if (!window.csrfToken) {
        throw new Error('Security token not loaded. Please refresh the page.');
    }

    const response = await fetch('/api/v1/leads', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        credentials: 'include',  // Important: include cookies
        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: window.csrfToken  // ← Add this line
            // Note: DO NOT include website (honeypot field)
        })
    });

    if (!response.ok) {
        const error = await response.json();
        throw new Error(error.detail || 'Submission failed');
    }

    return response.json();
}

3. Add Honeypot Fields to Forms (10 minutes)

Two forms need honeypot fields:

Registration Form

File: frontend/html/bookkeeper-landing.html

Add this hidden field inside the registration <form> tag:

<!-- Honeypot field - hidden from users, catches bots -->
<input
    type="text"
    name="website_url"
    id="website_url"
    value=""
    style="position: absolute; left: -9999px; width: 1px; height: 1px;"
    tabindex="-1"
    autocomplete="off"
    aria-hidden="true"
>

Important: Make sure this field is NOT included in your registration JavaScript (don't read its value). The backend will reject registration if this field has any value.

Lead Submission Form

File: frontend/html/business-owner-form.html

Add this hidden field inside the lead <form> tag:

<!-- Honeypot field - hidden from users, catches bots -->
<input
    type="text"
    name="website"
    id="website"
    value=""
    style="position: absolute; left: -9999px; width: 1px; height: 1px;"
    tabindex="-1"
    autocomplete="off"
    aria-hidden="true"
>

Important: Make sure this field is NOT included in your lead submission JavaScript (don't read its value). The backend will reject the submission if this field has any value.

4. Create Email Verification Page (30 minutes)

File: frontend/html/verify-lead.html (NEW)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Verify Email - Bookkeeper Platform</title>
    <link rel="stylesheet" href="template/template.css">
    <link rel="stylesheet" href="app.css">
</head>
<body>
    <div class="container">
        <div class="card card-large">
            <h1>Email Verification</h1>
            <div id="verification-message">
                <p>Verifying your email address...</p>
            </div>
        </div>
    </div>

    <script>
        // Get token from URL parameter
        const urlParams = new URLSearchParams(window.location.search);
        const token = urlParams.get('token');

        if (!token) {
            document.getElementById('verification-message').innerHTML = `
                <p class="error">Invalid verification link.</p>
            `;
        } else {
            // Verify email
            fetch(\`/api/v1/leads/verify-email?token=\${token}\`)
                .then(response => {
                    if (!response.ok) {
                        throw new Error('Verification failed');
                    }
                    return response.json();
                })
                .then(data => {
                    document.getElementById('verification-message').innerHTML = `
                        <p class="success">✓ Email verified successfully!</p>
                        <p>Your lead for <strong>\${data.business_name}</strong> is now pending admin review.</p>
                        <p>We'll contact you shortly with next steps.</p>
                    `;
                })
                .catch(error => {
                    document.getElementById('verification-message').innerHTML = `
                        <p class="error">Verification failed. The link may be expired or invalid.</p>
                        <p>Please submit your lead again if needed.</p>
                    `;
                });
        }
    </script>
</body>
</html>

5. Update Form Submission Success Message (10 minutes)

File: frontend/html/app.js

Update the success message after lead submission to mention email verification:

// After successful lead submission
if (emailVerificationEnabled) {  // You can check with config or feature flag
    showSuccessMessage(`
        Thank you for submitting your lead!

        We've sent a verification email to ${email}.
        Please check your inbox and click the link to verify your email address.

        Your lead will be reviewed by our team after verification.
    `);
} else {
    showSuccessMessage(`
        Thank you for submitting your lead!

        Your request is now pending review by our team.
        We'll be in touch shortly!
    `);
}

Testing Your Implementation

Test 1: CSRF Token Works

  1. Open browser DevTools → Network tab
  2. Load the form page
  3. Check for request to /api/v1/csrf-token
  4. Verify Set-Cookie header with csrf_token
  5. Submit form
  6. Check request body includes _csrf_token

Test 2: Honeypot Catches Bots

  1. Open browser console
  2. Manually fill the honeypot field: document.getElementById('website').value = 'spam'
  3. Try to submit form
  4. Should get error: "Invalid submission"

Test 3: Rate Limiting

  1. Submit lead form 5 times quickly
  2. 6th submission should fail with "Rate limit exceeded"
  3. Wait 1 hour or test from different IP

Test 4: Email Verification (If Enabled)

  1. Submit lead with real email
  2. Check email for verification link
  3. Click link → should redirect to /verify-lead?token=...
  4. Verify page shows success message
  5. Check admin dashboard → lead now appears

Configuration Toggles

Disable CSRF Protection (Development Mode)

To test with Swagger UI or disable CSRF:

# backend/.env
REQUIRE_CSRF=false

Then restart backend:

cd backend
docker-compose restart backend

Use Cases:

  • Development/testing with Swagger UI at /docs
  • Direct API testing with Postman/curl
  • Should be set to true in production

Disable Email Verification (Testing)

To disable email verification for testing:

# backend/.env
REQUIRE_EMAIL_VERIFICATION=false

Then restart backend:

cd backend
docker-compose restart backend

Error Handling

Add proper error messages for security failures:

async function submitLead(formData) {
    try {
        const response = await fetch('/api/v1/leads', { /* ... */ });

        if (!response.ok) {
            const error = await response.json();

            // Handle specific security errors
            if (error.detail.includes('CSRF')) {
                alert('Security token expired. Please refresh the page and try again.');
                window.location.reload();
                return;
            }

            if (error.detail.includes('Rate limit')) {
                alert('Too many submissions. Please try again later.');
                return;
            }

            if (error.detail.includes('Invalid submission')) {
                console.error('Honeypot triggered - possible bot');
                return;  // Don't show error to bot
            }

            throw new Error(error.detail);
        }

        return response.json();
    } catch (error) {
        console.error('Submission error:', error);
        throw error;
    }
}

Implementation Checklist

CSRF Integration

  • Add CSRF token fetching on page load (all form pages)
  • Include _csrf_token in registration requests
  • Include _csrf_token in login requests
  • Include _csrf_token in lead submission requests

Honeypot Fields

  • Add website_url honeypot to registration form
  • Add website honeypot to lead submission form
  • Ensure honeypot fields are NOT included in JavaScript submission

Email Verification

  • Create email verification page (verify-lead.html)
  • Update success messages to mention email verification

Error Handling

  • Add error handling for CSRF failures
  • Add error handling for rate limiting
  • Add error handling for honeypot detection

Testing

  • Test CSRF protection works (with REQUIRE_CSRF=true)
  • Test Swagger UI works (with REQUIRE_CSRF=false)
  • Test honeypot catches bot submissions
  • Test rate limiting blocks excessive requests
  • Test email verification flow (if enabled)
  • Update user-facing documentation

Estimated Time

  • CSRF Integration (3 endpoints): 35 minutes
  • Honeypot Fields (2 forms): 10 minutes
  • Email Verification Page: 30 minutes
  • Error Handling: 15 minutes
  • Testing: 45 minutes

Total: ~2.5 hours

Need Help?

  • Backend security docs: backend/SECURITY.md
  • API documentation: http://localhost:8000/docs (when running)
  • Check browser console for CSRF token errors
  • Check backend logs for security rejections

Next Steps After Frontend

  1. n8n Workflow: Create email verification workflow
  2. Cron Job: Schedule cleanup tasks (app/tasks/cleanup.py)
  3. Monitoring: Track spam attempts and adjust rate limits
  4. Production: Set secure=True for CSRF cookies (HTTPS only)