feat: Anmeldung mit Pflicht-2FA, Rollen und Audit-Log
Auth.js mit Zugangsdaten-Provider: E-Mail, Passwort und TOTP-Code werden in einem Formular abgefragt. Ein zweistufiger Ablauf muesste zwischen den Schritten einen Zwischenzustand halten - genau das ist die uebliche Schwachstelle, und am Handy ist ein Formular ohnehin schneller. Anmeldung: - Sperre wird VOR dem Passwortvergleich geprueft, damit ein gesperrter Zugang nicht einmal Argon2-Rechenzeit kostet. - Bei unbekannter Adresse wird gegen einen Dummy-Hash gerechnet und die Antwort auf eine Mindestdauer gestreckt. Sonst verraet die Antwortzeit, welche Adressen existieren. - Wiederherstellungscodes werden nach Gebrauch entfernt. - Veraltete Argon2-Parameter werden beim erfolgreichen Login nachgezogen. Rate-Limiting in der Datenbank statt Redis - ein Server, wenige Benutzer, kein Grund fuer eine weitere Komponente. Gezaehlt wird nach E-Mail und nach Anschluss, damit weder viele Anschluesse noch viele Adressen helfen. Zugriffsschutz (sitzung.ts): - Die Sitzung allein entscheidet nichts. Bei jedem Aufruf wird der Benutzer frisch geladen und die Sitzungsgeneration verglichen - so lassen sich alle Anmeldungen auf einen Schlag beenden. - erfordereZugang() prueft immer gegen die betreuteId, nie nur im UI. - Erneute Bestaetigung vor sensiblen Aktionen ueber ein signiertes, kurzlebiges Cookie statt ueber Serverzustand. Audit-Log: aenderungErmitteln() protokolliert nur die tatsaechlich geaenderten Felder; Passwoerter und TOTP-Geheimnisse werden ersetzt, nie gespeichert. Ein Fehler im Protokoll bricht die Fachaktion nicht ab - ausser die Aktion laeuft in einer Transaktion, dann faellt beides. CSP mit Nonce pro Anfrage in der Middleware: keine Fremdquellen, kein CDN, keine Fonts von aussen. Fachseiten werden nicht zwischengespeichert. Etappe: 1 Phase: Fundament
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
import 'server-only'
|
||||
import { PrismaClient } from '@prisma/client'
|
||||
|
||||
/**
|
||||
* Prisma-Client als Singleton.
|
||||
*
|
||||
* Im Entwicklungsmodus laedt Next.js Module bei jeder Aenderung neu. Ohne
|
||||
* diesen Umweg ueber globalThis entstuende bei jedem Speichern eine neue
|
||||
* Verbindung, bis Postgres keine mehr annimmt.
|
||||
*/
|
||||
|
||||
const globalFuerPrisma = globalThis as unknown as {
|
||||
prisma: PrismaClient | undefined
|
||||
}
|
||||
|
||||
export const db =
|
||||
globalFuerPrisma.prisma ??
|
||||
new PrismaClient({
|
||||
log:
|
||||
process.env.NODE_ENV === 'development'
|
||||
? [{ emit: 'stdout', level: 'warn' }, { emit: 'stdout', level: 'error' }]
|
||||
: [{ emit: 'stdout', level: 'error' }],
|
||||
})
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
globalFuerPrisma.prisma = db
|
||||
}
|
||||
|
||||
/**
|
||||
* Standardfilter: Der Papierkorb wird ueberall ausgeblendet. Wer die
|
||||
* geloeschten Datensaetze sehen will, fragt sie ausdruecklich ab.
|
||||
*/
|
||||
export const nichtGeloescht = { geloeschtAm: null } as const
|
||||
|
||||
/**
|
||||
* Stichtag, ab dem ein Datensatz im Papierkorb endgueltig entfernt wird.
|
||||
* 30 Tage - lang genug fuer ein "oh, das brauchte ich doch noch".
|
||||
*/
|
||||
export const PAPIERKORB_TAGE = 30
|
||||
|
||||
export function papierkorbGrenze(jetzt = new Date()): Date {
|
||||
const grenze = new Date(jetzt)
|
||||
grenze.setDate(grenze.getDate() - PAPIERKORB_TAGE)
|
||||
return grenze
|
||||
}
|
||||
|
||||
/**
|
||||
* BigInt laesst sich nicht als JSON serialisieren. Server Components
|
||||
* reichen Daten an Client Components weiter - deshalb hier ein Helfer,
|
||||
* der Cent-Betraege auf dem Weg nach draussen in Number wandelt.
|
||||
* Cent-Betraege bis 90 Billionen Euro bleiben dabei exakt.
|
||||
*/
|
||||
export function centZuZahl(wert: bigint | null | undefined): number | null {
|
||||
if (wert === null || wert === undefined) return null
|
||||
if (wert > BigInt(Number.MAX_SAFE_INTEGER) || wert < BigInt(-Number.MAX_SAFE_INTEGER)) {
|
||||
throw new Error(`Betrag ${wert} liegt ausserhalb des sicher darstellbaren Bereichs.`)
|
||||
}
|
||||
return Number(wert)
|
||||
}
|
||||
Reference in New Issue
Block a user