Files
anouma/collections/Redirects.ts
T
maro 82c4261a81 Fix security and correctness issues found in full-codebase audit
- Restrict meetingPassword field so it isn't leaked via the public REST/GraphQL API
- Fix booking slot computation to use Europe/Berlin time instead of the server's UTC clock
- Reset email verification when a customer changes their email address
- Fix ICS calendar injection via unescaped carriage returns in event descriptions
- Bind WebRTC meeting kicks to a persistent client id so they survive a rejoin
- Move the meeting signaling token out of the WebSocket query string into the first message
- Prevent duplicate signaling connections from leaking when the same participant reconnects
- Use the rightmost X-Forwarded-For entry for rate limiting (single trusted reverse proxy)
- Use constant-time comparison for the cron endpoint's bearer token
- Remove SVG from allowed Media upload types (stored-XSS risk, unused anyway)
- Add baseline security headers and a CSP to next.config.ts
- Fix CI/CD script injection via unsanitized git ref names in release.yml
2026-09-06 21:59:39 +02:00

81 lines
2.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { CollectionConfig } from "payload";
import { isAdmin } from "@/access";
function normalizePath(value: string): string {
// Lowercased to match the slugs it points at/from (fields/slug.ts always
// lowercases via slugify) — without this, "/Angebote/Doula" and
// "/angebote/doula" count as different values to the `unique` constraint
// on fromPath, letting near-duplicate redirects coexist.
const trimmed = value.trim().toLowerCase();
const withSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
return withSlash.length > 1 ? withSlash.replace(/\/+$/, "") : withSlash;
}
/**
* Simple slug-change redirect table (see lib/seo/redirects.ts). Deliberately
* scoped to the three dynamic [slug] detail routes (Angebote/Termine/
* Aktuelles) — that's the only place slugs actually live and can change, so
* looking this up only there avoids adding a database lookup to every
* single request the way a catch-all middleware would.
*/
export const Redirects: CollectionConfig = {
slug: "redirects",
labels: {
singular: "Weiterleitung",
plural: "Weiterleitungen",
},
admin: {
useAsTitle: "fromPath",
defaultColumns: ["fromPath", "toPath", "type", "enabled"],
group: "SEO",
description: "Leitet eine alte URL (z. B. nach einer Slug-Änderung bei Angeboten, Terminen oder Aktuelles) dauerhaft auf eine neue weiter.",
},
access: {
read: () => true,
create: isAdmin,
update: isAdmin,
delete: isAdmin,
},
fields: [
{
name: "fromPath",
type: "text",
label: "Alte URL (Pfad)",
required: true,
unique: true,
index: true,
admin: { description: "Nur der Pfad, z. B. /angebote/doula (ohne Domain)." },
hooks: {
beforeValidate: [({ value }) => (typeof value === "string" && value ? normalizePath(value) : value)],
},
},
{
name: "toPath",
type: "text",
label: "Neue URL (Pfad)",
required: true,
admin: { description: "Ziel-Pfad, z. B. /angebote/doula-begleitung." },
hooks: {
beforeValidate: [({ value }) => (typeof value === "string" && value ? normalizePath(value) : value)],
},
},
{
name: "type",
type: "select",
label: "Art der Weiterleitung",
required: true,
defaultValue: "permanent",
options: [
{ label: "301 – Dauerhaft", value: "permanent" },
{ label: "302 – Vorübergehend", value: "temporary" },
],
},
{
name: "enabled",
type: "checkbox",
label: "Aktiv",
defaultValue: true,
},
],
};