- Invite flow: admin can invite users by email with role selection; accept-invite page sets password and creates the account; 72-hour token expiry; E2E tests - User deactivate/reactivate/delete: new tRPC procedures + UI buttons; deactivation revokes all active sessions immediately; delete cascades vacation/broadcast records; isActive field added via migration 20260402000000_user_isactive - Auth: block login for inactive users with audit entry - Favicon: SVG favicon + ICO/PNG fallbacks (16, 32, 180, 192, 512px); manifest updated - Dashboard: GridLayout dynamic-import loading skeleton prevents blank dark area on first login before react-grid-layout chunk is cached - Admin users: remove max-w-5xl constraint so table uses full page width - Dev: docker container restart workflow documented in LEARNINGS.md; Prisma generate must run inside the container after schema changes (named node_modules volume) Co-Authored-By: claude-flow <ruv@ruv.net>
4.8 KiB
Plan: App Base URL — configurable via env, no localhost fallback in production
Stand: 2026-04-02
Anforderungsanalyse
Email-Links (Invite, Password Reset) werden aktuell mit process.env["NEXTAUTH_URL"] ?? "http://localhost:3100" gebaut. Das ist korrekt, wenn NEXTAUTH_URL gesetzt ist. Das Problem: wenn der Wert fehlt oder leer ist, enthalten Produktions-E-Mails localhost-Links. Außerdem gibt es keine .env.example, die dokumentiert, welche Variablen gesetzt werden müssen.
Was gebaut wird:
- Zentrale
getAppBaseUrl()Funktion inpackages/api— liestNEXTAUTH_URL, wirft in production einen Fehler wenn nicht gesetzt, fällt in dev auf localhost zurück. - Beide Router (
invite.ts,auth.ts) verwenden diese Funktion statt duplizierter Inline-Logik. .env.examplemit allen benötigten Variablen.- Health-Route zeigt ob
NEXTAUTH_URLkonfiguriert ist.
Betroffene Pakete: packages/api, apps/web
Audit-Ergebnis — intentionale localhost-Referenzen (NICHT ändern):
apps/web/e2e/dev-system/— alle E2E-Helfer, Specs, global-setup.tsapps/web/playwright.dev.config.tsapps/web/src/middleware.test.ts.github/workflows/deploy-test.yml
Betroffene Pakete & Dateien
| Paket | Datei | Art |
|---|---|---|
packages/api |
src/lib/app-base-url.ts |
create |
packages/api |
src/router/invite.ts |
edit |
packages/api |
src/router/auth.ts |
edit |
apps/web |
src/app/api/health/route.ts |
edit |
| root | .env.example |
create |
Task-Liste
-
Task 1:
getAppBaseUrl()inpackages/api/src/lib/app-base-url.tserstellen.- Liest
process.env["NEXTAUTH_URL"](trimmed). - Wenn gesetzt und nicht leer → gibt den Wert zurück (trailing slash entfernen).
- Wenn leer/fehlend und
NODE_ENV === "production"→ wirftError("NEXTAUTH_URL must be set in production — email links will be broken"). - Sonst (development/test) → gibt
"http://localhost:3100"zurück und loggt einmalig eine Warnung. - → Datei:
packages/api/src/lib/app-base-url.ts
- Liest
-
Task 2:
invite.tsaufgetAppBaseUrl()umstellen.- Ersetze
const baseUrl = process.env["NEXTAUTH_URL"] ?? "http://localhost:3100";durchconst baseUrl = getAppBaseUrl(); - → Datei:
packages/api/src/router/invite.tsZeile 53
- Ersetze
-
Task 3:
auth.tsaufgetAppBaseUrl()umstellen.- Ersetze
const baseUrl = process.env["NEXTAUTH_URL"] ?? "http://localhost:3100";durchconst baseUrl = getAppBaseUrl(); - → Datei:
packages/api/src/router/auth.tsZeile 50
- Ersetze
-
Task 4:
.env.exampleanlegen mit allen required und optionalen Variablen.- Sections: App, Auth, Database, Redis, SMTP, AI, Dev-Tools (pgAdmin)
- Jede Variable: Kommentar (required/optional, Beschreibung), Beispielwert.
NEXTAUTH_URLals REQUIRED mit Hinweis "must be the public URL (e.g. https://capakraken.example.com) — used in email links; do not use localhost in production"- → Datei:
.env.example
-
Task 5: Health-Route um
baseUrl-Check erweitern.- Liest
NEXTAUTH_URLdirekt (keingetAppBaseUrl()— soll nie werfen, nur reporten). - Fügt
"baseUrl": { "configured": bool, "isLocalhost": bool }zum JSON-Response hinzu. configured: falsewenn Var fehlt/leer;isLocalhost: truewenn Wert mithttp://localhostbeginnt.- → Datei:
apps/web/src/app/api/health/route.ts
- Liest
Abhängigkeiten
- Task 2 und Task 3 benötigen Task 1 (Funktion muss existieren).
- Task 2 und Task 3 können parallel ausgeführt werden (unterschiedliche Dateien).
- Task 4 und Task 5 sind unabhängig von allen anderen und können parallel ausgeführt werden.
Akzeptanzkriterien
pnpm test:unitläuft grünpnpm --filter @capakraken/web exec tsc --noEmit— keine neuen TS-Errorspnpm test:e2e:email— alle 3 E2E-Tests bestehen weiterhin- Wenn
NEXTAUTH_URL=https://capakraken.hartmut-noerenberg.com: E-Mail-Links enthalten diese Domain - Wenn
NEXTAUTH_URLfehlt undNODE_ENV=production:createInvite/requestPasswordResetwerfen einen klaren Fehler beim Link-Bau GET /api/healthliefertbaseUrl.configured: true/falseundbaseUrl.isLocalhost: bool.env.exampleexistiert und dokumentiert alle Pflichtfelder
Risiken & offene Fragen
- Unit-Tests für Router: Bestehende Tests für
invite.tsundauth.tsmüssenNEXTAUTH_URLin der Testumgebung gesetzt haben (oderNODE_ENV=test→ Dev-Fallback greift). Vorhandene Tests prüfen, obprocess.env["NEXTAUTH_URL"]dort gesetzt wird. docker-compose.prod.ymldelegiert ENV-Vars an.env.production(gitignored). Das.env.exampledeckt ab, was dort stehen muss — kein Code-Change nötig.- Trailing Slash: NEXTAUTH_URL könnte mit oder ohne
/enden.getAppBaseUrl()sollte trailing slashes normalisieren.