monacousa-portal/server/api/auth/verify-email.get.ts

87 lines
3.7 KiB
TypeScript

export default defineEventHandler(async (event) => {
try {
const { token } = getQuery(event);
if (!token || typeof token !== 'string') {
console.log('[verify-email] Missing or invalid token, redirecting to expired page');
return sendRedirect(event, '/auth/verify-expired?reason=invalid', 302);
}
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 (keycloakUpdateError: any) {
console.error('[verify-email] Keycloak update failed:', keycloakUpdateError.message);
// Check if this is a retryable error or a permanent failure
if (keycloakUpdateError.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 = keycloakUpdateError.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 = keycloakUpdateError.message;
}
}
// Build success redirect URL with query parameters
const successUrl = new URL('/auth/verify-success', 'https://portal.monacousa.org');
successUrl.searchParams.set('email', email);
if (partialSuccess && keycloakError) {
successUrl.searchParams.set('warning', 'partial');
console.log('[verify-email] Redirecting to success page with partial warning');
} else {
console.log('[verify-email] Redirecting to success page - verification complete');
}
// Redirect to success page instead of returning JSON
return sendRedirect(event, successUrl.pathname + successUrl.search, 302);
} catch (error: any) {
console.error('[verify-email] Verification failed:', error.message);
// Redirect to appropriate error page instead of throwing errors
if (error.message?.includes('expired')) {
console.log('[verify-email] Token expired, redirecting to expired page');
return sendRedirect(event, '/auth/verify-expired?reason=expired', 302);
} else if (error.message?.includes('already used')) {
console.log('[verify-email] Token already used, redirecting to expired page');
return sendRedirect(event, '/auth/verify-expired?reason=used', 302);
} else if (error.message?.includes('not found')) {
console.log('[verify-email] Token not found, redirecting to expired page');
return sendRedirect(event, '/auth/verify-expired?reason=invalid', 302);
} else {
console.log('[verify-email] Generic verification error, redirecting to expired page');
return sendRedirect(event, '/auth/verify-expired?reason=invalid', 302);
}
}
});