Basis: Certvia dev@a48c5fb als Fundament für Craftvia
CI / build-and-check (push) Canceled after 0s
CI / audit (push) Canceled after 0s
CI / sbom (push) Canceled after 0s

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:
2026-09-14 11:05:39 +02:00
co-authored by Claude Opus 5
commit c8e6f30a27
720 changed files with 140143 additions and 0 deletions
+83
View File
@@ -0,0 +1,83 @@
// WS1-Abnahmetest (Option C) — Mandanten-Login gegen die globale Identity.
//
// Prüft `authorizeTenantCredentials` (src/server/auth.ts) gegen die Seed-DB:
// 1. Single-Membership: admin@demo (ohne Slug) landet in seinem Mandanten.
// 2. Falsches Passwort → null (kein Login).
// 3. Multi-Membership OHNE Slug → null (Auswahlseite /select-tenant = WS2).
// 4. Multi-Membership MIT Slug → richtiger Mandant + mandantenspezifische Rechte.
// 5. Falscher/fremder Slug → null.
// 6. Unbekannte E-Mail → null.
// 7. Session-Shape trägt identityId, activeMembershipId und memberships[].
//
// Lauf: npx tsx scripts/test-identity-login.ts (setzt den Demo-Seed voraus)
import "dotenv/config";
import { prisma } from "../src/server/db";
import { authorizeTenantCredentials } from "../src/server/auth";
const PW = "Demo1234!";
let failures = 0;
const ok = (cond: boolean, msg: string) => {
console.log(`${cond ? "✓" : "✗ FEHLER"} ${msg}`);
if (!cond) failures++;
};
async function main() {
console.log("\n— 1) Single-Membership: admin@demo ohne Slug —");
const admin = await authorizeTenantCredentials({ email: "admin@demo.example", password: PW });
ok(!!admin, "admin@demo meldet sich an");
ok(admin?.tenantSlug === "demo", `aktiver Mandant = demo (${admin?.tenantSlug})`);
ok(!!admin?.identityId, "Session trägt identityId");
ok(admin?.activeMembershipId === admin?.id, "activeMembershipId = User.id der Mitgliedschaft");
ok((admin?.memberships?.length ?? 0) === 1, `memberships-Liste = 1 (${admin?.memberships?.length})`);
ok(admin?.roles.includes("tenant-admin") ?? false, "Rolle tenant-admin im aktiven Mandanten");
console.log("\n— 2) Falsches Passwort → null —");
const wrong = await authorizeTenantCredentials({ email: "admin@demo.example", password: "falsch!!" });
ok(wrong === null, "falsches Passwort ⇒ null");
// Zähler zurücksetzen, damit wiederholte Läufe die Identity nicht sperren.
await prisma.identity.update({ where: { email: "admin@demo.example" }, data: { failedLogins: 0, lockedUntil: null } });
console.log("\n— 3) Multi-Membership ohne Slug → Session ohne aktiven Mandanten (WS2) —");
const noTenant = await authorizeTenantCredentials({ email: "multi@demo.example", password: PW });
ok(!!noTenant && noTenant.tenantId === "", "multi@ ohne Slug ⇒ Session ohne aktiven Mandanten (tenantId leer → /select-tenant)");
ok((noTenant?.memberships?.length ?? 0) === 2, `memberships-Liste = 2 im No-Tenant-State (${noTenant?.memberships?.length})`);
console.log("\n— 4) Multi-Membership mit Slug → korrekter Mandant + Rechte —");
const inDemo2 = await authorizeTenantCredentials({ email: "multi@demo.example", password: PW, tenant: "demo2" });
ok(inDemo2?.tenantSlug === "demo2", `Slug demo2 ⇒ aktiver Mandant demo2 (${inDemo2?.tenantSlug})`);
ok(inDemo2?.roles.includes("tenant-admin") ?? false, "in demo2: Rolle tenant-admin");
ok((inDemo2?.memberships?.length ?? 0) === 2, `memberships-Liste = 2 (${inDemo2?.memberships?.length})`);
const inDemo = await authorizeTenantCredentials({ email: "multi@demo.example", password: PW, tenant: "demo" });
ok(inDemo?.tenantSlug === "demo", `Slug demo ⇒ aktiver Mandant demo (${inDemo?.tenantSlug})`);
ok(inDemo?.roles.includes("user") ?? false, "in demo: Rolle user");
// Rechte je aktivem Mandant verschieden (tenant-admin demo2 ≠ user demo).
ok(
JSON.stringify([...(inDemo?.permissions ?? [])].sort()) !== JSON.stringify([...(inDemo2?.permissions ?? [])].sort()),
"Rechte je aktivem Mandant verschieden (demo user ≠ demo2 admin)"
);
console.log("\n— 5) Fremder/falscher Slug → null —");
const wrongSlug = await authorizeTenantCredentials({ email: "admin@demo.example", password: PW, tenant: "gibtsnicht" });
ok(wrongSlug === null, "unbekannter Slug ⇒ null");
console.log("\n— 6) Unbekannte E-Mail → null —");
const unknown = await authorizeTenantCredentials({ email: "gibtsnicht@demo.example", password: PW });
ok(unknown === null, "unbekannte E-Mail ⇒ null");
}
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 prisma.$disconnect();
process.exit(1);
});