This commit is contained in:
Felix Klement committed 2026-10-05 10:44:55 +02:00
1 parent 490444032c
commit efecc500c4
15 files changed
+349 -47

No files matched your search

+16 -8
View File
@@ -61,13 +61,20 @@ Environment; see *Contact form* below.
- **Contact form.** The form posts `first_name`, `last_name`, `email`, `message`, `lang` and a
honeypot field `website` to `POST /contact`. That one path is handled by `server/contact.mjs`
(Node, nodemailer), which sends the message by SMTP to `CONTACT_TO` with the sender as
Reply-To. With scripting the form sends in place and shows the result under the button;
without, the handler redirects to `/contact/sent` or `/contact/error` (under `/de` for German).
Both pages are `noindex` and left out of the sitemap. The handler caps size and field lengths,
refuses cross-site posts, and allows 5 messages per address per 10 minutes and 60 per hour
overall.
Reply-To. The form sends in place and shows the result in a toast; a post that asks for HTML
is redirected to `/contact/sent` or `/contact/error` (under `/de` for German). Both pages are
`noindex` and left out of the sitemap. The handler caps size and field lengths, refuses
cross-site posts, and allows 5 messages per address per 10 minutes and 60 per hour overall.
In the container, nginx proxies `/contact` to the handler on `127.0.0.1:3000`; both are started
Against spam, every message carries the answer to a proof-of-work challenge (ALTCHA,
`altcha-lib`). The form fetches a signed challenge from `GET /contact/challenge` when the
visitor first focuses it and solves it in the background (about half a second on a laptop, one
to two on a phone). The handler refuses a post without a valid, unexpired, unused solution, or
one sent less than 3 seconds after the challenge was issued. Nothing is stored in the browser
and no third party is involved, so there is nothing to consent to. The form therefore needs
JavaScript; without it a `<noscript>` line points to the email address.
In the container, nginx proxies `/contact` and `/contact/challenge` to the handler on `127.0.0.1:3000`; both are started
by `docker/entrypoint.sh`, which restarts the handler if it crashes. If the handler is down,
visitors are sent to the error page. Without SMTP settings the handler does not start and the
rest of the site is served as usual.
@@ -81,10 +88,11 @@ Environment; see *Contact form* below.
| `CONTACT_TO` | `office@ciphercell.de` | Where messages go. |
| `SMTP_SECURE` | by port | Force TLS from the start (`true`) or not (`false`). |
| `SMTP_REQUIRE_TLS` | `true` | `false` only for a local mail catcher. |
| `ALTCHA_HMAC_SECRET`, `ALTCHA_HMAC_KEY_SECRET` | random per start | Signing secrets for the challenge (`openssl rand -hex 32`). Set them so a restart does not void challenges already handed out. |
In Dokploy, set these under the application's Environment. Locally, put them in `.env` (see
`.env.example`), run `npm run contact` next to `npm run dev`; the dev server proxies `/contact`
to it.
`.env.example`), run `npm run contact` next to `npm run dev`; the dev server proxies `/contact` and
`/contact/challenge` to it.
- **Alpha sign-up.** While `verifierComingSoon` in `src/data/flags.ts` is true, `/cipherverifier`
shows a coming-soon page whose form posts `email`, `consent=yes`, `lang`,
`list=cipherverifier-alpha` and the honeypot `website` to `POST /notify`. The handler is