export default defineEventHandler(async (event) => { try { const { token } = getQuery(event); if (!token || typeof token !== 'string') { throw createError({ statusCode: 400, statusMessage: 'Verification token is required' }); } console.log('[verify-email] Processing verification token...'); // Verify the token WITHOUT consuming it yet const { verifyEmailToken, consumeEmailToken } = await import('~/server/utils/email-tokens'); const { userId, email } = await verifyEmailToken(token); // Update user verification status in Keycloak const { createKeycloakAdminClient } = await import('~/server/utils/keycloak-admin'); const keycloak = createKeycloakAdminClient(); let partialSuccess = false; let keycloakError = null; try { await keycloak.updateUserProfile(userId, { emailVerified: true, attributes: { lastLoginDate: new Date().toISOString() } }); console.log('[verify-email] Successfully verified user:', userId, 'email:', email); // ONLY consume token after successful Keycloak update await consumeEmailToken(token); } catch (keycloakError: any) { console.error('[verify-email] Keycloak update failed:', keycloakError.message); // Check if this is a retryable error or a permanent failure if (keycloakError.message?.includes('error-user-attribute-required')) { // This is a configuration issue - don't consume token, allow retries console.log('[verify-email] Keycloak configuration error - token preserved for retry'); partialSuccess = true; keycloakError = keycloakError.message; } else { // For other errors, still consume token to prevent infinite retries console.log('[verify-email] Consuming token despite Keycloak error to prevent loops'); await consumeEmailToken(token); partialSuccess = true; keycloakError = keycloakError.message; } } // Return JSON response for client-side navigation return { success: true, data: { userId, email, partialSuccess, keycloakError: keycloakError || undefined } }; } catch (error: any) { console.error('[verify-email] Verification failed:', error.message); // Return error response if (error.message?.includes('expired')) { throw createError({ statusCode: 410, statusMessage: 'Verification link has expired. Please request a new one.' }); } else if (error.message?.includes('already used') || error.message?.includes('not found')) { throw createError({ statusCode: 409, statusMessage: 'This verification link has already been used or is invalid.' }); } else { throw createError({ statusCode: 400, statusMessage: error.message || 'Invalid verification link' }); } } });