{t("title")}
+ +{t("welcome", { name: session.user.name ?? "" })}
+-
+
- {t("tenant")} +
- {session.user.tenantSlug} +
- {t("roles")} +
- {session.user.roles.join(", ")} +
{t("placeholder")}
+diff --git a/.claude/launch.json b/.claude/launch.json
new file mode 100644
index 0000000..c34d187
--- /dev/null
+++ b/.claude/launch.json
@@ -0,0 +1,11 @@
+{
+ "version": "0.0.1",
+ "configurations": [
+ {
+ "name": "isms-dev",
+ "runtimeExecutable": "npm",
+ "runtimeArgs": ["run", "dev"],
+ "port": 3000
+ }
+ ]
+}
diff --git a/AGENTS.md b/AGENTS.md
index f87cd61..fc05a5d 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -25,6 +25,12 @@ Multi-Tenant-SaaS für Informationssicherheits-Managementsysteme (ISO/IEC 27001:
- Tests: Vitest (Unit), Playwright (E2E). `npm run lint` und `npm run build` müssen vor jedem Commit grün sein.
- Sprache: UI und Fachbegriffe Deutsch; Code (Bezeichner, Kommentare) Englisch.
+## Offene Härtungspunkte (Iteration 8)
+
+- RLS scharfschalten: App-Verbindung auf DB-Rolle `isms_app` umstellen und `app.tenant_id` pro Transaktion setzen (Policies existieren bereits, siehe Migration `row_level_security`).
+- TOTP-2FA (Schema-Feld `mfa_secret` ist vorbereitet).
+- Rollenänderungen wirken erst beim nächsten Login (Permissions liegen im JWT).
+
## Entwicklung
```bash
diff --git a/docker-compose.yml b/docker-compose.yml
index 6af1740..7c7d34b 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -13,7 +13,9 @@ services:
condition: service_started
restart: unless-stopped
+ # Wird in Iteration 4 aktiviert (BullMQ-Scheduler, siehe docs/SPEC.md §4.5)
worker:
+ profiles: ["worker"]
build: .
command: ["npm", "run", "worker"]
env_file: .env
diff --git a/messages/de.json b/messages/de.json
new file mode 100644
index 0000000..cd521cd
--- /dev/null
+++ b/messages/de.json
@@ -0,0 +1,23 @@
+{
+ "common": {
+ "appName": "ISMS-Tool",
+ "logout": "Abmelden"
+ },
+ "login": {
+ "title": "Anmelden",
+ "subtitle": "Melde dich mit deinem Firmenkonto an.",
+ "email": "E-Mail-Adresse",
+ "password": "Passwort",
+ "tenant": "Organisation (optional)",
+ "tenantHint": "Nur nötig, wenn deine E-Mail in mehreren Organisationen existiert.",
+ "submit": "Anmelden",
+ "error": "Anmeldung fehlgeschlagen. Bitte prüfe E-Mail, Passwort und ggf. die Organisation."
+ },
+ "dashboard": {
+ "title": "Dashboard",
+ "welcome": "Willkommen, {name}",
+ "tenant": "Mandant",
+ "roles": "Rollen",
+ "placeholder": "Die Modul-Dashboards (Risiken, Aufgaben, SoA-Erfüllungsgrad) folgen in den nächsten Iterationen."
+ }
+}
diff --git a/messages/en.json b/messages/en.json
new file mode 100644
index 0000000..3591c35
--- /dev/null
+++ b/messages/en.json
@@ -0,0 +1,23 @@
+{
+ "common": {
+ "appName": "ISMS Tool",
+ "logout": "Sign out"
+ },
+ "login": {
+ "title": "Sign in",
+ "subtitle": "Sign in with your company account.",
+ "email": "E-mail address",
+ "password": "Password",
+ "tenant": "Organization (optional)",
+ "tenantHint": "Only needed if your e-mail exists in several organizations.",
+ "submit": "Sign in",
+ "error": "Sign-in failed. Please check e-mail, password and organization."
+ },
+ "dashboard": {
+ "title": "Dashboard",
+ "welcome": "Welcome, {name}",
+ "tenant": "Tenant",
+ "roles": "Roles",
+ "placeholder": "Module dashboards (risks, tasks, SoA coverage) follow in upcoming iterations."
+ }
+}
diff --git a/next.config.ts b/next.config.ts
index 566c88e..895f595 100644
--- a/next.config.ts
+++ b/next.config.ts
@@ -1,8 +1,11 @@
import type { NextConfig } from "next";
+import createNextIntlPlugin from "next-intl/plugin";
const nextConfig: NextConfig = {
// Standalone-Output für den Docker-Multi-Stage-Build (siehe Dockerfile)
output: "standalone",
};
-export default nextConfig;
+const withNextIntl = createNextIntlPlugin();
+
+export default withNextIntl(nextConfig);
diff --git a/package-lock.json b/package-lock.json
index 18f960c..b9813a0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,10 +8,13 @@
"name": "isms-tool",
"version": "0.1.0",
"dependencies": {
+ "@node-rs/argon2": "^2.0.2",
"@prisma/adapter-pg": "^7.8.0",
"@prisma/client": "^7.8.0",
"dotenv": "^17.4.2",
"next": "16.2.10",
+ "next-auth": "^5.0.0-beta.31",
+ "next-intl": "^4.13.1",
"react": "19.2.4",
"react-dom": "19.2.4",
"zod": "^4.4.3"
@@ -42,6 +45,35 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/@auth/core": {
+ "version": "0.41.2",
+ "resolved": "https://registry.npmjs.org/@auth/core/-/core-0.41.2.tgz",
+ "integrity": "sha512-Hx5MNBxN2fJTbJKGUKAA0wca43D0Akl3TvufY54Gn8lop7F+34vU1zA1pn0vQfIoVuLIrpfc2nkyjwIaPJMW7w==",
+ "license": "ISC",
+ "dependencies": {
+ "@panva/hkdf": "^1.2.1",
+ "jose": "^6.0.6",
+ "oauth4webapi": "^3.3.0",
+ "preact": "10.24.3",
+ "preact-render-to-string": "6.5.11"
+ },
+ "peerDependencies": {
+ "@simplewebauthn/browser": "^9.0.1",
+ "@simplewebauthn/server": "^9.0.2",
+ "nodemailer": "^7.0.7"
+ },
+ "peerDependenciesMeta": {
+ "@simplewebauthn/browser": {
+ "optional": true
+ },
+ "@simplewebauthn/server": {
+ "optional": true
+ },
+ "nodemailer": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@babel/code-frame": {
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
@@ -316,7 +348,6 @@
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
"integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==",
- "dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
@@ -338,7 +369,6 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
"integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==",
- "dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
@@ -931,6 +961,36 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
+ "node_modules/@formatjs/fast-memoize": {
+ "version": "3.1.6",
+ "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-3.1.6.tgz",
+ "integrity": "sha512-H5aexk1Le7T9TPmscacZ+1pR6CTa2n1wq+HDVGXhH8TzUlQQpeXzZs91dRtmFHrbeNbjPFPfQujUqm7MHgVoXQ==",
+ "license": "MIT"
+ },
+ "node_modules/@formatjs/icu-messageformat-parser": {
+ "version": "3.5.12",
+ "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-3.5.12.tgz",
+ "integrity": "sha512-YyzzxVgYJ8DELmmkhn0Yr0rUj0dTJFf9Jp628K3S0ysInBWxLVDOS8i3RP91cCp4DMK4WYb4cVMhWA9i4knSJg==",
+ "license": "MIT",
+ "dependencies": {
+ "@formatjs/icu-skeleton-parser": "2.1.10"
+ }
+ },
+ "node_modules/@formatjs/icu-skeleton-parser": {
+ "version": "2.1.10",
+ "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-2.1.10.tgz",
+ "integrity": "sha512-XuSva+8ZGawk8VnD5VD6UeH8KarQ/Z022zgjHDoHmlNiAewstXuuzXc0Hk5pGFSdG+nNw5bfJKXqj1ZXHn9yUA==",
+ "license": "MIT"
+ },
+ "node_modules/@formatjs/intl-localematcher": {
+ "version": "0.8.10",
+ "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.8.10.tgz",
+ "integrity": "sha512-P/IC3qws3jH+1fEs+o0RIFgXKRaQlFehjS5W0FPAqdo6hgzawLl+eD0q0JjheQ3XtoOe5n8WSYfX06KQZI/QJA==",
+ "license": "MIT",
+ "dependencies": {
+ "@formatjs/fast-memoize": "3.1.6"
+ }
+ },
"node_modules/@hono/node-server": {
"version": "1.19.11",
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.11.tgz",
@@ -1756,6 +1816,279 @@
"node": ">= 10"
}
},
+ "node_modules/@node-rs/argon2": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@node-rs/argon2/-/argon2-2.0.2.tgz",
+ "integrity": "sha512-t64wIsPEtNd4aUPuTAyeL2ubxATCBGmeluaKXEMAFk/8w6AJIVVkeLKMBpgLW6LU2t5cQxT+env/c6jxbtTQBg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10"
+ },
+ "optionalDependencies": {
+ "@node-rs/argon2-android-arm-eabi": "2.0.2",
+ "@node-rs/argon2-android-arm64": "2.0.2",
+ "@node-rs/argon2-darwin-arm64": "2.0.2",
+ "@node-rs/argon2-darwin-x64": "2.0.2",
+ "@node-rs/argon2-freebsd-x64": "2.0.2",
+ "@node-rs/argon2-linux-arm-gnueabihf": "2.0.2",
+ "@node-rs/argon2-linux-arm64-gnu": "2.0.2",
+ "@node-rs/argon2-linux-arm64-musl": "2.0.2",
+ "@node-rs/argon2-linux-x64-gnu": "2.0.2",
+ "@node-rs/argon2-linux-x64-musl": "2.0.2",
+ "@node-rs/argon2-wasm32-wasi": "2.0.2",
+ "@node-rs/argon2-win32-arm64-msvc": "2.0.2",
+ "@node-rs/argon2-win32-ia32-msvc": "2.0.2",
+ "@node-rs/argon2-win32-x64-msvc": "2.0.2"
+ }
+ },
+ "node_modules/@node-rs/argon2-android-arm-eabi": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@node-rs/argon2-android-arm-eabi/-/argon2-android-arm-eabi-2.0.2.tgz",
+ "integrity": "sha512-DV/H8p/jt40lrao5z5g6nM9dPNPGEHL+aK6Iy/og+dbL503Uj0AHLqj1Hk9aVUSCNnsDdUEKp4TVMi0YakDYKw==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@node-rs/argon2-android-arm64": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@node-rs/argon2-android-arm64/-/argon2-android-arm64-2.0.2.tgz",
+ "integrity": "sha512-1LKwskau+8O1ktKx7TbK7jx1oMOMt4YEXZOdSNIar1TQKxm6isZ0cRXgHLibPHEcNHgYRsJWDE9zvDGBB17QDg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@node-rs/argon2-darwin-arm64": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@node-rs/argon2-darwin-arm64/-/argon2-darwin-arm64-2.0.2.tgz",
+ "integrity": "sha512-3TTNL/7wbcpNju5YcqUrCgXnXUSbD7ogeAKatzBVHsbpjZQbNb1NDxDjqqrWoTt6XL3z9mJUMGwbAk7zQltHtA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@node-rs/argon2-darwin-x64": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@node-rs/argon2-darwin-x64/-/argon2-darwin-x64-2.0.2.tgz",
+ "integrity": "sha512-vNPfkLj5Ij5111UTiYuwgxMqE7DRbOS2y58O2DIySzSHbcnu+nipmRKg+P0doRq6eKIJStyBK8dQi5Ic8pFyDw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@node-rs/argon2-freebsd-x64": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@node-rs/argon2-freebsd-x64/-/argon2-freebsd-x64-2.0.2.tgz",
+ "integrity": "sha512-M8vQZk01qojQfCqQU0/O1j1a4zPPrz93zc9fSINY7Q/6RhQRBCYwDw7ltDCZXg5JRGlSaeS8cUXWyhPGar3cGg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@node-rs/argon2-linux-arm-gnueabihf": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-arm-gnueabihf/-/argon2-linux-arm-gnueabihf-2.0.2.tgz",
+ "integrity": "sha512-7EmmEPHLzcu0G2GDh30L6G48CH38roFC2dqlQJmtRCxs6no3tTE/pvgBGatTp/o2n2oyOJcfmgndVFcUpwMnww==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@node-rs/argon2-linux-arm64-gnu": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-arm64-gnu/-/argon2-linux-arm64-gnu-2.0.2.tgz",
+ "integrity": "sha512-6lsYh3Ftbk+HAIZ7wNuRF4SZDtxtFTfK+HYFAQQyW7Ig3LHqasqwfUKRXVSV5tJ+xTnxjqgKzvZSUJCAyIfHew==",
+ "cpu": [
+ "arm64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@node-rs/argon2-linux-arm64-musl": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-arm64-musl/-/argon2-linux-arm64-musl-2.0.2.tgz",
+ "integrity": "sha512-p3YqVMNT/4DNR67tIHTYGbedYmXxW9QlFmF39SkXyEbGQwpgSf6pH457/fyXBIYznTU/smnG9EH+C1uzT5j4hA==",
+ "cpu": [
+ "arm64"
+ ],
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@node-rs/argon2-linux-x64-gnu": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-x64-gnu/-/argon2-linux-x64-gnu-2.0.2.tgz",
+ "integrity": "sha512-ZM3jrHuJ0dKOhvA80gKJqBpBRmTJTFSo2+xVZR+phQcbAKRlDMSZMFDiKbSTnctkfwNFtjgDdh5g1vaEV04AvA==",
+ "cpu": [
+ "x64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@node-rs/argon2-linux-x64-musl": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-x64-musl/-/argon2-linux-x64-musl-2.0.2.tgz",
+ "integrity": "sha512-of5uPqk7oCRF/44a89YlWTEfjsftPywyTULwuFDKyD8QtVZoonrJR6ZWvfFE/6jBT68S0okAkAzzMEdBVWdxWw==",
+ "cpu": [
+ "x64"
+ ],
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@node-rs/argon2-wasm32-wasi": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@node-rs/argon2-wasm32-wasi/-/argon2-wasm32-wasi-2.0.2.tgz",
+ "integrity": "sha512-U3PzLYKSQYzTERstgtHLd4ZTkOF9co57zTXT77r0cVUsleGZOrd6ut7rHzeWwoJSiHOVxxa0OhG1JVQeB7lLoQ==",
+ "cpu": [
+ "wasm32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@napi-rs/wasm-runtime": "^0.2.5"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@node-rs/argon2-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": {
+ "version": "0.2.12",
+ "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz",
+ "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.4.3",
+ "@emnapi/runtime": "^1.4.3",
+ "@tybys/wasm-util": "^0.10.0"
+ }
+ },
+ "node_modules/@node-rs/argon2-win32-arm64-msvc": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@node-rs/argon2-win32-arm64-msvc/-/argon2-win32-arm64-msvc-2.0.2.tgz",
+ "integrity": "sha512-Eisd7/NM0m23ijrGr6xI2iMocdOuyl6gO27gfMfya4C5BODbUSP7ljKJ7LrA0teqZMdYHesRDzx36Js++/vhiQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@node-rs/argon2-win32-ia32-msvc": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@node-rs/argon2-win32-ia32-msvc/-/argon2-win32-ia32-msvc-2.0.2.tgz",
+ "integrity": "sha512-GsE2ezwAYwh72f9gIjbGTZOf4HxEksb5M2eCaj+Y5rGYVwAdt7C12Q2e9H5LRYxWcFvLH4m4jiSZpQQ4upnPAQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@node-rs/argon2-win32-x64-msvc": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@node-rs/argon2-win32-x64-msvc/-/argon2-win32-x64-msvc-2.0.2.tgz",
+ "integrity": "sha512-cJxWXanH4Ew9CfuZ4IAEiafpOBCe97bzoKowHCGk5lG/7kR4WF/eknnBlHW9m8q7t10mKq75kruPLtbSDqgRTw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -1804,6 +2137,340 @@
"node": ">=12.4.0"
}
},
+ "node_modules/@panva/hkdf": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.2.1.tgz",
+ "integrity": "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/panva"
+ }
+ },
+ "node_modules/@parcel/watcher": {
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz",
+ "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "dependencies": {
+ "detect-libc": "^2.0.3",
+ "is-glob": "^4.0.3",
+ "node-addon-api": "^7.0.0",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "@parcel/watcher-android-arm64": "2.5.6",
+ "@parcel/watcher-darwin-arm64": "2.5.6",
+ "@parcel/watcher-darwin-x64": "2.5.6",
+ "@parcel/watcher-freebsd-x64": "2.5.6",
+ "@parcel/watcher-linux-arm-glibc": "2.5.6",
+ "@parcel/watcher-linux-arm-musl": "2.5.6",
+ "@parcel/watcher-linux-arm64-glibc": "2.5.6",
+ "@parcel/watcher-linux-arm64-musl": "2.5.6",
+ "@parcel/watcher-linux-x64-glibc": "2.5.6",
+ "@parcel/watcher-linux-x64-musl": "2.5.6",
+ "@parcel/watcher-win32-arm64": "2.5.6",
+ "@parcel/watcher-win32-ia32": "2.5.6",
+ "@parcel/watcher-win32-x64": "2.5.6"
+ }
+ },
+ "node_modules/@parcel/watcher-android-arm64": {
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz",
+ "integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-darwin-arm64": {
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz",
+ "integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-darwin-x64": {
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz",
+ "integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-freebsd-x64": {
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz",
+ "integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-arm-glibc": {
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz",
+ "integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==",
+ "cpu": [
+ "arm"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-arm-musl": {
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz",
+ "integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==",
+ "cpu": [
+ "arm"
+ ],
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-arm64-glibc": {
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz",
+ "integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==",
+ "cpu": [
+ "arm64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-arm64-musl": {
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz",
+ "integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==",
+ "cpu": [
+ "arm64"
+ ],
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-x64-glibc": {
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz",
+ "integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==",
+ "cpu": [
+ "x64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-x64-musl": {
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz",
+ "integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==",
+ "cpu": [
+ "x64"
+ ],
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-win32-arm64": {
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz",
+ "integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-win32-ia32": {
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz",
+ "integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-win32-x64": {
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz",
+ "integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher/node_modules/picomatch": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
"node_modules/@prisma/adapter-pg": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/@prisma/adapter-pg/-/adapter-pg-7.8.0.tgz",
@@ -2192,6 +2859,12 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@schummar/icu-type-parser": {
+ "version": "1.21.5",
+ "resolved": "https://registry.npmjs.org/@schummar/icu-type-parser/-/icu-type-parser-1.21.5.tgz",
+ "integrity": "sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==",
+ "license": "MIT"
+ },
"node_modules/@standard-schema/spec": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
@@ -2199,6 +2872,222 @@
"devOptional": true,
"license": "MIT"
},
+ "node_modules/@swc/core-darwin-arm64": {
+ "version": "1.15.43",
+ "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.43.tgz",
+ "integrity": "sha512-v1aVuvXdo/BHxJzco9V2xpHrvwWmhfS8t6gziY5wJxd+Z2h8AeJRnAwPD8itCDaGXVBwJ/CaKfxEzTkG0Va0OA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-darwin-x64": {
+ "version": "1.15.43",
+ "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.43.tgz",
+ "integrity": "sha512-lp3d4Lamc8dt5huYdGLSR+9hLxmfr1jb0l+4XXG2zPqZwYWRN9R0U2qYoTrggiU2RWW0oV9VbWM3kBnqIc2kdQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-linux-arm-gnueabihf": {
+ "version": "1.15.43",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.43.tgz",
+ "integrity": "sha512-JWTQQELtsG5GgphDrr/XqqmM2pDN3cZqbMS0Mrg+iTiXL3F74sn/S2IyYE/5u4h2KLkTf9qQ7dXyxsbx7YzkeA==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-linux-arm64-gnu": {
+ "version": "1.15.43",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.43.tgz",
+ "integrity": "sha512-B4otJRdPWIsmiSBf0uG7Z/+vMWmkufjz5MmYxubwKuZazDW14Zd3symga1N62QR4RT+kEFeHEgsXfZGyn/w0hw==",
+ "cpu": [
+ "arm64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-linux-arm64-musl": {
+ "version": "1.15.43",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.43.tgz",
+ "integrity": "sha512-6zB6OnpViBxYy4tgY3v2i6AZY9fwkcHZ032UOwtwUuW1d19sdT07qF0kZe6/3UR1tUaK6jjg2rmVcUIBCEYVjQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "libc": [
+ "musl"
+ ],
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-linux-ppc64-gnu": {
+ "version": "1.15.43",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.43.tgz",
+ "integrity": "sha512-coxE1ZWdB3uSDVNoEtYNrRi/1epvckZx9cTJ8ICUxTMTxGk+yvQ/Twacp3ruZSaMPGCriUjP86C37VhaT6nyRg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-linux-s390x-gnu": {
+ "version": "1.15.43",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.43.tgz",
+ "integrity": "sha512-lXfLhs+LpBsD5inuYx+YDH5WsPPBQ95KPUiy8P5wq9ob9xKDZFqwNfU2QW6bGO8NqRO/H9JQomTSt5Yyh+FGfA==",
+ "cpu": [
+ "s390x"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-linux-x64-gnu": {
+ "version": "1.15.43",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.43.tgz",
+ "integrity": "sha512-07XnKwTmKy8TGOZG3D9fRnLWGynxPjwQnZLVmBFbo6F+7vHYzBIOuwXEhemrChBWb6yDNZsVCcMWCPX6FDD2xg==",
+ "cpu": [
+ "x64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-linux-x64-musl": {
+ "version": "1.15.43",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.43.tgz",
+ "integrity": "sha512-TJc+bsSIaBh+hZvZ5GRtW/K1bw66TJ9vsUwvVIsZdiWxU5ObLwZvfcnZ3UpgVfMnFibRes9uriJrQNBHEEogRQ==",
+ "cpu": [
+ "x64"
+ ],
+ "libc": [
+ "musl"
+ ],
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-win32-arm64-msvc": {
+ "version": "1.15.43",
+ "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.43.tgz",
+ "integrity": "sha512-jfd7s2/bUQYkOHLs+LWQNKZdmDa8+sufKLllhpWAhVQ2GDCwsHe3vR/j+OSiItZNtkzFuaawa3+SAKz9y5gYfw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-win32-ia32-msvc": {
+ "version": "1.15.43",
+ "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.43.tgz",
+ "integrity": "sha512-rLAE8JvucqEW1ZGohxPQrQWPBQeJG4+ypKbWfdlU/qmKScvCkxf9/Jxnzki1dkUQCQ7P5Enp13RlvqOlvx/32g==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-win32-x64-msvc": {
+ "version": "1.15.43",
+ "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.43.tgz",
+ "integrity": "sha512-h8MLDHZcfIukwQWj03rIJZx1I0E81AYj2X7J/nGErG4nz+QAv6G1Z+peotvinL3lqpbo32tLYSMFo32/ySzxKg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/counter": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
+ "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==",
+ "license": "Apache-2.0"
+ },
"node_modules/@swc/helpers": {
"version": "0.5.15",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
@@ -2208,6 +3097,15 @@
"tslib": "^2.8.0"
}
},
+ "node_modules/@swc/types": {
+ "version": "0.1.27",
+ "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.27.tgz",
+ "integrity": "sha512-K6h3iUlqeM946U4sXFYeahefR1YBbXJvko+hv8WS8/0BNJ4OHiHRywMnQUJCqkR7Y9+hqQ1TvEpiKqUhz7NEFg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@swc/counter": "^0.1.3"
+ }
+ },
"node_modules/@tailwindcss/node": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.2.tgz",
@@ -2495,7 +3393,6 @@
"version": "0.10.3",
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz",
"integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==",
- "dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
@@ -3976,7 +4873,6 @@
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
- "devOptional": true,
"license": "Apache-2.0",
"engines": {
"node": ">=8"
@@ -5357,6 +6253,21 @@
"url": "https://opencollective.com/express"
}
},
+ "node_modules/icu-minify": {
+ "version": "4.13.1",
+ "resolved": "https://registry.npmjs.org/icu-minify/-/icu-minify-4.13.1.tgz",
+ "integrity": "sha512-nFYW2im0WJ3RUVZwabd71J8QTZRtkK1xxZBY7klg7a6KS/os17LZSj9q1VhbRSnk3S8Mv2I7F1izr/aEJ6cUsw==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/amannn"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@formatjs/icu-messageformat-parser": "^3.4.0"
+ }
+ },
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@@ -5409,6 +6320,16 @@
"node": ">= 0.4"
}
},
+ "node_modules/intl-messageformat": {
+ "version": "11.2.9",
+ "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-11.2.9.tgz",
+ "integrity": "sha512-cGzymZerpDhVXRKjKLgXKda9gI29TU2o88L7gwNMHp3WZVxA/0c5tX52udXbW9JklDApolvMXZG6Dhhdz5eirA==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@formatjs/fast-memoize": "3.1.6",
+ "@formatjs/icu-messageformat-parser": "3.5.12"
+ }
+ },
"node_modules/is-array-buffer": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
@@ -5587,7 +6508,6 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@@ -5633,7 +6553,6 @@
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"is-extglob": "^2.1.1"
@@ -5889,6 +6808,15 @@
"jiti": "lib/jiti-cli.mjs"
}
},
+ "node_modules/jose": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz",
+ "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/panva"
+ }
+ },
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -6517,6 +7445,15 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/negotiator": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
+ "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/next": {
"version": "16.2.10",
"resolved": "https://registry.npmjs.org/next/-/next-16.2.10.tgz",
@@ -6570,6 +7507,110 @@
}
}
},
+ "node_modules/next-auth": {
+ "version": "5.0.0-beta.31",
+ "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-5.0.0-beta.31.tgz",
+ "integrity": "sha512-1OBgCKPzo+S7UWWMp3xgvGvIJ0OpV7B3vR4ZDRqD9a4Ch+OT6dakLXG9ivhtmIWVa71nTSXattOHyCg8sNi8/Q==",
+ "license": "ISC",
+ "dependencies": {
+ "@auth/core": "0.41.2"
+ },
+ "peerDependencies": {
+ "@simplewebauthn/browser": "^9.0.1",
+ "@simplewebauthn/server": "^9.0.2",
+ "next": "^14.0.0-0 || ^15.0.0 || ^16.0.0",
+ "nodemailer": "^7.0.7",
+ "react": "^18.2.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@simplewebauthn/browser": {
+ "optional": true
+ },
+ "@simplewebauthn/server": {
+ "optional": true
+ },
+ "nodemailer": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/next-intl": {
+ "version": "4.13.1",
+ "resolved": "https://registry.npmjs.org/next-intl/-/next-intl-4.13.1.tgz",
+ "integrity": "sha512-aS8KTA+nNhSNJJBlIhxgvU135WzoObwzFwav4wTDti/Gmhxqe0fs/Q343igo8Z7HGqPB/xgmoagwySZAlHmIfA==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/amannn"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@formatjs/intl-localematcher": "^0.8.1",
+ "@parcel/watcher": "^2.4.1",
+ "@swc/core": "^1.15.2",
+ "icu-minify": "^4.13.1",
+ "negotiator": "^1.0.0",
+ "next-intl-swc-plugin-extractor": "^4.13.1",
+ "po-parser": "^2.1.1",
+ "use-intl": "^4.13.1"
+ },
+ "peerDependencies": {
+ "next": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/next-intl-swc-plugin-extractor": {
+ "version": "4.13.1",
+ "resolved": "https://registry.npmjs.org/next-intl-swc-plugin-extractor/-/next-intl-swc-plugin-extractor-4.13.1.tgz",
+ "integrity": "sha512-RhlH2DR1ViEXzcX7G3tDXAvzNrBL2Ph54Hq/q/9oP9eXQV/okh3UQpA/lx2k9U5Ck83CZOSgH0eFTNi5U+zyXw==",
+ "license": "MIT"
+ },
+ "node_modules/next-intl/node_modules/@swc/core": {
+ "version": "1.15.43",
+ "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.43.tgz",
+ "integrity": "sha512-1CuKjFkPxIgGdeHVuNbkxmBxkcbdc08u0aiI43pFq6yY1tTVKmXT9hFEooyyKs/sJ3xf1GPHyEwTtk9Xl8dvQw==",
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@swc/counter": "^0.1.3",
+ "@swc/types": "^0.1.27"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/swc"
+ },
+ "optionalDependencies": {
+ "@swc/core-darwin-arm64": "1.15.43",
+ "@swc/core-darwin-x64": "1.15.43",
+ "@swc/core-linux-arm-gnueabihf": "1.15.43",
+ "@swc/core-linux-arm64-gnu": "1.15.43",
+ "@swc/core-linux-arm64-musl": "1.15.43",
+ "@swc/core-linux-ppc64-gnu": "1.15.43",
+ "@swc/core-linux-s390x-gnu": "1.15.43",
+ "@swc/core-linux-x64-gnu": "1.15.43",
+ "@swc/core-linux-x64-musl": "1.15.43",
+ "@swc/core-win32-arm64-msvc": "1.15.43",
+ "@swc/core-win32-ia32-msvc": "1.15.43",
+ "@swc/core-win32-x64-msvc": "1.15.43"
+ },
+ "peerDependencies": {
+ "@swc/helpers": ">=0.5.17"
+ },
+ "peerDependenciesMeta": {
+ "@swc/helpers": {
+ "optional": true
+ }
+ }
+ },
"node_modules/next/node_modules/postcss": {
"version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
@@ -6598,6 +7639,12 @@
"node": "^10 || ^12 || >=14"
}
},
+ "node_modules/node-addon-api": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
+ "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
+ "license": "MIT"
+ },
"node_modules/node-exports-info": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.2.tgz",
@@ -6627,6 +7674,15 @@
"node": ">=18"
}
},
+ "node_modules/oauth4webapi": {
+ "version": "3.8.6",
+ "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.8.6.tgz",
+ "integrity": "sha512-iwemM91xz8nryHti2yTmg5fhyEMVOkOXwHNqbvcATjyajb5oQxCQzrNOA6uElRHuMhQQTKUyFKV9y/CNyg25BQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/panva"
+ }
+ },
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -7008,6 +8064,12 @@
"pathe": "^2.0.3"
}
},
+ "node_modules/po-parser": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/po-parser/-/po-parser-2.1.1.tgz",
+ "integrity": "sha512-ECF4zHLbUItpUgE3OTtLKlPjeBN+fKEczj2zYjDfCGOzicNs0GK3Vg2IoAYwx7LH/XYw43fZQP6xnZ4TkNxSLQ==",
+ "license": "MIT"
+ },
"node_modules/possible-typed-array-names": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
@@ -7100,6 +8162,25 @@
"node": ">=0.10.0"
}
},
+ "node_modules/preact": {
+ "version": "10.24.3",
+ "resolved": "https://registry.npmjs.org/preact/-/preact-10.24.3.tgz",
+ "integrity": "sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/preact"
+ }
+ },
+ "node_modules/preact-render-to-string": {
+ "version": "6.5.11",
+ "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-6.5.11.tgz",
+ "integrity": "sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "preact": ">=10"
+ }
+ },
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -8353,6 +9434,27 @@
"punycode": "^2.1.0"
}
},
+ "node_modules/use-intl": {
+ "version": "4.13.1",
+ "resolved": "https://registry.npmjs.org/use-intl/-/use-intl-4.13.1.tgz",
+ "integrity": "sha512-UU5C3zAC7yVg3m7rq5C8VF5J5jhAfvS19Wi9bPNCB9xB7jQYBsUcrqfdxs4Mxl9XR3x6BDB5K++iAw7/rcm3gg==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/amannn"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@formatjs/fast-memoize": "^3.1.0",
+ "@schummar/icu-type-parser": "1.21.5",
+ "icu-minify": "^4.13.1",
+ "intl-messageformat": "^11.1.0"
+ },
+ "peerDependencies": {
+ "react": "^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0"
+ }
+ },
"node_modules/valibot": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/valibot/-/valibot-1.2.0.tgz",
diff --git a/package.json b/package.json
index 7e938d3..04d29c9 100644
--- a/package.json
+++ b/package.json
@@ -9,10 +9,13 @@
"lint": "eslint"
},
"dependencies": {
+ "@node-rs/argon2": "^2.0.2",
"@prisma/adapter-pg": "^7.8.0",
"@prisma/client": "^7.8.0",
"dotenv": "^17.4.2",
"next": "16.2.10",
+ "next-auth": "^5.0.0-beta.31",
+ "next-intl": "^4.13.1",
"react": "19.2.4",
"react-dom": "19.2.4",
"zod": "^4.4.3"
diff --git a/prisma/migrations/20260702095800_init/migration.sql b/prisma/migrations/20260702095800_init/migration.sql
new file mode 100644
index 0000000..2e56fd5
--- /dev/null
+++ b/prisma/migrations/20260702095800_init/migration.sql
@@ -0,0 +1,131 @@
+-- CreateExtension
+CREATE EXTENSION IF NOT EXISTS "vector";
+
+-- CreateEnum
+CREATE TYPE "TenantStatus" AS ENUM ('ACTIVE', 'SUSPENDED');
+
+-- CreateEnum
+CREATE TYPE "UserStatus" AS ENUM ('ACTIVE', 'INVITED', 'LOCKED', 'DEACTIVATED');
+
+-- CreateTable
+CREATE TABLE "tenants" (
+ "id" TEXT NOT NULL,
+ "name" TEXT NOT NULL,
+ "slug" TEXT NOT NULL,
+ "status" "TenantStatus" NOT NULL DEFAULT 'ACTIVE',
+ "config" JSONB NOT NULL DEFAULT '{}',
+ "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updated_at" TIMESTAMP(3) NOT NULL,
+
+ CONSTRAINT "tenants_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "users" (
+ "id" TEXT NOT NULL,
+ "tenant_id" TEXT NOT NULL,
+ "email" TEXT NOT NULL,
+ "password_hash" TEXT NOT NULL,
+ "name" TEXT NOT NULL,
+ "status" "UserStatus" NOT NULL DEFAULT 'ACTIVE',
+ "mfa_secret" TEXT,
+ "is_platform_admin" BOOLEAN NOT NULL DEFAULT false,
+ "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updated_at" TIMESTAMP(3) NOT NULL,
+
+ CONSTRAINT "users_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "roles" (
+ "id" TEXT NOT NULL,
+ "tenant_id" TEXT NOT NULL,
+ "key" TEXT NOT NULL,
+ "name" TEXT NOT NULL,
+
+ CONSTRAINT "roles_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "permissions" (
+ "id" TEXT NOT NULL,
+ "key" TEXT NOT NULL,
+
+ CONSTRAINT "permissions_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "user_roles" (
+ "user_id" TEXT NOT NULL,
+ "role_id" TEXT NOT NULL,
+
+ CONSTRAINT "user_roles_pkey" PRIMARY KEY ("user_id","role_id")
+);
+
+-- CreateTable
+CREATE TABLE "role_permissions" (
+ "role_id" TEXT NOT NULL,
+ "permission_id" TEXT NOT NULL,
+
+ CONSTRAINT "role_permissions_pkey" PRIMARY KEY ("role_id","permission_id")
+);
+
+-- CreateTable
+CREATE TABLE "audit_logs" (
+ "id" TEXT NOT NULL,
+ "tenant_id" TEXT NOT NULL,
+ "actor_id" TEXT,
+ "action" TEXT NOT NULL,
+ "entity" TEXT NOT NULL,
+ "entity_id" TEXT,
+ "before" JSONB,
+ "after" JSONB,
+ "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+
+ CONSTRAINT "audit_logs_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateIndex
+CREATE UNIQUE INDEX "tenants_slug_key" ON "tenants"("slug");
+
+-- CreateIndex
+CREATE INDEX "users_tenant_id_idx" ON "users"("tenant_id");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "users_tenant_id_email_key" ON "users"("tenant_id", "email");
+
+-- CreateIndex
+CREATE INDEX "roles_tenant_id_idx" ON "roles"("tenant_id");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "roles_tenant_id_key_key" ON "roles"("tenant_id", "key");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "permissions_key_key" ON "permissions"("key");
+
+-- CreateIndex
+CREATE INDEX "audit_logs_tenant_id_entity_entity_id_idx" ON "audit_logs"("tenant_id", "entity", "entity_id");
+
+-- CreateIndex
+CREATE INDEX "audit_logs_tenant_id_created_at_idx" ON "audit_logs"("tenant_id", "created_at");
+
+-- AddForeignKey
+ALTER TABLE "users" ADD CONSTRAINT "users_tenant_id_fkey" FOREIGN KEY ("tenant_id") REFERENCES "tenants"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "roles" ADD CONSTRAINT "roles_tenant_id_fkey" FOREIGN KEY ("tenant_id") REFERENCES "tenants"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "user_roles" ADD CONSTRAINT "user_roles_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "user_roles" ADD CONSTRAINT "user_roles_role_id_fkey" FOREIGN KEY ("role_id") REFERENCES "roles"("id") ON DELETE CASCADE ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "role_permissions" ADD CONSTRAINT "role_permissions_role_id_fkey" FOREIGN KEY ("role_id") REFERENCES "roles"("id") ON DELETE CASCADE ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "role_permissions" ADD CONSTRAINT "role_permissions_permission_id_fkey" FOREIGN KEY ("permission_id") REFERENCES "permissions"("id") ON DELETE CASCADE ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "audit_logs" ADD CONSTRAINT "audit_logs_tenant_id_fkey" FOREIGN KEY ("tenant_id") REFERENCES "tenants"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
diff --git a/prisma/migrations/20260702095816_row_level_security/migration.sql b/prisma/migrations/20260702095816_row_level_security/migration.sql
new file mode 100644
index 0000000..4027740
--- /dev/null
+++ b/prisma/migrations/20260702095816_row_level_security/migration.sql
@@ -0,0 +1,36 @@
+-- Row Level Security als zweite Verteidigungslinie (docs/SPEC.md §2).
+-- Primäre Isolation ist der Tenant-Guard in src/server/db.ts.
+--
+-- Aktivierungsstand: Policies sind definiert; die App verbindet sich aktuell
+-- noch als Tabellen-Owner (Policies greifen für Owner ohne FORCE nicht).
+-- Härtungs-Iteration (SPEC §11 Punkt 8): App-Verbindung auf die Rolle
+-- "isms_app" umstellen und app.tenant_id pro Transaktion setzen — dann sind
+-- die Policies scharf. FORCE wird bewusst nicht gesetzt, damit Migrationen,
+-- Seeds und der mandantenübergreifende Login-Lookup als Owner funktionieren.
+
+-- Eingeschränkte Anwendungsrolle (für die Härtungs-Iteration)
+DO $$
+BEGIN
+ IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'isms_app') THEN
+ CREATE ROLE isms_app NOLOGIN NOBYPASSRLS;
+ END IF;
+END
+$$;
+
+GRANT USAGE ON SCHEMA public TO isms_app;
+GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO isms_app;
+ALTER DEFAULT PRIVILEGES IN SCHEMA public
+ GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO isms_app;
+
+-- RLS auf allen mandanten-gebundenen Tabellen
+ALTER TABLE "users" ENABLE ROW LEVEL SECURITY;
+ALTER TABLE "roles" ENABLE ROW LEVEL SECURITY;
+ALTER TABLE "audit_logs" ENABLE ROW LEVEL SECURITY;
+
+-- Isolation: Zugriff nur auf Zeilen des in app.tenant_id gesetzten Mandanten
+CREATE POLICY tenant_isolation ON "users"
+ USING ("tenant_id" = current_setting('app.tenant_id', true));
+CREATE POLICY tenant_isolation ON "roles"
+ USING ("tenant_id" = current_setting('app.tenant_id', true));
+CREATE POLICY tenant_isolation ON "audit_logs"
+ USING ("tenant_id" = current_setting('app.tenant_id', true));
diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml
new file mode 100644
index 0000000..044d57c
--- /dev/null
+++ b/prisma/migrations/migration_lock.toml
@@ -0,0 +1,3 @@
+# Please do not edit this file manually
+# It should be added in your version-control system (e.g., Git)
+provider = "postgresql"
diff --git a/prisma/seed.ts b/prisma/seed.ts
new file mode 100644
index 0000000..b2a2f93
--- /dev/null
+++ b/prisma/seed.ts
@@ -0,0 +1,92 @@
+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);
+ });
diff --git a/src/app/api/auth/[...nextauth]/route.ts b/src/app/api/auth/[...nextauth]/route.ts
new file mode 100644
index 0000000..63cd05d
--- /dev/null
+++ b/src/app/api/auth/[...nextauth]/route.ts
@@ -0,0 +1,3 @@
+import { handlers } from "@/server/auth";
+
+export const { GET, POST } = handlers;
diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx
new file mode 100644
index 0000000..6423d47
--- /dev/null
+++ b/src/app/dashboard/page.tsx
@@ -0,0 +1,43 @@
+import { getTranslations } from "next-intl/server";
+import { redirect } from "next/navigation";
+import { auth, signOut } from "@/server/auth";
+
+export default async function DashboardPage() {
+ const session = await auth();
+ if (!session?.user) redirect("/login");
+
+ const t = await getTranslations("dashboard");
+ const tc = await getTranslations("common");
+
+ async function logout() {
+ "use server";
+ await signOut({ redirectTo: "/login" });
+ }
+
+ return (
+ {t("welcome", { name: session.user.name ?? "" })} {t("placeholder")}{t("title")}
+
+
+
+
+ {t("error")} +
+ )} + + +- Looking for a starting point or more instructions? Head over to{" "} - - Templates - {" "} - or the{" "} - - Learning - {" "} - center. -
-