Files
craftvia/src/app/platform/login/page.tsx
T
msolarczekandClaude Opus 5 c8e6f30a27
CI / build-and-check (push) Canceled after 0s
CI / audit (push) Canceled after 0s
CI / sbom (push) Canceled after 0s
Basis: Certvia dev@a48c5fb als Fundament für Craftvia
Unveränderter Stand von certvia/dev (a48c5fb) plus Craftvia-Spezifikation
und Brandbook unter docs/craftvia/. ISMS-Module werden im Folgecommit entfernt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 11:05:39 +02:00

93 lines
3.4 KiB
TypeScript

import Link from "next/link";
import { redirect } from "next/navigation";
import { AuthError } from "next-auth";
import { platformAuth, platformSignIn } from "@/server/platform-auth";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { CertviaLogo } from "@/components/brand/certvia-logo";
import { PoweredByGefim } from "@/components/brand/powered-by-gefim";
/**
* Getrennter Login für Plattform-Administratoren (Phase-1-Härtung Paket 2).
* Eigene Auth-Domäne, kein Mandantenkontext. TOTP-Code ist ab eingerichteter MFA
* erforderlich (Feld optional, damit der erste Login zur Einrichtung durchläuft).
*/
export default async function PlatformLoginPage({
searchParams,
}: {
searchParams: Promise<{ error?: string }>;
}) {
const { error } = await searchParams;
const session = await platformAuth();
if (session?.user?.id) redirect("/admin");
async function login(formData: FormData) {
"use server";
try {
await platformSignIn("credentials", {
email: formData.get("email"),
password: formData.get("password"),
token: formData.get("token"),
redirectTo: "/admin",
});
} catch (err) {
if (err instanceof AuthError) redirect("/platform/login?error=1");
throw err; // NEXT_REDIRECT eines erfolgreichen signIn muss durchlaufen
}
}
return (
<main className="flex flex-1 items-center justify-center p-6">
<div className="shadow-card w-full max-w-sm rounded-2xl border bg-card p-8">
<CertviaLogo variant="lockup" theme="dark" height={38} />
<p className="mt-4 font-heading text-lg font-semibold">Plattform-Administration</p>
<h2 className="mt-1 text-sm font-normal text-muted-foreground">
Betreiberzugang — getrennt vom Mandanten-Login
</h2>
{error && (
<p
role="alert"
className="mt-4 rounded-lg bg-[rgba(255,107,107,0.16)] px-3 py-2 text-sm text-[var(--risk)]"
>
Anmeldung fehlgeschlagen. Bitte E-Mail, Passwort und — falls MFA eingerichtet — den 6-stelligen Code prüfen.
</p>
)}
<form action={login} className="mt-6 space-y-4">
<div>
<Label htmlFor="email">E-Mail</Label>
<Input id="email" name="email" type="email" required autoComplete="email" className="mt-1" />
</div>
<div>
<Label htmlFor="password">Passwort</Label>
<Input id="password" name="password" type="password" required autoComplete="current-password" className="mt-1" />
</div>
<div>
<Label htmlFor="token">MFA-Code (falls eingerichtet)</Label>
<Input
id="token"
name="token"
inputMode="numeric"
autoComplete="one-time-code"
placeholder="6-stelliger Code oder Recovery-Code"
className="mt-1"
/>
</div>
<Button type="submit" className="w-full">Anmelden</Button>
</form>
<p className="mt-4 text-center text-[12.5px]">
<Link href="/forgot-password?domain=platform" className="text-muted-foreground hover:text-foreground">
Passwort vergessen?
</Link>
</p>
<PoweredByGefim className="mt-7 border-t border-[var(--panel-brd)] pt-4" />
</div>
</main>
);
}