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
+106 -9
View File
@@ -2,6 +2,7 @@
import { useState, useEffect, useCallback, useRef } from "react";
import { useAuth } from "@/hooks/useAuth";
import { features, type Feature } from "@/data/features";
import {
getUsers,
createUser,
@@ -276,18 +277,17 @@ export default function AdminPage() {
const auditTotalPages = Math.ceil(auditTotal / AUDIT_PAGE_SIZE);
if (authLoading || !user) {
return (
<div className="flex min-h-screen items-center justify-center">
<Skeleton className="h-8 w-48" />
</div>
);
}
return (
<div className="min-h-screen">
<Navbar username={user.username} role={user.role} />
<Navbar username={user?.username ?? ""} role={user?.role ?? ""} />
<main className="mx-auto max-w-7xl px-4 py-6">
{(authLoading || !user) ? (
<div className="space-y-4">
<Skeleton className="h-8 w-48" />
<Skeleton className="h-10 w-full max-w-sm" />
<Skeleton className="h-64 w-full" />
</div>
) : (<>
<h1 className="mb-6 text-2xl font-bold">Administration</h1>
<Tabs defaultValue="dashboard">
@@ -296,6 +296,7 @@ export default function AdminPage() {
<TabsTrigger value="services">Dienste</TabsTrigger>
<TabsTrigger value="users">Benutzer</TabsTrigger>
<TabsTrigger value="audit">Audit-Log</TabsTrigger>
<TabsTrigger value="modules">Module</TabsTrigger>
</TabsList>
{/* ── Dashboard ── */}
@@ -1018,7 +1019,11 @@ export default function AdminPage() {
</>
)}
</TabsContent>
<TabsContent value="modules" className="mt-4">
<ModulesTab />
</TabsContent>
</Tabs>
</>)}
</main>
{/* Passwort-Reset Dialog */}
@@ -1060,3 +1065,95 @@ export default function AdminPage() {
</div>
);
}
// ── Module Tab ─────────────────────────────────────────────────────────────
const statusColors: Record<string, string> = {
"Planned": "bg-gray-100 text-gray-700",
"In Progress": "bg-yellow-100 text-yellow-800",
"In Review": "bg-blue-100 text-blue-800",
"Deployed": "bg-green-100 text-green-800",
};
const statusCounts = (list: Feature[]) => ({
total: list.length,
planned: list.filter((f) => f.status === "Planned").length,
inProgress: list.filter((f) => f.status === "In Progress").length,
inReview: list.filter((f) => f.status === "In Review").length,
deployed: list.filter((f) => f.status === "Deployed").length,
});
function ModulesTab() {
const counts = statusCounts(features);
return (
<div className="space-y-4">
<h2 className="text-lg font-semibold">Modulübersicht</h2>
{/* Summary bar */}
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
{[
{ label: "In Progress", value: counts.inProgress, color: "bg-yellow-100 text-yellow-800" },
{ label: "In Review", value: counts.inReview, color: "bg-blue-100 text-blue-800" },
{ label: "Deployed", value: counts.deployed, color: "bg-green-100 text-green-800" },
{ label: "Geplant", value: counts.planned, color: "bg-gray-100 text-gray-700" },
].map((s) => (
<Card key={s.label}>
<CardContent className="p-4 flex items-center justify-between">
<span className="text-sm text-muted-foreground">{s.label}</span>
<span className={`text-lg font-bold px-2 py-0.5 rounded ${s.color}`}>
{s.value}
</span>
</CardContent>
</Card>
))}
</div>
{/* Table */}
<Card>
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-20">ID</TableHead>
<TableHead>Feature</TableHead>
<TableHead className="w-32">Status</TableHead>
<TableHead className="w-24 text-center">Frontend</TableHead>
<TableHead className="w-24 text-center">Backend</TableHead>
<TableHead className="w-32">Aktualisiert</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{features.map((f) => (
<TableRow key={f.id}>
<TableCell className="font-mono text-xs text-muted-foreground">
{f.id}
</TableCell>
<TableCell className="font-medium">{f.name}</TableCell>
<TableCell>
<span className={`text-xs font-medium px-2 py-1 rounded-full ${statusColors[f.status]}`}>
{f.status}
</span>
</TableCell>
<TableCell className="text-center">
{f.frontend
? <span className="text-green-600 font-bold"></span>
: <span className="text-muted-foreground"></span>
}
</TableCell>
<TableCell className="text-center">
{f.backend
? <span className="text-green-600 font-bold"></span>
: <span className="text-muted-foreground"></span>
}
</TableCell>
<TableCell className="text-sm text-muted-foreground">
{f.lastUpdated}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Card>
</div>
);
}