feat(privat/CV): sync to latest upstream (cv-upstream/main, 30f9608b)

This commit is contained in:
2026-07-07 15:20:55 +02:00
parent 8ac664d33d
commit a4efabbc60
417 changed files with 48564 additions and 712 deletions
@@ -0,0 +1,38 @@
/**
* Anti-spam middleware for public forms.
*
* Two complementary checks:
* 1. Honeypot — a hidden field (`company_website`) that real users never see.
* If it is filled, the request is a bot. We respond with a fake success
* so the bot does not learn it was detected.
* 2. Time trap — a `form_started_at` timestamp set by the client when the form
* is rendered. Submissions faster than MIN_FILL_MS are almost certainly bots.
*
* Both fields are stripped from req.body before reaching route handlers.
*/
const MIN_FILL_MS = 2000 // 2 seconds
export function antiSpam(req, res, next) {
const { company_website, form_started_at } = req.body || {}
// Honeypot filled → silently pretend success
if (company_website) {
return res.status(201).json({ ok: true, message: 'Bestätigungs-E-Mail gesendet.' })
}
// Time trap → too fast to be human
if (form_started_at) {
const elapsed = Date.now() - Number(form_started_at)
if (Number.isFinite(elapsed) && elapsed >= 0 && elapsed < MIN_FILL_MS) {
return res.status(201).json({ ok: true, message: 'Bestätigungs-E-Mail gesendet.' })
}
}
// Clean up internal fields before route handlers use req.body
if (req.body) {
delete req.body.company_website
delete req.body.form_started_at
}
next()
}
@@ -0,0 +1,21 @@
import rateLimit from 'express-rate-limit'
/**
* Per-endpoint rate limiters. Limits are per IP per hour.
* Behind a reverse proxy (Caddy), `app.set('trust proxy', 1)` must be set
* so the real client IP is used instead of the proxy IP.
*/
function makeLimiter(max, message) {
return rateLimit({
windowMs: 60 * 60 * 1000, // 1 hour
max,
standardHeaders: true,
legacyHeaders: false,
message: { error: message },
})
}
export const contactLimiter = makeLimiter(3, 'Zu viele Anfragen. Bitte versuche es später erneut.')
export const talkRequestLimiter = makeLimiter(3, 'Zu viele Anfragen. Bitte versuche es später erneut.')
export const newsletterLimiter = makeLimiter(5, 'Zu viele Anfragen. Bitte versuche es später erneut.')
export const resourceLimiter = makeLimiter(10, 'Zu viele Anfragen. Bitte versuche es später erneut.')