feat(PROJ-1): httpOnly Cookie, Auditor-Guard, Nutzer-Aktionen (C)

Backend:
- Login setzt httpOnly SameSite=Strict Cookie (archivmail_session)
- Logout löscht Cookie + blacklistet Token
- authMiddleware: Cookie first, Bearer als Fallback (CLI kompatibel)

Frontend:
- api.ts: credentials: include statt localStorage/Bearer Token
- updateUser(), deleteUser() hinzugefügt
- useAuth: kein localStorage mehr, nur /api/auth/me
  requireRole: "admin" | "auditor" | undefined
- Login-Seite: kein localStorage
- Navbar: kein localStorage
- Admin: Nutzer-Aktionen (Sperren/Freischalten, Löschen, Passwort-Reset)
  Löschen verhindert wenn letzter Admin (HTTP 409)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
sysops
2026-03-15 19:57:13 +01:00
parent a94b1d3e52
commit 7e165c8eed
6 changed files with 213 additions and 62 deletions
+7 -10
View File
@@ -10,7 +10,7 @@ interface AuthState {
error: string | null;
}
export function useAuth(requireAdmin?: boolean) {
export function useAuth(requireRole?: "admin" | "auditor") {
const router = useRouter();
const [state, setState] = useState<AuthState>({
user: null,
@@ -19,24 +19,21 @@ export function useAuth(requireAdmin?: boolean) {
});
const checkAuth = useCallback(async () => {
const token = localStorage.getItem("archivmail_token");
if (!token) {
router.replace("/");
return;
}
try {
const user = await getMe();
if (requireAdmin && user.role !== "admin") {
if (requireRole === "admin" && user.role !== "admin") {
router.replace("/search");
return;
}
if (requireRole === "auditor" && user.role !== "auditor" && user.role !== "admin") {
router.replace("/search");
return;
}
setState({ user, loading: false, error: null });
} catch {
localStorage.removeItem("archivmail_token");
router.replace("/");
}
}, [router, requireAdmin]);
}, [router, requireRole]);
useEffect(() => {
checkAuth();