init
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user