monacousa-portal/server/api/auth/forgot-password.post.ts

146 lines
4.8 KiB
TypeScript
Raw Normal View History

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
const resetResponse = await fetch(`${adminBaseUrl}/users/${userId}/execute-actions-email`, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${adminToken.access_token}`,
'Content-Type': 'application/json',
'User-Agent': 'MonacoUSA-Portal/1.0'
},
body: JSON.stringify(['UPDATE_PASSWORD'])
});
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);
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);
// 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.'
});
}
});