chahinebrini 38811820e6 feat(backend): Public-Domain-Guard + Mail-Detection (spins/%-Pattern)
Public-Domain-Guard (icloud.com/gmail.com etc. nie blockbar/veröffentlichbar):
- neue utils/public-email-domains.ts (shared Freemail-Liste)
- custom-domains/index.post + custom-domains/suggest + curated-domains/suggest
  lehnen Public-Domains mit 400 PUBLIC_DOMAIN ab (defense-in-depth)

Mail-Detection (mo): "spins" zu GAMBLING_KEYWORDS + Subject-%-Pattern (Score 10)
→ fängt "Spins + 400% Bonus"-Spam von Freemail-Absendern. 61/61 Tests grün.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 01:06:06 +02:00

64 lines
2.0 KiB
TypeScript

import { usePrisma } from "../../utils/prisma";
import { suggestCuratedDomain } from "../../db/curatedDomains";
import { isPublicEmailDomain } from "../../utils/public-email-domains";
// Unterstützte Ländercodes für Layer-2-Listen
const SUPPORTED_COUNTRIES = ["DE", "GB", "FR", "TN"] as const;
type SupportedCountry = (typeof SUPPORTED_COUNTRIES)[number];
// Regex: Domain muss mindestens eine TLD haben
const DOMAIN_RE =
/^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$/;
/**
* POST /api/custom-domains/suggest
*
* User schlägt eine Domain für die Country-Curated-Layer-2-Liste vor.
* Erstellt einen CuratedDomain-Eintrag mit status="suggested".
* Admin entscheidet via PATCH /api/admin/curated-domains/[id] (approve/reject).
*
* Body: { domain: string, country: string }
*
* Response:
* { ok: true, id: string, domain: string, country: string }
* oder 409 wenn domain+country-Kombination bereits existiert
*/
export default defineEventHandler(async (event) => {
const user = await requireUser(event);
const body = await readBody(event);
const rawDomain = (body?.domain as string)?.trim().toLowerCase() ?? "";
const rawCountry = (body?.country as string)?.trim().toUpperCase() ?? "";
if (!rawDomain || !DOMAIN_RE.test(rawDomain)) {
throw createError({ statusCode: 400, data: { error: "INVALID_DOMAIN" } });
}
// Public-/Freemail-Domains dürfen NIE in die kuratierte Country-Liste —
// sonst landet z.B. icloud.com global (genau der gemeldete Vorfall).
if (isPublicEmailDomain(rawDomain)) {
throw createError({
statusCode: 400,
data: { error: "PUBLIC_DOMAIN" },
});
}
if (!(SUPPORTED_COUNTRIES as readonly string[]).includes(rawCountry)) {
throw createError({
statusCode: 400,
data: {
error: "INVALID_COUNTRY",
supported: SUPPORTED_COUNTRIES,
},
});
}
const result = await suggestCuratedDomain(
user.id,
rawDomain,
rawCountry as SupportedCountry,
);
return { ok: true, ...result };
});