// WS4-Abnahmetest (Option C) — Passwort/Reset/Session-Kill-Switch an der Identity. // // Prüft, dass die Self-Service-/Reset-Bausteine auf der GLOBALEN Identity arbeiten // (nicht mehr auf der per-Mandant-Mitgliedschaft): // 1. findAccountByEmail("tenant") liefert die Identity (id === Identity.id). // 2. writePasswordHash/readPasswordHash operieren auf der Identity; ein neuer // Hash wirkt sofort im Login (authorizeTenantCredentials). // 3. invalidateSessions({type:"identity"}) setzt die Kill-Switch-Marke an der Identity. // 4. Der Mandanten-Admin-Reset (resetUserPassword/resetTenantUserPassword) ist // entfernt — durch tsc/lint bereits abgesichert (keine Referenzen mehr). // // Lauf: npx tsx scripts/test-identity-account.ts (setzt den Demo-Seed voraus) import "dotenv/config"; import { prisma } from "../src/server/db"; import { hashPassword, verifyPassword } from "../src/server/password"; import { authorizeTenantCredentials } from "../src/server/auth"; import { findAccountByEmail, readPasswordHash, writePasswordHash } from "../src/server/auth-selfservice"; import { invalidateSessions, getSessionsValidAfter } from "../src/server/sessions"; const EMAIL = "ws4-account@demo.example"; const PW1 = "Start-Passwort-1!"; const PW2 = "Neues-Passwort-2!"; let failures = 0; const ok = (cond: boolean, msg: string) => { console.log(`${cond ? "✓" : "✗ FEHLER"} ${msg}`); if (!cond) failures++; }; async function cleanup() { await prisma.user.deleteMany({ where: { email: EMAIL } }); await prisma.identity.deleteMany({ where: { email: EMAIL } }); } async function main() { await cleanup(); const demo = await prisma.tenant.findUniqueOrThrow({ where: { slug: "demo" } }); const role = await prisma.role.findFirst({ where: { tenantId: demo.id, key: "user" } }); const identity = await prisma.identity.create({ data: { email: EMAIL, passwordHash: await hashPassword(PW1) } }); await prisma.user.create({ data: { tenantId: demo.id, identityId: identity.id, email: EMAIL, name: "WS4 Konto", status: "ACTIVE", ...(role ? { userRoles: { create: [{ roleId: role.id }] } } : {}), }, }); console.log("\n— 1) findAccountByEmail liefert die Identity —"); const acc = await findAccountByEmail("tenant", EMAIL); ok(acc?.id === identity.id, `Account.id === Identity.id (${acc?.id === identity.id})`); ok(acc?.email === EMAIL, "Account.email = Identity.email"); console.log("\n— 2) Passwort lebt an der Identity; Login nutzt es —"); const loginOld = await authorizeTenantCredentials({ email: EMAIL, password: PW1, tenant: "demo" }); ok(!!loginOld && loginOld.identityId === identity.id, "Login mit Start-Passwort erfolgreich"); // Passwort an der Identity ändern (Reset-Baustein). await writePasswordHash("tenant", identity.id, await hashPassword(PW2)); const storedHash = await readPasswordHash("tenant", identity.id); ok(!!storedHash && (await verifyPassword(storedHash, PW2)), "readPasswordHash liefert den neuen Identity-Hash"); const loginOldAfter = await authorizeTenantCredentials({ email: EMAIL, password: PW1, tenant: "demo" }); ok(loginOldAfter === null, "altes Passwort funktioniert nach Änderung NICHT mehr"); const loginNew = await authorizeTenantCredentials({ email: EMAIL, password: PW2, tenant: "demo" }); ok(!!loginNew, "neues Passwort funktioniert sofort (Passwort an Identity, nicht Membership)"); console.log("\n— 3) Session-Kill-Switch an der Identity —"); const before = await getSessionsValidAfter({ type: "identity", id: identity.id }); ok(before === null, "vor Invalidierung keine Marke"); await invalidateSessions({ type: "identity", id: identity.id }); const after = await getSessionsValidAfter({ type: "identity", id: identity.id }); ok(after instanceof Date, "invalidateSessions setzt sessionsValidAfter an der Identity"); // Der Kill-Switch existiert NUR an der Identity — die Membership hat kein solches Feld // mehr (Contract-Migration hat die User-Auth-Spalten entfernt). await cleanup(); } main() .then(async () => { await prisma.$disconnect(); if (failures > 0) { console.error(`\n✗ ${failures} Testfall/-fälle fehlgeschlagen.`); process.exit(1); } console.log("\nOK"); }) .catch(async (e) => { console.error(e); await cleanup().catch(() => {}); await prisma.$disconnect(); process.exit(1); });