Verweise auf fremde Akten an einer Stelle pruefen

Wenn ein Formular einen Fremdschluessel mitschickt, kommt der aus einer
Auswahlliste im Browser. Der Server darf sich darauf nicht verlassen -
eine veraenderte Anfrage kann jede beliebige Id enthalten.

Alle 22 Dateien unter src/server/actions/ durchgesehen. Das Ergebnis ist
besser als befuerchtet: Bis auf eine Stelle war jeder Verweis geprueft.

  buchung.vorgangId ging ungeprueft durch. Konto, Beleg und Vertrag
  wurden geprueft, der Vorgang nicht.

Die Pruefung liegt jetzt in lib/auth/verweise.ts statt verstreut in den
Aktionen. Die acht erlaubten Ziele sind eine ausgeschriebene Aufzaehlung
und kein dynamischer Tabellenzugriff: So sieht der Typpruefer jede
Abfrage, und welche Tabellen erreichbar sind, ist eine Entscheidung und
keine Nebenwirkung. Mehrere Verweise laufen nebeneinander - bei vier
Verweisen eine Wartezeit statt vier.

Umgestellt: die vier Einzelpruefungen der Buchung, die drei des
Vermoegenspostens, die eigene Hilfsfunktion fuer den Ansprechpartner.
Nicht angefasst wurden die Stellen, die ihre eigene, passendere Form
haben - etwa das Kontaktprotokoll, das beide Verweise parallel laedt und
feldgenaue Meldungen zurueckgibt.

Zum Test: Der erste Entwurf rief die Server Action per fetch nach. Der
waere auch ohne jede Pruefung gruen gewesen, weil Next einen Aufruf ohne
gueltige Action-Id ohnehin ablehnt - er haette nichts bewiesen. Der
jetzige schiebt dem echten Auswahlfeld einen Eintrag mit fremder Id
unter und schickt das Formular normal ab. Gegenprobe mit ausgebauter
Pruefung: faellt um, wie er soll.

Refs #23
This commit is contained in:
Florian Wilde
2026-08-23 23:20:26 +02:00
parent 91592cbe88
commit 91a9aca0d8
4 changed files with 269 additions and 83 deletions
+18 -51
View File
@@ -4,6 +4,7 @@ import { revalidatePath } from 'next/cache'
import { redirect } from 'next/navigation'
import { Prisma } from '@prisma/client'
import { db, nichtGeloescht } from '@/lib/db'
import { verweisePruefen } from '@/lib/auth/verweise'
import { erfordereZugang, ZugriffVerweigert } from '@/lib/auth/sitzung'
import { aenderungErmitteln, protokollieren } from '@/lib/audit'
import { sealen } from '@/lib/crypto/feld'
@@ -325,29 +326,15 @@ export async function buchungSpeichern(
}
const werte = gepruft.data
if (werte.kontoId) {
const konto = await db.konto.findFirst({
where: { id: werte.kontoId, betreuteId, ...nichtGeloescht },
select: { id: true },
})
if (!konto) return { fehler: 'Dieses Konto gehört nicht zu dieser betreuten Person.' }
}
// Wie beim Konto: Der Vertrag muss zu dieser Akte gehoeren.
if (werte.vertragId) {
const vertrag = await db.vertrag.findFirst({
where: { id: werte.vertragId, betreuteId, ...nichtGeloescht },
select: { id: true },
})
if (!vertrag) return { fehler: 'Dieser Vertrag gehört nicht zu dieser betreuten Person.' }
}
if (werte.belegId) {
const beleg = await db.dokument.findFirst({
where: { id: werte.belegId, betreuteId, ...nichtGeloescht },
select: { id: true },
})
if (!beleg) return { fehler: 'Dieses Dokument gehört nicht zu dieser betreuten Person.' }
}
// Alle vier Verweise auf einmal - der Vorgang war bisher der einzige,
// der ungeprueft durchging.
const verweisfehler = await verweisePruefen(betreuteId, {
konto: werte.kontoId,
vertrag: werte.vertragId,
dokument: werte.belegId,
vorgang: werte.vorgangId,
})
if (verweisfehler) return { fehler: verweisfehler }
const felder = {
kontoId: werte.kontoId,
@@ -635,34 +622,14 @@ export async function vermoegenspostenSpeichern(
}
const werte = gepruft.data
if (werte.kontoId) {
const konto = await db.konto.findFirst({
where: { id: werte.kontoId, betreuteId, ...nichtGeloescht },
select: { id: true },
})
if (!konto) return { fehler: 'Dieses Konto gehört nicht zu dieser betreuten Person.' }
}
// Dieselbe Pruefung fuer die beiden anderen Verweise. Sie fehlte:
// An der Verknuepfung zum Vertrag haengt dessen Rueckkaufswert, und
// die darf nicht in eine fremde Akte zeigen.
if (werte.vertragId) {
const vertrag = await db.vertrag.findFirst({
where: { id: werte.vertragId, betreuteId, ...nichtGeloescht },
select: { id: true },
})
if (!vertrag) return { fehler: 'Dieser Vertrag gehört nicht zu dieser betreuten Person.' }
}
if (werte.immobilieId) {
const immobilie = await db.immobilie.findFirst({
where: { id: werte.immobilieId, betreuteId, ...nichtGeloescht },
select: { id: true },
})
if (!immobilie) {
return { fehler: 'Diese Immobilie gehört nicht zu dieser betreuten Person.' }
}
}
// An der Verknuepfung zum Vertrag haengt dessen Rueckkaufswert - sie
// darf nicht in eine fremde Akte zeigen.
const verweisfehler = await verweisePruefen(betreuteId, {
konto: werte.kontoId,
vertrag: werte.vertragId,
immobilie: werte.immobilieId,
})
if (verweisfehler) return { fehler: verweisfehler }
const felder = {
bezeichnung: werte.bezeichnung,