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:
@@ -6,7 +6,8 @@ import { assertModuleEnabled, requireModule } from "@/server/modules";
|
||||
import type { Permission } from "@/server/rbac";
|
||||
import type { ModuleKey } from "@/lib/modules";
|
||||
import type { ServiceCtx } from "@/server/services/context";
|
||||
import { ApiError } from "@/server/api/respond";
|
||||
import { ApiError, isMutatingApiRequest } from "@/server/api/respond";
|
||||
import { assertTenantWritable } from "@/server/services/trial/state";
|
||||
import { consumeRateLimit } from "@/server/rate-limit";
|
||||
|
||||
// assertSameOrigin lives in respond.ts (withApi applies it to every mutation); re-exported for
|
||||
@@ -63,10 +64,20 @@ export async function requireApiContext(moduleKey: ModuleKey | null, ...permissi
|
||||
}
|
||||
if (moduleKey) await assertModuleEnabled(session, moduleKey); // throws ModuleDisabledError → 403
|
||||
if (moduleKey) enforceApiRateLimit(session.user.id, moduleKey);
|
||||
await assertApiWriteAllowed(tenantId);
|
||||
|
||||
return { db, tenantId, userId: session.user.id, permissions: effective };
|
||||
}
|
||||
|
||||
/**
|
||||
* L15 Testphase: non-GET /api/v1 requests (withApi) of an expired trial tenant → `blocked
|
||||
* trial_expired` (422). GET requests (reads, PDFs, downloads, export) are never blocked. Route
|
||||
* handlers outside `withApi` that write (backoffice upload) call `assertTenantWritable` themselves.
|
||||
*/
|
||||
export async function assertApiWriteAllowed(tenantId: string): Promise<void> {
|
||||
if (isMutatingApiRequest()) await assertTenantWritable(tenantId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Per-user request budget for /api/v1 (in-memory, per app instance — see rate-limit.ts).
|
||||
* Field endpoints (sync outbox, uploads, offline pre-download, document cache) get the generous
|
||||
|
||||
Reference in New Issue
Block a user