Skip to main content

Functional Logic

The security review proves the system can’t be exploited. This section proves it works correctly.

Form Submission Flow

The most important code path. Everything flows through submitFormResponse():
Steps 1–8 are synchronous — the submission must save before the user gets a response. Step 9 (hooks) is non-blocking. If Airtable is down or a webhook fails, the submission is already safe in the database.
Source: src/app/actions.ts, submitFormResponse() (~line 2512) Tests: 3 cases in form-renderer.test.tsx (rendering), integration coverage across encryption, rate limiting, and queue tests.
Try to break it:
  • Submit to a closed/unpublished form — should return a clear error, not a 500
  • Submit 31 times in a minute — rate limiting should kick in
  • Disable a webhook endpoint, submit a form — submission should still save

Notifications

Email (Resend) and SMS (Twilio) notifications fire on form events. Admin-only test sends verify provider configuration.
Tests: 37+ cases across 3 files
  • Event retrieval with filtering by form, channel, status - Pagination returns distinct results - Stats math: sent count = (sent + delivered), excludes failed/pending - Error categorization: invalid address, bounced, carrier rejected, rate limited, provider error - Admin-only enforcement on test email/SMS - Missing provider config caught (RESEND_API_KEY, Twilio credentials) - Per-form notification settings: auth enforced, insert vs update path, null handling

Async Queue

Post-submission hooks use an async queue with exponential backoff:
Queue operations are resilient — errors return null instead of throwing:
Tests: 20+ cases in src/lib/__tests__/async-queue.test.ts
  • Correct RPC payload structure for enqueue - Webhook gets 5 retries, others get 3 - Queue errors return null (no crash) - Database timeout and connection reset handled gracefully - Complex nested payloads preserved (unicode, special chars, nested objects) - Backoff formula verified: 2^(n-1) * 30s

Airtable Sync

Bidirectional sync with loop prevention. System fields use _ prefix convention:
Safety constraint: the Airtable client exposes create, update, and upsertno delete operation. Tests: 15 cases in airtable-status-sync.test.ts
  • Missing/disabled connections skip gracefully - Unmapped status fields skip (no crash) - Only valid statuses accepted (submitted, pending, processing, approved, rejected, withdrawn) - _submission_id mapping required to find Airtable record - No delete operation exposed — records preserved on withdrawal - Sync loop prevention: airtable_sync source blocks reverse trigger - Change source tracked for audit

Import Security

Forms can be imported from PDFs, images, and HTML. The import pipeline prevents user impersonation:
Tests: 10 cases in import-security.test.ts
  • Job ID contains authenticated user’s ID, not attacker’s - Unauthenticated users rejected - userId parameter in request body ignored - Separate jobs for different users (no cross-contamination) - Max 8 images, max 10MB PDF, only PDF file type accepted

Test Coverage

Full inventory of every test

Security

Auth, RBAC, input validation