197 lines
7.3 KiB
TypeScript
197 lines
7.3 KiB
TypeScript
export default defineEventHandler(async (event) => {
|
|
console.log('🔄 Forgot password endpoint called at:', new Date().toISOString());
|
|
|
|
try {
|
|
const { email } = await readBody(event);
|
|
|
|
console.log('📧 Password reset request for email:', email ? 'present' : 'missing');
|
|
|
|
// Input validation
|
|
if (!email || typeof email !== 'string') {
|
|
throw createError({
|
|
statusCode: 400,
|
|
statusMessage: 'Email is required'
|
|
});
|
|
}
|
|
|
|
// Basic email validation
|
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
if (!emailRegex.test(email)) {
|
|
throw createError({
|
|
statusCode: 400,
|
|
statusMessage: 'Please enter a valid email address'
|
|
});
|
|
}
|
|
|
|
const config = useRuntimeConfig();
|
|
|
|
// Validate Keycloak configuration
|
|
if (!config.keycloak?.issuer || !config.keycloak?.clientId || !config.keycloak?.clientSecret) {
|
|
console.error('❌ Missing Keycloak configuration');
|
|
throw createError({
|
|
statusCode: 500,
|
|
statusMessage: 'Authentication service configuration error'
|
|
});
|
|
}
|
|
|
|
console.log('🔧 Using Keycloak config for password reset:', {
|
|
issuer: config.keycloak.issuer,
|
|
clientId: config.keycloak.clientId
|
|
});
|
|
|
|
try {
|
|
// Get admin token for Keycloak admin API
|
|
const adminTokenResponse = await fetch(`${config.keycloak.issuer}/protocol/openid-connect/token`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
'User-Agent': 'MonacoUSA-Portal/1.0'
|
|
},
|
|
body: new URLSearchParams({
|
|
grant_type: 'client_credentials',
|
|
client_id: config.keycloak.clientId,
|
|
client_secret: config.keycloak.clientSecret
|
|
})
|
|
});
|
|
|
|
if (!adminTokenResponse.ok) {
|
|
console.error('❌ Failed to get admin token:', adminTokenResponse.status);
|
|
throw new Error('Failed to authenticate with admin service');
|
|
}
|
|
|
|
const adminToken = await adminTokenResponse.json();
|
|
console.log('✅ Admin token obtained');
|
|
|
|
// Find user by email using Keycloak admin API
|
|
const realmName = config.keycloak.issuer.split('/realms/')[1];
|
|
const adminBaseUrl = config.keycloak.issuer.replace('/realms/', '/admin/realms/');
|
|
|
|
const usersResponse = await fetch(`${adminBaseUrl}/users?email=${encodeURIComponent(email)}&exact=true`, {
|
|
headers: {
|
|
'Authorization': `Bearer ${adminToken.access_token}`,
|
|
'User-Agent': 'MonacoUSA-Portal/1.0'
|
|
}
|
|
});
|
|
|
|
if (!usersResponse.ok) {
|
|
console.error('❌ Failed to search users:', usersResponse.status);
|
|
throw new Error('Failed to search for user');
|
|
}
|
|
|
|
const users = await usersResponse.json();
|
|
console.log('🔍 User search result:', { found: users.length > 0 });
|
|
|
|
if (users.length === 0) {
|
|
// For security, don't reveal if email exists or not
|
|
console.log('⚠️ Email not found, but returning success message for security');
|
|
return {
|
|
success: true,
|
|
message: 'If the email exists in our system, a reset link has been sent.'
|
|
};
|
|
}
|
|
|
|
const userId = users[0].id;
|
|
console.log('👤 Found user:', { id: userId, email: users[0].email });
|
|
|
|
// Send reset password email using Keycloak's execute-actions-email
|
|
// Add query parameters for better email template rendering
|
|
const resetUrl = new URL(`${adminBaseUrl}/users/${userId}/execute-actions-email`);
|
|
resetUrl.searchParams.set('clientId', config.keycloak.clientId);
|
|
resetUrl.searchParams.set('redirectUri', `${config.keycloak.callbackUrl.replace('/auth/callback', '/login')}`);
|
|
resetUrl.searchParams.set('lifespan', '43200'); // 12 hours
|
|
|
|
console.log('🔄 Sending password reset email with parameters:', {
|
|
clientId: config.keycloak.clientId,
|
|
redirectUri: resetUrl.searchParams.get('redirectUri'),
|
|
lifespan: resetUrl.searchParams.get('lifespan')
|
|
});
|
|
|
|
// Create AbortController for timeout handling
|
|
const controller = new AbortController();
|
|
const timeoutId = setTimeout(() => controller.abort(), 30000); // 30 second timeout
|
|
|
|
const resetResponse = await fetch(resetUrl.toString(), {
|
|
method: 'PUT',
|
|
headers: {
|
|
'Authorization': `Bearer ${adminToken.access_token}`,
|
|
'Content-Type': 'application/json',
|
|
'User-Agent': 'MonacoUSA-Portal/1.0'
|
|
},
|
|
body: JSON.stringify(['UPDATE_PASSWORD']),
|
|
signal: controller.signal
|
|
});
|
|
|
|
clearTimeout(timeoutId);
|
|
|
|
if (!resetResponse.ok) {
|
|
console.error('❌ Failed to send reset email:', resetResponse.status);
|
|
const errorText = await resetResponse.text().catch(() => 'Unknown error');
|
|
console.error('Reset email error details:', errorText);
|
|
|
|
// Enhanced error handling for different scenarios
|
|
if (resetResponse.status === 500) {
|
|
console.error('🚨 SMTP server error detected - this usually indicates email configuration issues in Keycloak');
|
|
console.error('💡 Suggestion: Check Keycloak Admin Console → Realm Settings → Email tab');
|
|
|
|
// For now, still return success to user for security, but log the issue
|
|
console.log('🔄 Returning success message to user despite email failure for security');
|
|
return {
|
|
success: true,
|
|
message: 'If the email exists in our system, a reset link has been sent. If you don\'t receive an email, please contact your administrator.'
|
|
};
|
|
}
|
|
|
|
throw new Error('Failed to send reset email');
|
|
}
|
|
|
|
console.log('✅ Password reset email sent successfully');
|
|
|
|
return {
|
|
success: true,
|
|
message: 'If the email exists in our system, a reset link has been sent.'
|
|
};
|
|
|
|
} catch (keycloakError: any) {
|
|
console.error('❌ Keycloak API error:', keycloakError);
|
|
|
|
// Handle timeout errors specifically
|
|
if (keycloakError.name === 'AbortError') {
|
|
console.error('⏰ Password reset request timed out after 30 seconds');
|
|
return {
|
|
success: true,
|
|
message: 'Password reset request is being processed. If the email exists in our system, a reset link will be sent shortly.'
|
|
};
|
|
}
|
|
|
|
// Handle SMTP/email server errors
|
|
if (keycloakError.message?.includes('send reset email') || keycloakError.message?.includes('SMTP')) {
|
|
console.error('📧 Email server error detected, but user search was successful');
|
|
return {
|
|
success: true,
|
|
message: 'If the email exists in our system, a reset link has been sent. If you don\'t receive an email, please contact your administrator.'
|
|
};
|
|
}
|
|
|
|
// For security, don't reveal specific errors to the user
|
|
return {
|
|
success: true,
|
|
message: 'If the email exists in our system, a reset link has been sent.'
|
|
};
|
|
}
|
|
|
|
} catch (error: any) {
|
|
console.error('❌ Forgot password error:', error);
|
|
|
|
// If it's already a createError, just throw it
|
|
if (error.statusCode) {
|
|
throw error;
|
|
}
|
|
|
|
// Generic error for unexpected issues
|
|
throw createError({
|
|
statusCode: 500,
|
|
statusMessage: 'Failed to process password reset request. Please try again.'
|
|
});
|
|
}
|
|
});
|