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,89 @@
|
||||
import type { Modul, Rolle } from '@prisma/client'
|
||||
|
||||
/**
|
||||
* Rollen und Modulfreigaben - ohne "server-only", weil die Beschriftungen
|
||||
* auch im Browser gebraucht werden.
|
||||
*
|
||||
* Wichtig: Diese Datei entscheidet nichts allein. Der eigentliche
|
||||
* Zugriffsschutz sitzt in src/lib/auth/sitzung.ts und prueft serverseitig
|
||||
* gegen die betreuteId. Hier stehen nur die Regeln, damit sie an einem Ort
|
||||
* nachlesbar sind.
|
||||
*/
|
||||
|
||||
export const ROLLEN_BESCHRIFTUNG: Record<Rolle, string> = {
|
||||
BETREUER: 'Betreuer',
|
||||
MITLESER: 'Mitleser',
|
||||
ADMIN: 'Administrator',
|
||||
}
|
||||
|
||||
export const ROLLEN_ERKLAERUNG: Record<Rolle, string> = {
|
||||
BETREUER: 'Darf alles sehen und ändern, was zu den zugeordneten Betreuten gehört.',
|
||||
MITLESER: 'Darf nur lesen, und nur die ausdrücklich freigegebenen Bereiche.',
|
||||
ADMIN: 'Verwaltet Benutzer und Systemeinstellungen.',
|
||||
}
|
||||
|
||||
export const MODUL_BESCHRIFTUNG: Record<Modul, string> = {
|
||||
STAMMDATEN: 'Stammdaten',
|
||||
VORGAENGE: 'Vorgänge',
|
||||
FRISTEN: 'Fristen',
|
||||
DOKUMENTE: 'Dokumente',
|
||||
FINANZEN: 'Vermögen & Kassenbuch',
|
||||
SOZIALHILFE: 'Sozialhilfe',
|
||||
IMMOBILIE: 'Immobilie',
|
||||
VERTRAEGE: 'Versicherungen & Verträge',
|
||||
GESUNDHEIT: 'Heim & Pflege',
|
||||
KONTAKTE: 'Kontakte',
|
||||
BERICHTE: 'Berichte',
|
||||
}
|
||||
|
||||
export const ALLE_MODULE: Modul[] = [
|
||||
'STAMMDATEN',
|
||||
'VORGAENGE',
|
||||
'FRISTEN',
|
||||
'DOKUMENTE',
|
||||
'FINANZEN',
|
||||
'SOZIALHILFE',
|
||||
'IMMOBILIE',
|
||||
'VERTRAEGE',
|
||||
'GESUNDHEIT',
|
||||
'KONTAKTE',
|
||||
'BERICHTE',
|
||||
]
|
||||
|
||||
/**
|
||||
* Module mit besonders sensiblen Inhalten. Fuer sie ist eine Freigabe an
|
||||
* Mitleser eine bewusste Entscheidung - die Oberflaeche weist darauf hin.
|
||||
*/
|
||||
export const BESONDERS_SENSIBEL: Modul[] = ['GESUNDHEIT', 'FINANZEN', 'SOZIALHILFE']
|
||||
|
||||
export type Zugriffsart = 'lesen' | 'schreiben'
|
||||
|
||||
export type Zugang = {
|
||||
rolle: Rolle
|
||||
freigegebeneModule: Modul[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Die eine Regel, nach der entschieden wird:
|
||||
*
|
||||
* - BETREUER darf alles, lesen wie schreiben.
|
||||
* - ADMIN darf alles (er verwaltet die Anwendung).
|
||||
* - MITLESER darf ausschliesslich lesen, und nur freigegebene Module.
|
||||
*/
|
||||
export function darf(zugang: Zugang | null, modul: Modul, art: Zugriffsart = 'lesen'): boolean {
|
||||
if (!zugang) return false
|
||||
|
||||
if (zugang.rolle === 'BETREUER' || zugang.rolle === 'ADMIN') return true
|
||||
|
||||
if (zugang.rolle === 'MITLESER') {
|
||||
if (art === 'schreiben') return false
|
||||
return zugang.freigegebeneModule.includes(modul)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/** Kurzform fuer die Oberflaeche. */
|
||||
export function darfSchreiben(zugang: Zugang | null, modul: Modul): boolean {
|
||||
return darf(zugang, modul, 'schreiben')
|
||||
}
|
||||
Reference in New Issue
Block a user