security: Zufallspasswörter beim Erststart, kryptographisch sichere JTI-Generierung

- seedDefaultUsers: generiert kryptographisch zufällige Passwörter (crypto/rand)
  statt hartkodiertes "archivmailrockz" — Passwörter werden einmalig im Terminal
  angezeigt und können danach nicht wiederhergestellt werden
- generateJTI: verwendet crypto/rand (16 Byte, hex) statt time.UnixNano XOR deadbeef

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
sysops
2026-03-17 01:19:24 +01:00
parent 7e165c8eed
commit bb963a796f
25 changed files with 471 additions and 111 deletions
+29 -16
View File
@@ -2,35 +2,48 @@
import { useState, useEffect, useCallback } from "react";
import { useRouter } from "next/navigation";
import { getMe, type MeResponse } from "@/lib/api";
import { getMe } from "@/lib/api";
import { getCachedUser, setCachedUser } from "@/lib/auth-cache";
interface AuthState {
user: MeResponse | null;
loading: boolean;
error: string | null;
}
export { clearAuthCache } from "@/lib/auth-cache";
export function useAuth(requireRole?: "admin" | "auditor") {
const router = useRouter();
const [state, setState] = useState<AuthState>({
user: null,
loading: true,
error: null,
});
const cached = getCachedUser();
const [user, setUser] = useState(cached);
const [loading, setLoading] = useState(cached === null);
const checkAuth = useCallback(async () => {
const cached = getCachedUser();
if (cached !== null) {
if (requireRole === "admin" && cached.role !== "admin") {
router.replace("/search");
return;
}
if (requireRole === "auditor" && cached.role !== "auditor" && cached.role !== "admin") {
router.replace("/search");
return;
}
setUser(cached);
setLoading(false);
return;
}
try {
const user = await getMe();
if (requireRole === "admin" && user.role !== "admin") {
const me = await getMe();
setCachedUser(me);
if (requireRole === "admin" && me.role !== "admin") {
router.replace("/search");
return;
}
if (requireRole === "auditor" && user.role !== "auditor" && user.role !== "admin") {
if (requireRole === "auditor" && me.role !== "auditor" && me.role !== "admin") {
router.replace("/search");
return;
}
setState({ user, loading: false, error: null });
setUser(me);
setLoading(false);
} catch {
setCachedUser(null);
router.replace("/");
}
}, [router, requireRole]);
@@ -39,5 +52,5 @@ export function useAuth(requireRole?: "admin" | "auditor") {
checkAuth();
}, [checkAuth]);
return state;
return { user, loading };
}