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>
55 lines
1.6 KiB
TypeScript
55 lines
1.6 KiB
TypeScript
import type { Metadata } from 'next';
|
|
import Link from 'next/link';
|
|
import { redirect } from 'next/navigation';
|
|
import { InviteForm } from '@/components/auth/invite-form';
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
|
import { formatDate } from '@/lib/format';
|
|
import { findUsableInvite } from '@/lib/invites';
|
|
import { getSession } from '@/lib/session';
|
|
|
|
export const metadata: Metadata = {
|
|
title: 'Einladung',
|
|
robots: { index: false, follow: false },
|
|
};
|
|
|
|
export default async function InvitePage({ params }: { params: Promise<{ token: string }> }) {
|
|
if (await getSession()) redirect('/');
|
|
|
|
const { token } = await params;
|
|
const invitation = findUsableInvite(token);
|
|
|
|
if (!invitation) {
|
|
return (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Einladung ungültig</CardTitle>
|
|
<CardDescription>
|
|
Dieser Link ist abgelaufen oder wurde schon benutzt. Lass dir einfach einen neuen
|
|
schicken.
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Link href="/anmelden" className="text-sm underline underline-offset-4">
|
|
Zur Anmeldung
|
|
</Link>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Konto anlegen</CardTitle>
|
|
<CardDescription>
|
|
Du wurdest zu Bricktrack eingeladen. Der Link gilt noch bis zum{' '}
|
|
{formatDate(invitation.expiresAt)}.
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<InviteForm token={token} email={invitation.email} />
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|