From 572052285c6827d37f04fe3a48a11e61f5edfdce Mon Sep 17 00:00:00 2001 From: Sharang Parnerkar <30073382+mighty840@users.noreply.github.com> Date: Thu, 7 May 2026 23:30:27 +0200 Subject: [PATCH] fix: require button click to consume magic link token Email security gateways follow GET redirects automatically and were consuming the token before the investor clicked through. The verify page now shows an 'Access Pitch Deck' button; the token is only consumed on explicit click, which scanners cannot trigger. Co-Authored-By: Claude Sonnet 4.6 --- pitch-deck/app/auth/verify/page.tsx | 86 +++++++++++++++++------------ 1 file changed, 51 insertions(+), 35 deletions(-) diff --git a/pitch-deck/app/auth/verify/page.tsx b/pitch-deck/app/auth/verify/page.tsx index d363ffb..546481d 100644 --- a/pitch-deck/app/auth/verify/page.tsx +++ b/pitch-deck/app/auth/verify/page.tsx @@ -1,6 +1,6 @@ 'use client' -import { Suspense, useEffect, useState } from 'react' +import { Suspense, useEffect, useState, useCallback } from 'react' import { useSearchParams, useRouter } from 'next/navigation' import { motion } from 'framer-motion' @@ -9,7 +9,7 @@ function VerifyContent() { const router = useRouter() const token = searchParams.get('token') - const [status, setStatus] = useState<'verifying' | 'success' | 'error'>('verifying') + const [status, setStatus] = useState<'ready' | 'verifying' | 'success' | 'error'>('ready') const [errorMsg, setErrorMsg] = useState('') useEffect(() => { @@ -19,38 +19,37 @@ function VerifyContent() { return } - async function verify() { - try { - // If the investor already has a valid session, skip token verification - const sessionCheck = await fetch('/api/auth/me') - if (sessionCheck.ok) { - router.push('/') - return - } - - const res = await fetch('/api/auth/verify', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ token }), - }) - - if (res.ok) { - setStatus('success') - setTimeout(() => router.push('/'), 1000) - } else { - const data = await res.json() - setStatus('error') - setErrorMsg(data.error || 'Verification failed.') - } - } catch { - setStatus('error') - setErrorMsg('Network error. Please try again.') - } - } - - verify() + // If the investor already has a valid session, skip the button entirely + fetch('/api/auth/me').then(res => { + if (res.ok) router.push('/') + }) }, [token, router]) + const handleAccess = useCallback(async () => { + if (!token || status === 'verifying') return + setStatus('verifying') + + try { + const res = await fetch('/api/auth/verify', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ token }), + }) + + if (res.ok) { + setStatus('success') + setTimeout(() => router.push('/'), 1000) + } else { + const data = await res.json() + setStatus('error') + setErrorMsg(data.error || 'Verification failed.') + } + } catch { + setStatus('error') + setErrorMsg('Network error. Please try again.') + } + }, [token, status, router]) + return (
+ {status === 'ready' && ( + <> +
+ + + +
+

Your pitch deck is ready

+

Click below to access it.

+ + + )} + {status === 'verifying' && ( <>
-

Verifying your access link...

+

Verifying your access...

)} @@ -115,11 +132,10 @@ export default function VerifyPage() { - {/* Privacy Notice Footer */}

- Datenschutzhinweis (Art. 13 DSGVO): Beim Zugriff werden technische Zugriffsdaten (IP-Adresse, Zeitpunkt, Browser) sowie – soweit eingeladen – personenbezogene Kontaktdaten (E-Mail, Name, Unternehmen) verarbeitet. Zweck: Zugangsverwaltung und Missbrauchsprävention. Rechtsgrundlage: Art. 6 Abs. 1 lit. f DSGVO (berechtigtes Interesse). Speicherdauer: max. 30 Tage nach letztem Zugriff; nicht aktivierte Zugänge nach 90 Tagen. Danach automatische Anonymisierung. Ihre Rechte gem. Art. 15–21 DSGVO (Auskunft, Berichtigung, Löschung, Einschränkung, Datenübertragbarkeit, Widerspruch): Anfragen an pitch@breakpilot.ai. Beschwerderecht bei der Aufsichtsbehörde: LfDI Baden-Württemberg (www.baden-wuerttemberg.datenschutz.de).

+ Datenschutzhinweis (Art. 13 DSGVO): Beim Zugriff werden technische Zugriffsdaten (IP-Adresse, Zeitpunkt, Browser) sowie – soweit eingeladen – personenbezogene Kontaktdaten (E-Mail, Name, Unternehmen) verarbeitet. Zweck: Zugangsverwaltung und Missbrauchsprävention. Rechtsgrundlage: Art. 6 Abs. 1 lit. f DSGVO (berechtigtes Interesse). Speicherdauer: max. 30 Tage nach letztem Zugriff; nicht aktivierte Zugänge nach 90 Tagen. Danach automatische Anonymisierung. Ihre Rechte gem. Art. 15–21 DSGVO (Auskunft, Berichtigung, Löschung, Einschränkung, Datenübertragbarkeit, Widerspruch): Anfragen an pitch@breakpilot.ai. Beschwerderecht bei der Aufsichtsbehörde: LfDI Baden-Württemberg (www.baden-wuerttemberg.datenschutz.de).

Verantwortlich: Benjamin Bönisch & Sharang Parnerkar · Kontakt: info@breakpilot.com