Die Aktion ist die einzige Stelle der Anwendung, an der jemand ohne Anmeldung schreiben darf. Zwei Dinge fehlten. Erstens stand die Seite zwischen dem ersten Start und dem Anlegen des Zugangs fuer jeden offen: Wer sie in diesem Fenster fand, wurde Administrator einer Anwendung mit Gesundheits- und Finanzdaten. Das Fenster ist kurz, aber Zufall ist keine Zugriffskontrolle. Jetzt muss ERSTEINRICHTUNG_GEHEIMNIS aus der .env mit eingegeben werden; verglichen wird ueber Hashes in gleichbleibender Zeit, und ein falscher Wert antwortet mit derselben Mindestdauer wie das Anmeldeformular. Fehlt der Wert, ist die Einrichtung gesperrt statt offen. Das ist Absicht: Eine Installation, bei der die Zeile vergessen wurde, soll eine Meldung zeigen und nicht einrichtbar sein. Zweitens waren Pruefung und Anlegen zwei Schritte. Beide laufen jetzt in einer Transaktion, und die Sperrzeile mit fester Id entscheidet - eine zweite gleichzeitige Anfrage scheitert am Primaerschluessel, bevor ein zweiter Administrator entsteht. Die Seite liest die Sperrzeile mit: Lag der erste Zugang im Papierkorb, stand Schritt 1 sonst wieder offen. Der Seed setzt sie ebenfalls. Refs #38
91 lines
3.4 KiB
TypeScript
91 lines
3.4 KiB
TypeScript
import type { Metadata } from 'next'
|
|
import { redirect } from 'next/navigation'
|
|
import { KeyRound, UserPlus } from 'lucide-react'
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
|
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
|
|
import { aktuellerBenutzer, meineBetreuten } from '@/lib/auth/sitzung'
|
|
import { db } from '@/lib/db'
|
|
import { ZugangFormular } from './zugang-formular'
|
|
import { BetreuungFormular } from './betreuung-formular'
|
|
|
|
export const metadata: Metadata = { title: 'Ersteinrichtung' }
|
|
export const dynamic = 'force-dynamic'
|
|
|
|
/**
|
|
* Ersteinrichtung in drei Schritten.
|
|
*
|
|
* Diese Seite liegt bewusst ausserhalb des Anwendungsrahmens: der Rahmen
|
|
* setzt einen Benutzer, einen zweiten Faktor UND eine betreute Person
|
|
* voraus. Beim allerersten Start gibt es nichts davon.
|
|
*/
|
|
export default async function ErsteinrichtungSeite() {
|
|
const anzahlBenutzer = await db.user.count({ where: { geloeschtAm: null } })
|
|
|
|
// Die Sperrzeile ist die eigentliche Auskunft darueber, ob eingerichtet
|
|
// wurde. Der Benutzerzaehler allein genuegt nicht: Liegt der erste Zugang
|
|
// im Papierkorb, stuende Schritt 1 sonst wieder offen.
|
|
const sperre = await db.ersteinrichtung.findUnique({
|
|
where: { id: 'einmalig' },
|
|
select: { id: true },
|
|
})
|
|
|
|
// --- Schritt 1: Es gibt noch keinen Zugang -------------------------------
|
|
if (anzahlBenutzer === 0 && !sperre) {
|
|
return (
|
|
<>
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<UserPlus className="size-5" aria-hidden="true" />
|
|
Schritt 1 von 3: Dein Zugang
|
|
</CardTitle>
|
|
<CardDescription>
|
|
Dieser erste Zugang verwaltet auch die Anwendung. Danach richtest du den zweiten
|
|
Faktor ein und legst die betreute Person an.
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<ZugangFormular />
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Alert variant="hinweis">
|
|
<KeyRound aria-hidden="true" />
|
|
<AlertTitle>Zum Passwort</AlertTitle>
|
|
<AlertDescription>
|
|
Nimm vier zufällige Wörter statt eines kurzen Kunstworts —{' '}
|
|
<em>Kastanie-Waschbär-Turm-Grün</em> ist leichter zu merken und deutlich schwerer zu
|
|
knacken. Ein Passwortmanager ist noch besser.
|
|
</AlertDescription>
|
|
</Alert>
|
|
</>
|
|
)
|
|
}
|
|
|
|
const benutzer = await aktuellerBenutzer()
|
|
if (!benutzer) redirect('/anmelden')
|
|
|
|
// --- Schritt 2: Zweiter Faktor fehlt noch --------------------------------
|
|
if (!benutzer.totpAktiviertAm) redirect('/zwei-faktor/einrichten')
|
|
|
|
// --- Schritt 3: Noch keine betreute Person ------------------------------
|
|
const betreute = await meineBetreuten(benutzer)
|
|
if (betreute.length > 0) redirect('/heute')
|
|
|
|
return (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Schritt 3 von 3: Die betreute Person</CardTitle>
|
|
<CardDescription>
|
|
Nur das Nötigste — alles Weitere trägst du später in Ruhe unter Stammdaten nach. Das{' '}
|
|
<strong>Bestellungsdatum</strong> ist das wichtigste Feld: daraus berechnet die Anwendung
|
|
das Betreuungsjahr und damit deine gesetzlichen Fristen.
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<BetreuungFormular />
|
|
</CardContent>
|
|
</Card>
|
|
)
|
|
}
|