From efecc500c4001d326ffb412ba19761b2fc91e902 Mon Sep 17 00:00:00 2001 From: Felix Klement Date: Mon, 5 Oct 2026 10:44:55 +0200 Subject: [PATCH] misc --- .env.example | 4 + README.md | 24 ++++-- astro.config.mjs | 2 +- docker/nginx.conf | 8 ++ package-lock.json | 136 ++++++++++++++++++++++++++---- package.json | 1 + public/apple-touch-icon.png | Bin 0 -> 17594 bytes public/favicon.ico | Bin 0 -> 43407 bytes public/favicon.svg | 1 - server/contact.mjs | 86 ++++++++++++++++++- server/package-lock.json | 70 +++++++++++++++ server/package.json | 1 + src/components/home/Contact.astro | 50 ++++++++--- src/i18n/ui.ts | 10 ++- src/layouts/Base.astro | 3 +- 15 files changed, 349 insertions(+), 47 deletions(-) create mode 100644 public/apple-touch-icon.png create mode 100644 public/favicon.ico delete mode 100644 public/favicon.svg diff --git a/.env.example b/.env.example index 07c2bfc..51b4233 100644 --- a/.env.example +++ b/.env.example @@ -5,3 +5,7 @@ SMTP_USER=website@ciphercell.de SMTP_PASS= CONTACT_FROM=website@ciphercell.de CONTACT_TO=office@ciphercell.de +# Signing secrets for the form's proof-of-work challenge (ALTCHA). Optional: without them each +# start makes its own. Generate with: openssl rand -hex 32 +ALTCHA_HMAC_SECRET= +ALTCHA_HMAC_KEY_SECRET= diff --git a/README.md b/README.md index 11322b6..2cbe36c 100644 --- a/README.md +++ b/README.md @@ -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 `