- Auth.js v5 mit Credentials-Provider: Argon2id-Verifikation, JWT-Session mit Mandanten-Kontext, Rollen und Permissions; Login-/Logout-Flow (deutsch) - RBAC-Katalog (28 Permissions, 5 Rollen-Blueprints) mit serverseitigem requirePermission; Seed legt Demo-Mandant und 4 Demo-Nutzer an - Route-Gate über Next-16-proxy.ts (UX-Ebene), autoritative Prüfung serverseitig via requireSession/requirePermission - Prisma-Migrationen: init + Row-Level-Security-Policies (zweite Verteidigungslinie, Scharfschaltung in Härtungs-Iteration dokumentiert) - i18n-Gerüst mit next-intl (de aktiv, en vorbereitet), Audit-Log-Helper - Login/Logout end-to-end im Browser verifiziert; Build/Lint/Typecheck grün Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
93 lines
2.9 KiB
TypeScript
93 lines
2.9 KiB
TypeScript
import "dotenv/config";
|
|
import { PrismaClient } from "@prisma/client";
|
|
import { PrismaPg } from "@prisma/adapter-pg";
|
|
import { hash } from "@node-rs/argon2";
|
|
import { PERMISSIONS, ROLE_DEFS } from "../src/server/rbac";
|
|
|
|
/**
|
|
* Seed: global permission catalog + demo tenant with roles and users.
|
|
* Idempotent (upserts) — safe to run repeatedly.
|
|
*/
|
|
|
|
const prisma = new PrismaClient({
|
|
adapter: new PrismaPg({ connectionString: process.env.DATABASE_URL }),
|
|
});
|
|
|
|
const DEMO_PASSWORD = "Demo1234!";
|
|
|
|
async function main() {
|
|
// 1. Global permission catalog
|
|
for (const key of PERMISSIONS) {
|
|
await prisma.permission.upsert({ where: { key }, update: {}, create: { key } });
|
|
}
|
|
console.log(`✔ ${PERMISSIONS.length} Permissions`);
|
|
|
|
// 2. Demo tenant
|
|
const tenant = await prisma.tenant.upsert({
|
|
where: { slug: "demo" },
|
|
update: {},
|
|
create: { name: "Demo GmbH", slug: "demo" },
|
|
});
|
|
|
|
// 3. Roles from blueprints, with permission bundles
|
|
for (const [key, def] of Object.entries(ROLE_DEFS)) {
|
|
const role = await prisma.role.upsert({
|
|
where: { tenantId_key: { tenantId: tenant.id, key } },
|
|
update: { name: def.name },
|
|
create: { tenantId: tenant.id, key, name: def.name },
|
|
});
|
|
const perms = await prisma.permission.findMany({
|
|
where: { key: { in: [...def.permissions] } },
|
|
});
|
|
for (const p of perms) {
|
|
await prisma.rolePermission.upsert({
|
|
where: { roleId_permissionId: { roleId: role.id, permissionId: p.id } },
|
|
update: {},
|
|
create: { roleId: role.id, permissionId: p.id },
|
|
});
|
|
}
|
|
}
|
|
console.log(`✔ Mandant "demo" mit ${Object.keys(ROLE_DEFS).length} Rollen`);
|
|
|
|
// 4. Demo users
|
|
const passwordHash = await hash(DEMO_PASSWORD);
|
|
const demoUsers: { email: string; name: string; roles: string[] }[] = [
|
|
{ email: "admin@demo.example", name: "Anna Admin", roles: ["tenant-admin", "isb"] },
|
|
{ email: "auditor@demo.example", name: "Axel Auditor", roles: ["auditor"] },
|
|
{ email: "owner@demo.example", name: "Oskar Owner", roles: ["owner"] },
|
|
{ email: "user@demo.example", name: "Ulla User", roles: ["user"] },
|
|
];
|
|
|
|
for (const u of demoUsers) {
|
|
const user = await prisma.user.upsert({
|
|
where: { tenantId_email: { tenantId: tenant.id, email: u.email } },
|
|
update: {},
|
|
create: {
|
|
tenantId: tenant.id,
|
|
email: u.email,
|
|
name: u.name,
|
|
passwordHash,
|
|
},
|
|
});
|
|
const roles = await prisma.role.findMany({
|
|
where: { tenantId: tenant.id, key: { in: u.roles } },
|
|
});
|
|
for (const r of roles) {
|
|
await prisma.userRole.upsert({
|
|
where: { userId_roleId: { userId: user.id, roleId: r.id } },
|
|
update: {},
|
|
create: { userId: user.id, roleId: r.id },
|
|
});
|
|
}
|
|
}
|
|
console.log(`✔ ${demoUsers.length} Demo-Nutzer (Passwort: ${DEMO_PASSWORD})`);
|
|
}
|
|
|
|
main()
|
|
.then(() => prisma.$disconnect())
|
|
.catch(async (e) => {
|
|
console.error(e);
|
|
await prisma.$disconnect();
|
|
process.exit(1);
|
|
});
|