Nach dem ersten Nutzer bleibt die Registrierung geschlossen; weitere Konten entstehen nur ueber einen Einladungslink. Der Klartext-Token wird genau einmal angezeigt, in der Datenbank liegt sein SHA-256-Hash. Optional laesst sich eine Einladung an eine E-Mail-Adresse binden, die Rolle und die Gueltigkeit sind waehlbar. Die Freigabe zum Anlegen eines Kontos laeuft ueber AsyncLocalStorage: Der Server-Action, der den Token geprueft hat, umschliesst damit den signUp-Aufruf, und nur in diesem Kontext laesst der user-create-Hook von better-auth die Registrierung zu. Die Rolle kommt aus der Einladung und kann nicht ueber das Formular gesetzt werden. Der Smoke-Test deckt den ganzen Weg ab: Admin erstellt die Einladung, der zweite Elternteil registriert sich damit, bekommt die Rolle "Mitglied" (und wird von der Einladungsverwaltung weggeleitet), und der Link funktioniert kein zweites Mal. Refs Phase 2 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
23 lines
656 B
TypeScript
23 lines
656 B
TypeScript
import type { Metadata } from 'next';
|
|
import { InviteList, type InviteListRow } from '@/components/verwaltung/invite-list';
|
|
import { inviteState, listInvites } from '@/lib/invites';
|
|
import { requireAdmin } from '@/lib/session';
|
|
|
|
export const metadata: Metadata = { title: 'Einladungen' };
|
|
|
|
export default async function EinladungenPage() {
|
|
await requireAdmin();
|
|
|
|
const invites: InviteListRow[] = listInvites().map((row) => ({
|
|
id: row.id,
|
|
email: row.email,
|
|
role: row.role,
|
|
expiresAt: row.expiresAt,
|
|
usedAt: row.usedAt,
|
|
usedByName: row.usedByName,
|
|
state: inviteState(row),
|
|
}));
|
|
|
|
return <InviteList invites={invites} />;
|
|
}
|