Basis: Certvia dev@a48c5fb als Fundament für Craftvia
CI / build-and-check (push) Canceled after 0s
CI / audit (push) Canceled after 0s
CI / sbom (push) Canceled after 0s

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>
This commit is contained in:
2026-09-14 11:05:39 +02:00
co-authored by Claude Opus 5
commit c8e6f30a27
720 changed files with 140143 additions and 0 deletions
+65
View File
@@ -0,0 +1,65 @@
import Link from "next/link";
import { prisma } from "@/server/db";
import { CertviaLogo } from "@/components/brand/certvia-logo";
import { PoweredByGefim } from "@/components/brand/powered-by-gefim";
import { ResetPasswordForm } from "@/components/auth-recovery-forms";
import { checkInvitationToken } from "@/server/actions/auth-recovery";
import { describePasswordPolicy, resolvePasswordPolicy } from "@/lib/password-policy";
import { peekToken } from "@/server/auth-token";
/**
* Option C (WS3) — Einlösung eines Einladungs-Links. Die Nutzeranlage erfolgt
* ausschließlich per Einladung (goldene Regel 4): der Eingeladene setzt hier sein
* Erst-Passwort an der globalen Identity. Der Token wird nur geprüft (nicht
* verbraucht) — verbraucht wird er erst beim Absenden.
*/
export default async function InvitePage({
searchParams,
}: {
searchParams: Promise<{ token?: string }>;
}) {
const params = await searchParams;
const token = params.token ?? "";
const valid = token ? await checkInvitationToken(token) : false;
let policyHint = describePasswordPolicy(resolvePasswordPolicy(undefined));
if (valid) {
const resolved = await peekToken(token, "invitation");
if (resolved?.tenantId) {
const settings = await prisma.tenantSettings.findUnique({
where: { tenantId: resolved.tenantId },
select: { securityPolicy: true },
});
policyHint = describePasswordPolicy(resolvePasswordPolicy(settings?.securityPolicy));
}
}
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={36} />
<p className="mt-5 font-heading text-lg font-semibold">Konto einrichten</p>
{valid ? (
<>
<p className="mt-1 text-sm text-muted-foreground">
Willkommen! Vergeben Sie ein Passwort für Ihr Konto. Ihre E-Mail-Adresse ist zugleich Ihr Anmeldename.
</p>
<ResetPasswordForm token={token} domain="tenant" policyHint={policyHint} loginPath="/login" variant="invite" />
</>
) : (
<>
<p role="alert" className="mt-4 rounded-lg bg-[rgba(255,107,107,0.16)] px-3 py-2 text-sm text-[var(--risk)]">
Der Einladungs-Link ist ungültig oder abgelaufen. Bitte wenden Sie sich an Ihre Administration.
</p>
<Link href="/login" className="mt-4 block text-center text-[13px] font-semibold text-[var(--primary)]">
Zur Anmeldung →
</Link>
</>
)}
<PoweredByGefim className="mt-7 border-t border-[var(--panel-brd)] pt-4" />
</div>
</main>
);
}