104 lines
3.6 KiB
TypeScript
104 lines
3.6 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');
|
|
throw createError({
|
|
statusCode: 400,
|
|
statusMessage: 'Invalid or missing verification token'
|
|
});
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
// Return JSON response with email and success status
|
|
const responseData = {
|
|
success: true,
|
|
message: partialSuccess
|
|
? 'Email verified with partial success. You may experience minor account access issues.'
|
|
: 'Email verified successfully',
|
|
data: {
|
|
email,
|
|
partialSuccess,
|
|
keycloakError: partialSuccess ? keycloakError : null
|
|
}
|
|
};
|
|
|
|
console.log('[verify-email] Returning JSON response:', responseData);
|
|
return responseData;
|
|
|
|
} catch (error: any) {
|
|
console.error('[verify-email] Verification failed:', error.message);
|
|
|
|
// Return appropriate error responses as JSON
|
|
if (error.message?.includes('expired')) {
|
|
console.log('[verify-email] Token expired');
|
|
throw createError({
|
|
statusCode: 410,
|
|
statusMessage: 'Verification token has expired'
|
|
});
|
|
} else if (error.message?.includes('already used')) {
|
|
console.log('[verify-email] Token already used');
|
|
throw createError({
|
|
statusCode: 409,
|
|
statusMessage: 'Verification token has already been used'
|
|
});
|
|
} else if (error.message?.includes('not found')) {
|
|
console.log('[verify-email] Token not found');
|
|
throw createError({
|
|
statusCode: 404,
|
|
statusMessage: 'Verification token not found or invalid'
|
|
});
|
|
} else {
|
|
console.log('[verify-email] Generic verification error');
|
|
throw createError({
|
|
statusCode: 400,
|
|
statusMessage: error.message || 'Email verification failed'
|
|
});
|
|
}
|
|
}
|
|
});
|