From 9bfbd67cb4ca5bdb6632508d80860bbe8dc7c8eb Mon Sep 17 00:00:00 2001 From: Paperclip Date: Tue, 14 Apr 2026 14:43:46 +0000 Subject: [PATCH 1/4] fix: update vite to 6.4.2 to patch high-severity vulnerabilities Vite 6.4.1 has two high-severity vulnerabilities: - GHSA-4w7w-66w2-5vf9: Path Traversal in Optimized Deps .map Handling - GHSA-p9ff-h696-f583: Arbitrary File Read via Vite Dev Server WebSocket Updated to vite 6.4.2. Fixes CAR-599. Co-Authored-By: Paperclip --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index a56c4d4..709106e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9805,9 +9805,9 @@ } }, "node_modules/vite": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", - "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", + "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", "devOptional": true, "license": "MIT", "dependencies": { From a11726b8e60242a3ee069f1e03b57859e9db2a5e Mon Sep 17 00:00:00 2001 From: Paperclip Date: Tue, 14 Apr 2026 15:37:24 +0000 Subject: [PATCH 2/4] fix: remove VITE_MOCK_AUTH bypass from production code Co-Authored-By: Paperclip --- e2e/journeys/j1-registration-login.spec.ts | 1 - playwright.config.ts | 2 +- src/components/ProtectedRoute.tsx | 17 ----------------- src/pages/Login.tsx | 9 +-------- src/pages/Register.tsx | 9 +-------- 5 files changed, 3 insertions(+), 35 deletions(-) diff --git a/e2e/journeys/j1-registration-login.spec.ts b/e2e/journeys/j1-registration-login.spec.ts index ec116ab..b1b28a4 100644 --- a/e2e/journeys/j1-registration-login.spec.ts +++ b/e2e/journeys/j1-registration-login.spec.ts @@ -10,7 +10,6 @@ test.describe('J1: Registration and Login', () => { await page.fill('[placeholder="Password (min. 8 characters)"]', 'TestPass123!'); await page.click('button[type="submit"]'); - // With VITE_MOCK_AUTH=true the app navigates to "/" on success await expect(page).toHaveURL('http://localhost:5173/'); await expect(page.getByRole('heading', { name: /cart/i })).toBeVisible(); }); diff --git a/playwright.config.ts b/playwright.config.ts index b22d74a..a2d7b0b 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -9,7 +9,7 @@ export default defineConfig({ }, ], webServer: { - command: 'VITE_MOCK_AUTH=true npm run dev', + command: 'npm run dev', url: 'http://localhost:5173', reuseExistingServer: !process.env.CI, }, diff --git a/src/components/ProtectedRoute.tsx b/src/components/ProtectedRoute.tsx index cf92831..294ec4f 100644 --- a/src/components/ProtectedRoute.tsx +++ b/src/components/ProtectedRoute.tsx @@ -1,25 +1,8 @@ -import { useEffect } from 'react' import { Navigate, Outlet } from 'react-router-dom' import { authClient } from '../lib/auth-client.ts' -import { useAuthStore } from '../stores/auth.ts' export function ProtectedRoute() { - const isMockAuth = import.meta.env.VITE_MOCK_AUTH === 'true' const { data: session, isPending } = authClient.useSession() - const isAuthenticated = useAuthStore((s) => s.isAuthenticated) - const setAuthenticated = useAuthStore((s) => s.setAuthenticated) - - useEffect(() => { - if (!isMockAuth) { - setAuthenticated(!!session) - } - }, [session, setAuthenticated, isMockAuth]) - - // In mock auth mode, rely on Zustand store (set by Login/Register pages) - if (isMockAuth) { - if (!isAuthenticated) return - return - } if (isPending) { return ( diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index ae7fc0c..5044613 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -1,7 +1,6 @@ import { useState } from 'react' import { Link, useNavigate } from 'react-router-dom' import { authClient } from '../lib/auth-client.ts' -import { useAuthStore } from '../stores/auth.ts' export function Login() { const [email, setEmail] = useState('') @@ -9,7 +8,6 @@ export function Login() { const [error, setError] = useState('') const [loading, setLoading] = useState(false) const navigate = useNavigate() - const setAuthenticated = useAuthStore((s) => s.setAuthenticated) async function handleSubmit(e: React.FormEvent) { e.preventDefault() @@ -40,12 +38,7 @@ export function Login() { setError('Sign in failed. Please try again.') } } catch { - if (import.meta.env.VITE_MOCK_AUTH === 'true') { - setAuthenticated(true) - navigate('/') - } else { - setError('Invalid email or password. Please try again.') - } + setError('Invalid email or password. Please try again.') } finally { setLoading(false) } diff --git a/src/pages/Register.tsx b/src/pages/Register.tsx index c75e2d6..960aa0a 100644 --- a/src/pages/Register.tsx +++ b/src/pages/Register.tsx @@ -1,7 +1,6 @@ import { useState } from 'react' import { Link, useNavigate } from 'react-router-dom' import { authClient } from '../lib/auth-client.ts' -import { useAuthStore } from '../stores/auth.ts' export function Register() { const [name, setName] = useState('') @@ -10,7 +9,6 @@ export function Register() { const [error, setError] = useState('') const [loading, setLoading] = useState(false) const navigate = useNavigate() - const setAuthenticated = useAuthStore((s) => s.setAuthenticated) async function handleSubmit(e: React.FormEvent) { e.preventDefault() @@ -48,12 +46,7 @@ export function Register() { setError('Account created! Please sign in.') } } catch { - if (import.meta.env.VITE_MOCK_AUTH === 'true') { - setAuthenticated(true) - navigate('/') - } else { - setError('Registration failed. Please try again.') - } + setError('Registration failed. Please try again.') } finally { setLoading(false) } From 6fe91c748cae3fe189807d34988cb64531a1c075 Mon Sep 17 00:00:00 2001 From: Barcode Betty Date: Wed, 15 Apr 2026 03:30:44 +0000 Subject: [PATCH 3/4] feat(auth): enable email verification with Resend Co-Authored-By: Paperclip --- src/App.tsx | 2 + src/pages/Register.tsx | 56 ++++++++++++++++--- src/pages/VerifyEmail.tsx | 113 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 162 insertions(+), 9 deletions(-) create mode 100644 src/pages/VerifyEmail.tsx diff --git a/src/App.tsx b/src/App.tsx index ee4c2dc..953553a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -15,6 +15,7 @@ import { AccountLinking } from './pages/AccountLinking.tsx' import { Login } from './pages/Login.tsx' import { Register } from './pages/Register.tsx' import { ForgotPassword } from './pages/ForgotPassword.tsx' +import { VerifyEmail } from './pages/VerifyEmail.tsx' const queryClient = new QueryClient({ defaultOptions: { @@ -47,6 +48,7 @@ export default function App() { } /> } /> } /> + } /> diff --git a/src/pages/Register.tsx b/src/pages/Register.tsx index c75e2d6..f40ea2d 100644 --- a/src/pages/Register.tsx +++ b/src/pages/Register.tsx @@ -9,6 +9,9 @@ export function Register() { const [password, setPassword] = useState('') const [error, setError] = useState('') const [loading, setLoading] = useState(false) + const [registrationComplete, setRegistrationComplete] = useState(false) + const [resendLoading, setResendLoading] = useState(false) + const [resendMessage, setResendMessage] = useState('') const navigate = useNavigate() const setAuthenticated = useAuthStore((s) => s.setAuthenticated) @@ -38,15 +41,7 @@ export function Register() { throw new Error(authError.message ?? 'Registration failed') } - // After successful signUp, force a session fetch to confirm the cookie is set - // before navigating to the protected route - const sessionResult = await authClient.getSession() - if (sessionResult.data) { - navigate('/') - } else { - // Session not established — show success message and link to login - setError('Account created! Please sign in.') - } + setRegistrationComplete(true) } catch { if (import.meta.env.VITE_MOCK_AUTH === 'true') { setAuthenticated(true) @@ -59,6 +54,49 @@ export function Register() { } } + async function handleResendVerification() { + setResendLoading(true) + setResendMessage('') + try { + const { error } = await authClient.sendVerificationEmail({ email }) + if (error) { + setResendMessage('Failed to resend. Please try again.') + } else { + setResendMessage('Verification email sent!') + } + } finally { + setResendLoading(false) + } + } + + if (registrationComplete) { + return ( +
+

