From 80917efa8f2f7872220f9fb9bf196643dd602bcf Mon Sep 17 00:00:00 2001 From: Avery Felts Date: Mon, 2 Feb 2026 01:10:36 -0700 Subject: [PATCH] Fix auth error handling and revert debug UI --- src/app/api/auth/email/route.ts | 26 +++++++----- src/app/api/auth/verify/route.ts | 69 ++++++++++++++++++++++++++++++++ src/components/UnifiedLogin.tsx | 69 ++++++++++++++++++++++++++++++-- 3 files changed, 149 insertions(+), 15 deletions(-) create mode 100644 src/app/api/auth/verify/route.ts diff --git a/src/app/api/auth/email/route.ts b/src/app/api/auth/email/route.ts index f3c8b70..f499bbe 100644 --- a/src/app/api/auth/email/route.ts +++ b/src/app/api/auth/email/route.ts @@ -18,28 +18,35 @@ export async function POST(request: NextRequest) { return NextResponse.json({ error: 'Email and password are required' }, { status: 400 }); } - let user; - // Handle Signup if (type === 'signup') { try { - await workos.userManagement.createUser({ + const newUser = await workos.userManagement.createUser({ email, password, firstName, lastName, - emailVerified: false, // WorkOS sends verification email automatically if configured + emailVerified: false, }); + + // Return early for signup to allow verification + return NextResponse.json({ + success: true, + pendingVerification: true, + userId: newUser.id, + }); + } catch (error: any) { // Handle user already exists - if (error.code === 'user_already_exists') { - return NextResponse.json({ error: 'A user with this email already exists.' }, { status: 409 }); + if (error.code === 'user_already_exists' || + (error.code === 'user_creation_error' && error.errors?.some((e: any) => e.code === 'email_not_available'))) { + return NextResponse.json({ error: 'A user with this email already exists. Please sign in.' }, { status: 409 }); } throw error; } } - // Authenticate (for both login and after signup) + // Authenticate (Login) try { const response = await workos.userManagement.authenticateWithPassword({ email, @@ -47,10 +54,7 @@ export async function POST(request: NextRequest) { clientId, }); - user = response.user; - - // extensive logging to debug structure if needed - // console.log('Auth response:', JSON.stringify(response, null, 2)); + const user = response.user; // Create session await setSession({ diff --git a/src/app/api/auth/verify/route.ts b/src/app/api/auth/verify/route.ts new file mode 100644 index 0000000..11a62c6 --- /dev/null +++ b/src/app/api/auth/verify/route.ts @@ -0,0 +1,69 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { workos, clientId } from '@/lib/workos'; +import { setSession } from '@/lib/session'; + +export async function POST(request: NextRequest) { + try { + const body = await request.json() as { + userId?: string; + code?: string; + email?: string; + password?: string; + stayLoggedIn?: boolean; + }; + const { userId, code, email, password, stayLoggedIn } = body; + + if (!userId || !code || !email || !password) { + return NextResponse.json({ error: 'Missing verification details' }, { status: 400 }); + } + + // 1. Verify the email with the code + try { + await workos.userManagement.verifyEmail({ + code, + userId, + }); + } catch (error: any) { + console.error('Verification error:', error); + return NextResponse.json({ + error: error.message || 'Invalid verification code' + }, { status: 400 }); + } + + // 2. If verification successful, authenticate the user to create a session + try { + const response = await workos.userManagement.authenticateWithPassword({ + email, + password, + clientId, + }); + + const user = response.user; + + await setSession({ + user: { + id: user.id, + email: user.email, + firstName: user.firstName, + lastName: user.lastName, + profilePictureUrl: user.profilePictureUrl, + }, + accessToken: response.accessToken, + refreshToken: response.refreshToken, + stayLoggedIn: !!stayLoggedIn, + }); + + return NextResponse.json({ success: true, user }); + + } catch (error: any) { + console.error('Post-verification auth error:', error); + return NextResponse.json({ + error: 'Verification successful, but login failed. Please try logging in.' + }, { status: 400 }); + } + + } catch (error: any) { + console.error('Verify API error:', error); + return NextResponse.json({ error: 'An internal error occurred.' }, { status: 500 }); + } +} diff --git a/src/components/UnifiedLogin.tsx b/src/components/UnifiedLogin.tsx index 0bdbb4d..9879248 100644 --- a/src/components/UnifiedLogin.tsx +++ b/src/components/UnifiedLogin.tsx @@ -20,6 +20,10 @@ export function UnifiedLogin({ onSuccess }: UnifiedLoginProps) { const [error, setError] = useState(null); const [stayLoggedIn, setStayLoggedIn] = useState(false); + const [pendingVerification, setPendingVerification] = useState(false); + const [verificationCode, setVerificationCode] = useState(''); + const [userId, setUserId] = useState(null); + const [formData, setFormData] = useState({ email: '', password: '', @@ -33,6 +37,34 @@ export function UnifiedLogin({ onSuccess }: UnifiedLoginProps) { setError(null); try { + // If we are pending verification, submit the code + if (pendingVerification) { + const res = await fetch('/api/auth/verify', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + userId, + code: verificationCode, + email: formData.email, + password: formData.password, + stayLoggedIn, + }), + }); + + const data = await res.json() as { error?: string }; + + if (!res.ok) { + throw new Error(data.error || 'Verification failed'); + } + + // Success + if (onSuccess) onSuccess(); + router.push('/home'); + router.refresh(); + return; + } + + // Normal login / signup initiation const res = await fetch('/api/auth/email', { method: 'POST', headers: { 'Content-Type': 'application/json' }, @@ -43,13 +75,21 @@ export function UnifiedLogin({ onSuccess }: UnifiedLoginProps) { }), }); - const data = await res.json() as { error?: string }; + const data = await res.json() as { error?: string, success?: boolean, pendingVerification?: boolean, userId?: string }; if (!res.ok) { throw new Error(data.error || 'Authentication failed'); } - // Success + // Check if we need verification + if (data.pendingVerification) { + setPendingVerification(true); + setUserId(data.userId || null); + setLoading(false); // Stop loading so user can enter code + return; + } + + // Success (Direct login) if (onSuccess) onSuccess(); router.push('/home'); router.refresh(); @@ -235,7 +275,7 @@ export function UnifiedLogin({ onSuccess }: UnifiedLoginProps) {
- {!isSignup && ( + {!isSignup && !pendingVerification && ( { @@ -258,11 +298,32 @@ export function UnifiedLogin({ onSuccess }: UnifiedLoginProps) { onChange={(e) => setFormData({ ...formData, password: e.target.value })} required minLength={8} + disabled={pendingVerification} className="pl-10 bg-slate-50 dark:bg-slate-800/50" />
+ {pendingVerification && ( +
+ + setVerificationCode(e.target.value)} + required + className="bg-primary/5 border-primary/20 text-lg tracking-widest text-center font-mono placeholder:font-sans placeholder:text-sm placeholder:tracking-normal" + /> +

+ + + Please check your spam/junk folder if you don't see the email in your inbox. + +

+
+ )} +