L15 Testphase & Onboarding: Selbstanmeldung mit Double-Opt-in, Plattform-Wizard, Nur-Lesen-Sperre, Export, Lebenszyklus-Job
- Datenmodell: Testphasen-Lebenszyklus am Mandanten (plan, trialEndsAt, readOnlySince, deletionDueAt, Versandmarker), TrialSignup (Plattform, Hashes statt Klartext), TenantExport (RLS), Onboarding-Status - /testen: 5-Schritte-Wizard (Betrieb, Admin-Konto, Enddatum, Einrichtung, Zusammenfassung), Bestätigung per POST, direkte Anmeldung über login-ticket; Rate-Limit je IP/E-Mail, Honeypot, Enumeration-Schutz, Slug-Kollisionen - Plattform: Wizard „Testmandant anlegen“ mit Einladung, Badges/Filter, Enddatum ändern, umwandeln, beenden, Löschung vormerken/abbrechen (Bestätigung + Audit) - Schreibsperre nach Ablauf zentral in moduleGuard und requireApiContext (non-GET über withApi), Upload-Routen, Einstellungen/Nutzerverwaltung, Worker-Jobs; Banner Backoffice + mobil - Datenexport (ZIP mit CSV/JSON + Dateien) als Worker-Job, auch im Nur-Lesen-Zustand - Täglicher Job trial-lifecycle: Erinnerungen 7/3/1, Ablauf, Löschhinweis, Löschung über das Offboarding - Erste-Schritte-Checkliste im Dashboard, Mail-Vorlagen de/en, Tests + Smoke, Betriebsdoku Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -57,6 +57,12 @@ const ACTION_MODULE: Record<string, string> = {
|
||||
"account.ts": "EXEMPT",
|
||||
"tenant-switch.ts": "EXEMPT",
|
||||
"webauthn.ts": "EXEMPT",
|
||||
// L15 Testphase: Plattform-Wizard/-Aktionen (requirePlatformFullAdmin) und Mandanten-Export/Onboarding
|
||||
// (requireSession + requirePermission + requireApiContext; Export bewusst auch im Nur-Lesen-Zustand)
|
||||
"trial-platform.ts": "EXEMPT",
|
||||
"trial-tenant.ts": "EXEMPT",
|
||||
// L15 Testphase: öffentliche Selbstanmeldung ohne Session — jede Action MUSS das Rate-Limit prüfen
|
||||
"trial-signup.ts": "PUBLIC",
|
||||
};
|
||||
|
||||
const errors: string[] = [];
|
||||
@@ -118,6 +124,20 @@ for (const entry of readdirSync(ACTIONS_DIR)) {
|
||||
continue;
|
||||
}
|
||||
const src = readFileSync(full, "utf8");
|
||||
if (mapped === "PUBLIC") {
|
||||
// Öffentliche Actions (ohne Session): jede exportierte Action muss ein Rate-Limit prüfen.
|
||||
const exportRe = /export async function (\w+)\s*\(/g;
|
||||
const positions: { name: string; index: number }[] = [];
|
||||
let pm: RegExpExecArray | null;
|
||||
while ((pm = exportRe.exec(src))) positions.push({ name: pm[1], index: pm.index });
|
||||
positions.forEach((p, i) => {
|
||||
const body = src.slice(p.index, i + 1 < positions.length ? positions[i + 1].index : src.length);
|
||||
if (!/(enforceTrialRateLimit|checkRateLimit|consumeRateLimit)\(/.test(body)) {
|
||||
errors.push(`${entry}: öffentliche Action "${p.name}" ohne Rate-Limit-Prüfung.`);
|
||||
}
|
||||
});
|
||||
continue;
|
||||
}
|
||||
if (mapped === "EXEMPT") {
|
||||
// Auth-Nachweis: ein require*-Guard ODER ein direkter auth()-Aufruf.
|
||||
if (!/require(Session|Platform\w*|Permission)|\bauth\(\)/.test(src)) {
|
||||
|
||||
Reference in New Issue
Block a user