Check your email

+

+ We sent a verification link to {email}. Click it to activate your account. +

+ + {resendMessage && ( +

{resendMessage}

+ )} +

+ Already have an account?{' '} + + Sign in + +

+
+ ) + } + return (

Create Account

diff --git a/src/pages/VerifyEmail.tsx b/src/pages/VerifyEmail.tsx new file mode 100644 index 0000000..64da657 --- /dev/null +++ b/src/pages/VerifyEmail.tsx @@ -0,0 +1,113 @@ +import { useEffect, useState } from "react"; +import { useNavigate, useSearchParams } from "react-router-dom"; +import { authClient } from "../lib/auth-client.ts"; + +export function VerifyEmail() { + const [searchParams] = useSearchParams(); + const navigate = useNavigate(); + const [status, setStatus] = useState<"verifying" | "success" | "error">("verifying"); + const [resendEmail, setResendEmail] = useState(""); + const [showResend, setShowResend] = useState(false); + const [resending, setResending] = useState(false); + const [resendMessage, setResendMessage] = useState(""); + + useEffect(() => { + const token = searchParams.get("token"); + const callbackURL = searchParams.get("callbackURL") || "/"; + + if (!token) { + setStatus("error"); + return; + } + + authClient.verifyEmail({ query: { token } }) + .then(() => { + setStatus("success"); + setTimeout(() => { + navigate(callbackURL); + }, 2000); + }) + .catch(() => { + setStatus("error"); + }); + }, [searchParams, navigate]); + + async function handleResend() { + if (!resendEmail) { + setResendMessage("Please enter your email address."); + return; + } + + setResending(true); + setResendMessage(""); + + try { + const { error } = await authClient.sendVerificationEmail({ email: resendEmail }); + if (error) { + setResendMessage("Failed to resend. Please try again."); + } else { + setResendMessage("Verification email sent!"); + setShowResend(false); + } + } finally { + setResending(false); + } + } + + return ( +
+ {status === "verifying" && ( + <> +
+

Verifying your email...

+

Please wait while we verify your email address.

+ + )} + + {status === "success" && ( + <> +

Email verified!

+

Redirecting you shortly...

+ + )} + + {status === "error" && ( + <> +

Verification failed

+

The verification link may have expired or is invalid.

+ + {!showResend ? ( + + ) : ( +
+ setResendEmail(e.target.value)} + className="min-h-12 w-full rounded-xl border border-gray-200 px-4 text-base focus:border-brand-blue focus:outline-none focus:ring-1 focus:ring-brand-blue" + /> + + {resendMessage && ( +

{resendMessage}

+ )} +
+ )} + + )} +
+ ); +} \ No newline at end of file From 7651e0e72c67aee5d122810aaaaf5e8942096637 Mon Sep 17 00:00:00 2001 From: Barcode Betty Date: Tue, 14 Apr 2026 13:18:13 +0000 Subject: [PATCH 4/4] Enable Better-Auth email verification with Resend - Add emailVerification.sendVerificationEmail config to auth/src/auth.ts using Resend to send verification emails on sign-up - Add resend npm package to auth/package.json - Update auth/.env.example with RESEND_API_KEY and FROM_EMAIL - Create VerifyEmail.tsx page with token verification flow, spinner UX, success/Error states, and resend option - Update Register.tsx to redirect to /verify-email after signup instead of auto-navigating to dashboard - Add /verify-email route to App.tsx - Frontend shows 'check your email' step after registration Co-Authored-By: Paperclip --- src/pages/VerifyEmail.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/VerifyEmail.tsx b/src/pages/VerifyEmail.tsx index 64da657..d1c5fb3 100644 --- a/src/pages/VerifyEmail.tsx +++ b/src/pages/VerifyEmail.tsx @@ -110,4 +110,4 @@ export function VerifyEmail() { )}
); -} \ No newline at end of file +}