From acc0d93318a47ef81a9849a6989e84c1b0304164 Mon Sep 17 00:00:00 2001 From: sysops Date: Tue, 31 Mar 2026 23:47:41 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20getrennte=20Login-Seiten=20f=C3=BCr=20U?= =?UTF-8?q?ser=20(/),=20Admins=20(/admin/login)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - /admin/login: neue Login-Seite nur für auditor, admin, domain_admin, superadmin - /: blockiert Admin-Rollen mit Hinweis auf /admin - useAuth: neuer loginPage-Parameter für flexiblen Unauthentifiziert-Redirect - /admin: leitet bei Nicht-Auth zu /admin/login statt / Co-Authored-By: Claude Sonnet 4.6 --- src/app/admin/login/page.tsx | 104 +++++++++++++++++++++++++++++++++++ src/app/admin/page.tsx | 2 +- src/app/page.tsx | 12 +++- src/hooks/useAuth.ts | 6 +- 4 files changed, 117 insertions(+), 7 deletions(-) create mode 100644 src/app/admin/login/page.tsx diff --git a/src/app/admin/login/page.tsx b/src/app/admin/login/page.tsx new file mode 100644 index 0000000..62ca31f --- /dev/null +++ b/src/app/admin/login/page.tsx @@ -0,0 +1,104 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { useRouter } from "next/navigation"; +import { login } from "@/lib/api"; +import { getCachedUser, setCachedUser } from "@/lib/auth-cache"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Label } from "@/components/ui/label"; + +const ADMIN_ROLES = ["auditor", "admin", "domain_admin", "superadmin"]; + +export default function AdminLoginPage() { + const router = useRouter(); + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + const [error, setError] = useState(""); + const [loading, setLoading] = useState(false); + + useEffect(() => { + const cached = getCachedUser(); + if (cached !== null && ADMIN_ROLES.includes(cached.role)) { + router.replace("/admin"); + } + }, [router]); + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + setError(""); + setLoading(true); + + try { + const res = await login(username, password); + const role = res?.user?.role ?? ""; + if (!ADMIN_ROLES.includes(role)) { + setCachedUser(null); + setError("Kein Zugriff. Dieses Login ist nur für Admins und Auditoren."); + return; + } + if (role === "auditor") { + router.push("/search"); + } else { + router.push("/admin"); + } + } catch { + setError("Anmeldung fehlgeschlagen. Bitte Zugangsdaten prüfen."); + } finally { + setLoading(false); + } + } + + return ( +
+ + + archivmail +

Admin-Anmeldung

+
+ +
+
+ + setUsername(e.target.value)} + required + autoComplete="username" + /> +
+
+ + setPassword(e.target.value)} + required + autoComplete="current-password" + /> +
+ {error && ( +

+ {error} +

+ )} + +
+ +
+
+
+ ); +} diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index d82c344..02f74ea 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -84,7 +84,7 @@ import { ResetPasswordDialog, DeleteUserDialog } from "@/components/admin/UserDi const AUDIT_PAGE_SIZE = 25; export default function AdminPage() { - const { user, loading: authLoading } = useAuth("domain_admin"); + const { user, loading: authLoading } = useAuth("domain_admin", "/admin/login"); const isSuperAdmin = user?.role === "superadmin"; // Dashboard state diff --git a/src/app/page.tsx b/src/app/page.tsx index 5cd4472..bc2a260 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -16,13 +16,15 @@ export default function LoginPage() { const [error, setError] = useState(""); const [loading, setLoading] = useState(false); + const ADMIN_ROLES = ["auditor", "admin", "domain_admin", "superadmin"]; + useEffect(() => { // Only redirect if we have a cached session — no API call, no loop risk const cached = getCachedUser(); if (cached !== null) { - router.replace(cached.role === "superadmin" ? "/admin" : "/search"); + router.replace(ADMIN_ROLES.includes(cached.role) ? "/admin/login" : "/search"); } - }, [router]); + }, [router]); // eslint-disable-line react-hooks/exhaustive-deps async function handleSubmit(e: React.FormEvent) { e.preventDefault(); @@ -32,7 +34,11 @@ export default function LoginPage() { try { const res = await login(username, password); const role = res?.user?.role ?? ""; - router.push(role === "superadmin" ? "/admin" : "/search"); + if (ADMIN_ROLES.includes(role)) { + setError("Admins und Auditoren bitte über /admin anmelden."); + return; + } + router.push("/search"); } catch { setError("Anmeldung fehlgeschlagen. Bitte Zugangsdaten pruefen."); } finally { diff --git a/src/hooks/useAuth.ts b/src/hooks/useAuth.ts index 1ece603..2b04c6d 100644 --- a/src/hooks/useAuth.ts +++ b/src/hooks/useAuth.ts @@ -20,7 +20,7 @@ export function hasRole(userRole: string, required: string): boolean { return (roleLevels[userRole] ?? 0) >= (roleLevels[required] ?? 0); } -export function useAuth(requireRole?: "admin" | "domain_admin" | "superadmin" | "auditor") { +export function useAuth(requireRole?: "admin" | "domain_admin" | "superadmin" | "auditor", loginPage = "/") { const router = useRouter(); const cached = getCachedUser(); const [user, setUser] = useState(cached); @@ -49,9 +49,9 @@ export function useAuth(requireRole?: "admin" | "domain_admin" | "superadmin" | setLoading(false); } catch { setCachedUser(null); - router.replace("/"); + router.replace(loginPage); } - }, [router, requireRole]); + }, [router, requireRole, loginPage]); useEffect(() => { checkAuth();