Adds /de/audit and /en/audit with a lead-magnet page (WCAG audit for free, PDF report + 15min walkthrough) and a request form (name, company, email, URL, agency/client, motivation), including the mandatory § 14 BGB business-only notice and confirmation checkbox. Also, since the site turned out to run exclusively on the Bitpalast static host rather than Vercel: - remove vercel.json, port its security headers, agent-discovery Link header and .well-known CORS/cache headers into public/.htaccess - fix the .htaccess rewrite so prerendered routes are served instead of falling back to the empty SPA shell - self-host Comfortaa/Quicksand via Fontsource instead of Google Fonts - add a separate backend/ Express service for the audit endpoint, meant to run on the IONOS VPS independently of the static site - update the privacy policy to reflect the form and font hosting Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| deploy | ||
| routes | ||
| services | ||
| .env.example | ||
| README.md | ||
| package.json | ||
| server.js | ||
README.md
Audit request endpoint
Small Express service that receives the form on /de/audit and forwards it by
email. It is deployed separately from the static site: the website is built
with Vite and uploaded by FTP, this service runs on the VPS.
Its dependencies are intentionally kept out of the frontend package.json so
they never end up in the browser bundle.
Endpoints
| Method | Path | Purpose |
|---|---|---|
GET |
/health |
Liveness probe |
POST |
/api/audit-request |
Accepts one audit request |
Expected JSON body:
{
"name": "…",
"email": "…",
"url": "https://…",
"role": "agency | client",
"motivation": "…",
"gdprConsent": "true",
"website": ""
}
website is a honeypot and must stay empty. A filled honeypot is answered with
200 on purpose so bots learn nothing and do not retry.
Setup
cd backend
npm install
cp .env.example .env # then fill in the SMTP credentials
npm start
The process binds to 127.0.0.1 only. Put nginx in front of it with TLS, for
example on api.sascha-bach.de, and proxy to the port from .env.
Then point the frontend at it by setting VITE_AUDIT_ENDPOINT before building:
VITE_AUDIT_ENDPOINT=https://api.sascha-bach.de/api/audit-request npm run build
If the CSP connect-src in public/.htaccess names a different host than the
one you deploy to, the browser blocks the request - keep the two in sync.
Protections in place
- CORS allowlist from
ALLOWED_ORIGINS, never* - Rate limit: 3 requests per IP per 15 minutes
- Request body capped at 10 kB
- Server-side validation of every field (
express-validator) - Honeypot field
- Consent is required, not assumed
- CR/LF stripped from values used in mail headers, HTML escaped in the body
- No personal data written to logs
app.set('trust proxy', 1) assumes exactly one proxy hop (nginx). Adjust it if
you add a second one, otherwise the rate limiter sees a single IP for everyone.