feat: redesign CMS and unify German admin UI

This commit is contained in:
2026-09-17 16:51:24 +02:00
parent a2b3e99174
commit 7ec0c51ff5
129 changed files with 952 additions and 550 deletions
+3 -3
View File
@@ -1,6 +1,6 @@
/**
* Minimal in-memory fixed-window rate limiter for a single Node process
* (this app always runs as one process — see server.ts). Good enough to
* (this app always runs as one process - see server.ts). Good enough to
* blunt abuse of the email-verification resend endpoint without adding an
* external store; resets on deploy/restart, which is an acceptable
* trade-off for this use case.
@@ -34,12 +34,12 @@ export function checkRateLimit(key: string, opts: { max: number; windowMs: numbe
export function getClientIp(request: { headers: Headers }): string {
// Deployment is always behind exactly one trusted reverse proxy (see
// DEPLOYMENT.md — Nginx Proxy Manager; the app port isn't published
// DEPLOYMENT.md - Nginx Proxy Manager; the app port isn't published
// directly to the host). nginx's default `$proxy_add_x_forwarded_for`
// *appends* the real client IP to any X-Forwarded-For it already
// received rather than replacing it, so a directly-connecting client
// could prepend arbitrary spoofed entries of their own. Only the
// right-most (last) entry — the one the trusted proxy itself appended —
// right-most (last) entry - the one the trusted proxy itself appended -
// is trustworthy; taking the first entry would let a client bypass this
// rate limit entirely by sending a fake X-Forwarded-For header.
const forwarded = request.headers.get("x-forwarded-for");
+2 -2
View File
@@ -3,13 +3,13 @@ import { createHash, randomBytes } from "node:crypto";
/** How long a freshly issued email-verification link stays valid. */
export const EMAIL_VERIFICATION_TTL_MS = 24 * 60 * 60 * 1000;
/** 256 bits of randomness, hex-encoded — not guessable, never derived from user data. */
/** 256 bits of randomness, hex-encoded - not guessable, never derived from user data. */
export function generateVerificationToken(): string {
return randomBytes(32).toString("hex");
}
/**
* Only this hash is ever persisted (see collections/Customers.ts) — the
* Only this hash is ever persisted (see collections/Customers.ts) - the
* plaintext token exists only in the URL sent by email and briefly in
* memory while that email is being sent.
*/