Files
smgw-pki/frontend/src/routes/login.tsx
T
2026-05-12 19:25:14 +02:00

140 lines
5.3 KiB
TypeScript

import { useState } from "react";
import { useMutation } from "@tanstack/react-query";
import { createFileRoute, useNavigate } from "@tanstack/react-router";
import { ShieldCheck, KeyRound, AlertTriangle } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Badge } from "@/components/ui/badge";
import { api, unwrap, ApiError } from "@/lib/api";
export const Route = createFileRoute("/login")({
component: LoginPage,
});
function LoginPage() {
const navigate = useNavigate();
const [devSubject, setDevSubject] = useState("CN=lab.operator,OU=PKI,O=SMGW");
const [err, setErr] = useState<string | null>(null);
const login = useMutation({
mutationFn: () =>
unwrap(
api.POST("/auth/session", {
body: { dev_subject: devSubject },
}),
),
onError: (e: ApiError) => setErr(e.message),
onSuccess: () => navigate({ to: "/" }),
});
return (
<div className="min-h-screen grid lg:grid-cols-[1.05fr_0.95fr]">
<aside className="relative hidden lg:flex flex-col justify-between p-12 border-r border-line overflow-hidden bg-paper">
<div className="absolute inset-0 grid-bg opacity-60 pointer-events-none" />
<div className="relative z-10 flex items-center gap-3">
<div className="size-10 rounded-[12px] bg-ink text-paper grid place-items-center font-display font-semibold text-[17px] tracking-[-0.02em]">
sm
</div>
<div className="leading-tight">
<div className="font-display text-[16px] font-semibold tracking-[-0.015em] text-ink">
SMGW PKI · Console
</div>
<div className="text-[11px] uppercase tracking-[0.1em] text-ink-faint">
Test / Labor BSI TR-03129-4
</div>
</div>
</div>
<div className="relative z-10 max-w-md space-y-6">
<p className="font-serif italic text-[34px] leading-[1.1] text-ink tracking-[-0.01em]">
Eine ruhige Konsole für eine{" "}
<span className="not-italic font-display font-semibold">
vorsichtige PKI
</span>
.
</p>
<p className="text-[13.5px] leading-relaxed text-ink-mute">
Steuerfläche für den smgw-pki-automator. Erneuerungen, Sub-CA-Status,
iconfig-Generierung und SoftHSM-Diagnose alle Operationen sind
auditiert und an mTLS-Identitäten gebunden.
</p>
<div className="flex flex-wrap gap-2">
<Badge tone="outline">TR-03129-4</Badge>
<Badge tone="outline">TR-03109-1</Badge>
<Badge tone="outline">SM-PKI CP</Badge>
</div>
</div>
<div className="relative z-10 text-[11.5px] text-ink-faint mono">
v0.1.0 · build skeleton · {new Date().getFullYear()}
</div>
</aside>
<section className="flex items-center justify-center p-8">
<div className="w-full max-w-[420px] surface p-7">
<div className="flex items-center gap-2 text-[11px] uppercase tracking-[0.1em] text-ink-faint">
<ShieldCheck className="size-3.5 text-accent" />
Sitzung
</div>
<h2 className="mt-3 font-display text-[26px] font-semibold tracking-[-0.02em] text-ink">
Anmeldung über mTLS
</h2>
<p className="mt-1.5 text-[13px] leading-relaxed text-ink-mute">
Ihr Client-Zertifikat wird vom Reverse Proxy terminiert und im Header{" "}
<code className="mono text-[12px]">X-Forwarded-Cert-Subject</code>{" "}
übergeben. Im Lab-Modus ist ein Dev-Subject als Fallback erlaubt.
</p>
<form
onSubmit={(e) => {
e.preventDefault();
setErr(null);
login.mutate();
}}
className="mt-7 space-y-4"
>
<div className="space-y-1.5">
<Label htmlFor="dev_subject">Dev-Subject (nur Lab)</Label>
<Input
id="dev_subject"
value={devSubject}
onChange={(e) => setDevSubject(e.target.value)}
mono
placeholder="CN=…"
/>
<p className="text-[11.5px] text-ink-faint">
Wird ignoriert, sobald der Proxy ein gültiges Cert-Subject mitschickt.
</p>
</div>
{err && (
<div className="flex gap-2 items-start surface-inset p-3 text-[12.5px] text-danger">
<AlertTriangle className="size-4 mt-0.5 shrink-0" />
<span>{err}</span>
</div>
)}
<Button
type="submit"
variant="primary"
size="lg"
className="w-full"
disabled={login.isPending}
>
<KeyRound className="size-4" />
Sitzung erstellen
</Button>
</form>
<div className="mt-6 pt-5 border-t border-line text-[11.5px] text-ink-faint leading-relaxed">
Cookies sind <span className="text-ink">HttpOnly</span> und{" "}
<span className="text-ink">SameSite=Strict</span>. Sitzungsdauer 8 h.
</div>
</div>
</section>
</div>
);
}