feat(PROJ-28): Self-Service Onboarding — Signup, Verify, Password Reset, Invites
- internal/mailer: SMTP-Out via net/smtp (TLS + STARTTLS), HTML+Text-Templates - internal/tokenstore: auth_tokens Tabelle, SHA-256-Hash, TTL, einmalig verwendbar - userstore: CreateInactive(), Activate(), GetByEmail(), SetPassword() - API: POST /signup, GET /verify, POST /forgot-password, POST /reset-password - API: POST /admin/invite (domain_admin+), GET /auth/invite?token (check) - Login-Seite: Links zu "Passwort vergessen" und "Registrieren" - Frontend: /signup, /verify, /forgot-password, /reset-password Seiten - server.fqdn nicht konfiguriert → Startup-Warnung, Self-Service deaktiviert - LDAP-Nutzer: Passwort-Reset abgewiesen Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
7930b85cde
commit
4583262ea4
@@ -0,0 +1,63 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState, Suspense } from "react";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
function VerifyContent() {
|
||||
const params = useSearchParams();
|
||||
const token = params.get("token") ?? "";
|
||||
const [status, setStatus] = useState<"loading" | "ok" | "error">("loading");
|
||||
const [message, setMessage] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
if (!token) {
|
||||
setStatus("error");
|
||||
setMessage("Kein Token angegeben.");
|
||||
return;
|
||||
}
|
||||
fetch(`/api/auth/verify?token=${encodeURIComponent(token)}`, { credentials: "include" })
|
||||
.then(async (res) => {
|
||||
const data = await res.json().catch(() => ({}));
|
||||
if (res.ok) {
|
||||
setStatus("ok");
|
||||
setMessage((data as { message?: string }).message ?? "E-Mail bestätigt.");
|
||||
} else {
|
||||
setStatus("error");
|
||||
setMessage((data as { error?: string }).error ?? "Ungültiger oder abgelaufener Link.");
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
setStatus("error");
|
||||
setMessage("Netzwerkfehler. Bitte versuche es erneut.");
|
||||
});
|
||||
}, [token]);
|
||||
|
||||
return (
|
||||
<Card className="w-full max-w-sm">
|
||||
<CardHeader className="text-center">
|
||||
<CardTitle>{status === "ok" ? "E-Mail bestätigt" : status === "error" ? "Fehler" : "Bestätigen..."}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4 text-center">
|
||||
{status === "loading" && <p className="text-sm text-muted-foreground">Bitte warten...</p>}
|
||||
{status !== "loading" && <p className="text-sm">{message}</p>}
|
||||
{status === "ok" && (
|
||||
<Button className="w-full" onClick={() => window.location.href = "/"}>
|
||||
Zur Anmeldung
|
||||
</Button>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default function VerifyPage() {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center px-4">
|
||||
<Suspense>
|
||||
<VerifyContent />
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user