Add rate limiting, sliding sessions, email verification, GDPR self-service, order cancellation/returns, and critical-error alerting

Complements Payload's per-account login lockout with per-IP rate limiting
on auth routes; proxy.ts silently refreshes an active customer's session
via Payload's built-in refresh-token endpoint instead of a long-lived
token. Registration now sends a non-blocking email-verification link
(doesn't gate login, since checkout registers and immediately logs in
mid-purchase). /konto/profil gets GDPR export/delete; order detail pages
get self-service cancel/return-request, backed by a Payload hook that
closes a real gap (a customer's JWT could previously PATCH any field of
their own order, not just status). Checkout failures now email an alert
independent of Payload's own health, since Kuma's uptime checks can't see
an order silently failing to persist.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Marco
2026-07-22 07:28:01 +00:00
parent 7f37f111e8
commit df05ea5358
22 changed files with 822 additions and 20 deletions
+16 -1
View File
@@ -5,6 +5,7 @@ import { validateDiscountCode, redeemDiscountCode } from "../../lib/discountServ
import { createOrder } from "../../lib/orderServer";
import { getSessionCustomer, registerCustomer, setSessionCookie, type CustomerSummary } from "../../lib/customerAuth";
import { fetchProductsBySlug } from "../../lib/productsServer";
import { sendCriticalAlert } from "../../lib/alertAdmin";
type CheckoutBody = {
cart: CartItem[];
@@ -137,7 +138,21 @@ export async function POST(request: Request) {
discountAmount,
total,
});
if (!order) return NextResponse.json({ ok: false, reason: "Bestellung konnte nicht gespeichert werden." }, { status: 500 });
if (!order) {
// The worst-case failure in this whole flow: the customer went
// through checkout believing they bought something, and nothing was
// persisted. Kuma's uptime checks can't see this (the site is up,
// this route just returned a 500) — this is the one alert path that
// can.
sendCriticalAlert("Bestellung konnte nicht gespeichert werden", {
customerId: customer.id,
customerEmail: body.email,
cart: body.cart,
total,
timestamp: new Date().toISOString(),
});
return NextResponse.json({ ok: false, reason: "Bestellung konnte nicht gespeichert werden." }, { status: 500 });
}
return NextResponse.json({
ok: true,