68 lines
2.1 KiB
Markdown
68 lines
2.1 KiB
Markdown
# 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:
|
|
|
|
```json
|
|
{
|
|
"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
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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.
|