Basis: Certvia dev@a48c5fb als Fundament für Craftvia
Unveränderter Stand von certvia/dev (a48c5fb) plus Craftvia-Spezifikation und Brandbook unter docs/craftvia/. ISMS-Module werden im Folgecommit entfernt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
// 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);
|
||||
});
|
||||
Reference in New Issue
Block a user