// R-P2a S4 — serverseitige Sessions (Postgres als Source-of-Truth, Redis-Cache davor, AD-4). // Eigenes Modul-Schema (AD-21): neue Modelle NICHT in accounts.prisma/core.prisma; die // Rueck-Relation auf User bleibt minimal (User.userSessions). // // Das Login-Cookie traegt einen opaken Zufalls-Token; gespeichert wird nur dessen SHA-256-Hash // (tokenHash). Eine Session ist gueltig, solange revokedAt = null und expiresAt in der Zukunft // liegt. Die Geraete-Liste (Settings > Sicherheit), Revoke-einzeln und "ueberall abmelden" // arbeiten auf diesen Zeilen. model UserSession { id String @id @default(cuid()) userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) // Nur der Hash des Cookie-Tokens wird persistiert (kein Klartext-Token in der DB). tokenHash String @unique createdAt DateTime @default(now()) lastSeenAt DateTime @default(now()) expiresAt DateTime revokedAt DateTime? // Trust-Device (S5/2FA): markiert eine Session als vertrauenswuerdig, damit fuer sie kein // erneuter TOTP-Code verlangt wird. trustedAt DateTime? // Kontext fuer die Geraete-Liste. AD-22: KEINE Klartext-IP persistieren — nur der Hash (hashIp) // zur "gleiches Geraet"-Wiedererkennung; angezeigt wird das Land (approxLocation via geoip). ipHash String? userAgent String? deviceLabel String? approxLocation String? browserFingerprint String? @@index([userId]) @@index([userId, revokedAt]) @@index([expiresAt]) } // D1 (Trust-Device) — "diesem Geraet vertrauen" beim 2FA-Login. UEBERLEBT den Logout (anders als // UserSession.trustedAt, das per Session lebt) -> eigene, langlebige Tabelle. Das Geraete-Cookie // traegt einen opaken Zufalls-Token; persistiert wird nur dessen SHA-256-Hash (tokenHash). Ein Geraet // ueberspringt den zweiten Faktor, solange revokedAt = null und expiresAt in der Zukunft liegt. Die // Geraete-Liste (Settings > Sicherheit), Revoke-einzeln und "ueberall vergessen" arbeiten auf diesen // Zeilen -> erfuellt den DoD-Screen "Trust-Device-Revoke" (R-P2a) widerrufbar statt nur Cookie. model TrustedDevice { id String @id @default(cuid()) userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) // Nur der Hash des Cookie-Tokens wird persistiert (kein Klartext-Token in der DB). tokenHash String @unique createdAt DateTime @default(now()) lastUsedAt DateTime @default(now()) expiresAt DateTime revokedAt DateTime? // Kontext fuer die Geraete-Liste. AD-22: KEINE Klartext-IP persistieren — nur der Hash (hashIp); // angezeigt wird das Land (approxLocation via geoip). ipHash String? userAgent String? deviceLabel String? approxLocation String? @@index([userId]) @@index([userId, revokedAt]) @@index([expiresAt]) }