fix: resolve invitation link expiration by adding verification landing page
All checks were successful
Staging Build / build (push) Successful in 3m35s

This commit is contained in:
DanielS
2026-06-30 16:21:13 +02:00
parent 10f8683d7f
commit 4cf1ac0eb7
2 changed files with 78 additions and 2 deletions

View File

@@ -0,0 +1,76 @@
'use client';
import { use, useState } from "react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { ShieldCheck, Loader2 } from "lucide-react";
interface PageProps {
searchParams: Promise<{
token_hash?: string;
type?: string;
next?: string;
}>;
}
export default function VerifyLinkPage({ searchParams }: PageProps) {
const params = use(searchParams);
const [isSubmitting, setIsSubmitting] = useState(false);
const tokenHash = params.token_hash || "";
const type = params.type || "";
const next = params.next || "/";
const handleSubmit = () => {
setIsSubmitting(true);
};
return (
<div className="relative flex min-h-svh w-full items-center justify-center p-6 md:p-10 overflow-hidden bg-slate-950">
{/* Background gradients for rich aesthetics */}
<div className="absolute inset-0 bg-[linear-gradient(to_right,#0f172a_1px,transparent_1px),linear-gradient(to_bottom,#0f172a_1px,transparent_1px)] bg-[size:4rem_4rem] [mask-image:radial-gradient(ellipse_60%_50%_at_50%_0%,#000_70%,transparent_100%)] opacity-60" />
<div className="absolute top-0 left-1/2 -translate-x-1/2 w-[500px] h-[500px] bg-blue-500/10 rounded-full blur-[120px] pointer-events-none" />
<div className="absolute bottom-0 right-1/4 w-[400px] h-[400px] bg-indigo-500/10 rounded-full blur-[100px] pointer-events-none" />
<div className="relative w-full max-w-md z-10">
<Card className="border-slate-800/80 bg-slate-900/60 backdrop-blur-xl shadow-2xl transition-all duration-300 hover:border-slate-700/80">
<CardHeader className="flex flex-col items-center justify-center text-center space-y-4 pt-8">
<div className="p-3.5 bg-blue-500/10 rounded-full text-blue-400 border border-blue-500/20 shadow-[0_0_20px_rgba(59,130,246,0.15)] animate-pulse">
<ShieldCheck className="w-8 h-8" />
</div>
<div className="space-y-2">
<CardTitle className="text-2xl font-bold tracking-tight text-slate-100">
Sicherheit prüfen
</CardTitle>
<p className="text-sm text-slate-400 max-w-xs mx-auto">
Bitte bestätigen Sie den Vorgang mit einem Klick auf den Button, um fortzufahren.
</p>
</div>
</CardHeader>
<CardContent className="pb-8">
<form action="/auth/confirm" method="GET" onSubmit={handleSubmit} className="w-full">
<input type="hidden" name="token_hash" value={tokenHash} />
<input type="hidden" name="type" value={type} />
<input type="hidden" name="next" value={next} />
<Button
type="submit"
disabled={isSubmitting || !tokenHash}
className="w-full bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 text-white font-medium shadow-[0_0_20px_rgba(59,130,246,0.25)] transition-all duration-200 h-11 active:scale-[0.98] disabled:opacity-50"
>
{isSubmitting ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Wird weitergeleitet...
</>
) : (
"Bestätigen & Fortfahren"
)}
</Button>
</form>
</CardContent>
</Card>
</div>
</div>
);
}

View File

@@ -84,7 +84,7 @@ export async function createUser(data: { email: string; role?: 'admin' | 'partne
const tokenHash = (linkData as any)?.properties?.hashed_token;
if (tokenHash) {
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://staging.hephex.de';
const setupLink = `${siteUrl}/auth/confirm?token_hash=${tokenHash}&type=recovery&next=/auth/update-password`;
const setupLink = `${siteUrl}/auth/verify-link?token_hash=${tokenHash}&type=recovery&next=/auth/update-password`;
await sendMail({
to: data.email,
subject: 'Willkommen bei CASPOS Shop Ihr Benutzerkonto wurde erstellt',
@@ -183,7 +183,7 @@ export async function resetPassword(email: string) {
if (!tokenHash) throw new Error('Recovery token not generated');
// Build link pointing to our own /auth/confirm route
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://staging.hephex.de';
const resetLink = `${siteUrl}/auth/confirm?token_hash=${tokenHash}&type=recovery&next=/auth/update-password`;
const resetLink = `${siteUrl}/auth/verify-link?token_hash=${tokenHash}&type=recovery&next=/auth/update-password`;
await sendMail({
to: email,
subject: 'Passwort zurücksetzen',