export default defineEventHandler(async (event) => { try { const body = await readBody(event); const { email } = body; if (!email || typeof email !== 'string') { throw createError({ statusCode: 400, statusMessage: 'Email is required' }); } // Validate email format const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(email)) { throw createError({ statusCode: 400, statusMessage: 'Invalid email format' }); } console.log('[send-verification-email] Processing request for email:', email); // Check if user exists in Keycloak const { createKeycloakAdminClient } = await import('~/server/utils/keycloak-admin'); const keycloak = createKeycloakAdminClient(); let existingUsers; try { existingUsers = await keycloak.findUserByEmail(email.toLowerCase().trim()); } catch (error: any) { console.error('[send-verification-email] Failed to search users:', error.message); throw createError({ statusCode: 500, statusMessage: 'Failed to verify account status' }); } if (!existingUsers || existingUsers.length === 0) { throw createError({ statusCode: 404, statusMessage: 'No account found with this email address' }); } const user = existingUsers[0]; // Check if user is already verified if (user.emailVerified) { throw createError({ statusCode: 400, statusMessage: 'This email address is already verified' }); } // Rate limiting: check if we recently sent an email to this address const rateLimitKey = `verification_email_${email.toLowerCase()}`; // Simple in-memory rate limiting (in production, use Redis) const globalCache = globalThis as any; if (!globalCache.verificationEmailCache) { globalCache.verificationEmailCache = new Map(); } const lastSent = globalCache.verificationEmailCache.get(rateLimitKey); const cooldownPeriod = 2 * 60 * 1000; // 2 minutes if (lastSent && Date.now() - lastSent < cooldownPeriod) { throw createError({ statusCode: 429, statusMessage: 'Please wait a few minutes before requesting another verification email' }); } // Generate verification token const { generateEmailVerificationToken } = await import('~/server/utils/email-tokens'); const verificationToken = await generateEmailVerificationToken(user.id, email); // Get configuration const config = useRuntimeConfig(); const verificationLink = `${config.public.domain}/api/auth/verify-email?token=${verificationToken}`; // Send verification email const { getEmailService } = await import('~/server/utils/email'); const emailService = getEmailService(); try { await emailService.sendWelcomeEmail(email, { firstName: user.firstName || '', lastName: user.lastName || '', verificationLink, memberId: user.id }); console.log('[send-verification-email] Successfully sent verification email to:', email); // Update rate limiting cache globalCache.verificationEmailCache.set(rateLimitKey, Date.now()); // Clean up old rate limit entries periodically if (Math.random() < 0.1) { // 10% chance const now = Date.now(); for (const [key, timestamp] of globalCache.verificationEmailCache.entries()) { if (now - timestamp > cooldownPeriod * 2) { globalCache.verificationEmailCache.delete(key); } } } return { success: true, message: 'Verification email sent successfully' }; } catch (emailError: any) { console.error('[send-verification-email] Failed to send email:', emailError.message); throw createError({ statusCode: 500, statusMessage: 'Failed to send verification email. Please try again later.' }); } } catch (error: any) { console.error('[send-verification-email] Request failed:', error.message); // Re-throw HTTP errors if (error.statusCode) { throw error; } // Handle unexpected errors throw createError({ statusCode: 500, statusMessage: 'An unexpected error occurred. Please try again.' }); } });