feat: add email check endpoint and fix email service

- Add POST /api/auth/check-email endpoint for email validation
- Check if email exists in database
- Validate email domain with MX DNS records
- Fix email service transporter lazy loading
- Add detailed logging for email sending
- Add FRONTEND_URL to .env for email links

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
soufiane 2025-12-02 16:16:09 +01:00
parent de9e4cd337
commit 2eddd7aa1a
4 changed files with 106 additions and 16 deletions

3
.env
View File

@ -14,6 +14,9 @@ FACEBOOK_APP_SECRET=e6889f4339d140c218f1df177149893f
JWT_SECRET=thetiptopsecret JWT_SECRET=thetiptopsecret
SESSION_SECRET=thetiptopsessionsecret SESSION_SECRET=thetiptopsessionsecret
# Frontend URL (pour les liens dans les emails)
FRONTEND_URL=http://localhost:3000
# Email Configuration (SMTP) # Email Configuration (SMTP)
SMTP_HOST=smtp.gmail.com SMTP_HOST=smtp.gmail.com
SMTP_PORT=587 SMTP_PORT=587

View File

@ -6,6 +6,10 @@ import { pool } from '../../db.js';
import { AppError, asyncHandler } from '../middleware/errorHandler.js'; import { AppError, asyncHandler } from '../middleware/errorHandler.js';
import { generateToken, generateJWT, getTokenExpiry, isExpired } from '../utils/helpers.js'; import { generateToken, generateJWT, getTokenExpiry, isExpired } from '../utils/helpers.js';
import { sendResetPasswordEmail, sendWelcomeEmail } from '../services/email.service.js'; import { sendResetPasswordEmail, sendWelcomeEmail } from '../services/email.service.js';
import dns from 'dns';
import { promisify } from 'util';
const resolveMx = promisify(dns.resolveMx);
/** /**
* Inscription d'un nouvel utilisateur * Inscription d'un nouvel utilisateur
@ -280,6 +284,70 @@ export const getMe = asyncHandler(async (req, res, next) => {
}); });
}); });
/**
* Vérifier si un email existe et est valide
* POST /api/auth/check-email
*/
export const checkEmail = asyncHandler(async (req, res, next) => {
const { email } = req.body;
if (!email) {
return next(new AppError('Email requis', 400));
}
// Validation format email
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
return res.json({
success: true,
isValid: false,
exists: false,
message: 'Format d\'email invalide',
details: {
formatValid: false,
domainValid: false,
mxRecords: false,
existsInDb: false
}
});
}
const domain = email.split('@')[1];
let mxValid = false;
let mxRecords = [];
// Vérification MX (enregistrements DNS du domaine)
try {
mxRecords = await resolveMx(domain);
mxValid = mxRecords && mxRecords.length > 0;
} catch (error) {
console.log(`❌ Pas d'enregistrements MX pour ${domain}:`, error.code);
mxValid = false;
}
// Vérifier si l'email existe déjà en BDD
const existingUser = await pool.query('SELECT id FROM users WHERE email = $1', [email.toLowerCase()]);
const existsInDb = existingUser.rows.length > 0;
// Résultat final
const isValid = mxValid && !existsInDb;
res.json({
success: true,
isValid,
exists: existsInDb,
message: existsInDb
? 'Cet email est déjà utilisé'
: (mxValid ? 'Email valide et disponible' : 'Le domaine de cet email n\'existe pas'),
details: {
formatValid: true,
domainValid: mxValid,
mxRecords: mxValid,
existsInDb
}
});
});
export default { export default {
register, register,
login, login,
@ -288,4 +356,5 @@ export default {
resetPassword, resetPassword,
logout, logout,
getMe, getMe,
checkEmail,
}; };

View File

@ -21,6 +21,7 @@ router.post('/login', validate(loginSchema), authController.login);
router.get('/verify-email/:token', authController.verifyEmail); router.get('/verify-email/:token', authController.verifyEmail);
router.post('/forgot-password', validate(forgotPasswordSchema), authController.forgotPassword); router.post('/forgot-password', validate(forgotPasswordSchema), authController.forgotPassword);
router.post('/reset-password', validate(resetPasswordSchema), authController.resetPassword); router.post('/reset-password', validate(resetPasswordSchema), authController.resetPassword);
router.post('/check-email', authController.checkEmail);
// Routes OAuth // Routes OAuth
router.post('/google', oauthController.googleLogin); router.post('/google', oauthController.googleLogin);

View File

@ -6,44 +6,59 @@ import dotenv from 'dotenv';
dotenv.config(); dotenv.config();
// Configuration du transporteur email // Configuration du transporteur email (créé à la demande pour éviter les problèmes de chargement)
const createTransporter = () => { let transporter = null;
const getTransporter = () => {
if (transporter) return transporter;
// Pour le développement, utilisez un service de test comme Ethereal // Pour le développement, utilisez un service de test comme Ethereal
// Pour la production, utilisez un vrai service SMTP (Gmail, SendGrid, etc.) // Pour la production, utilisez un vrai service SMTP (Gmail, SendGrid, etc.)
if (process.env.NODE_ENV === 'development' && !process.env.SMTP_HOST) { if (process.env.NODE_ENV === 'development' && !process.env.SMTP_HOST) {
// Mode développement sans configuration SMTP
console.log('⚠️ Mode développement: Les emails seront affichés dans la console'); console.log('⚠️ Mode développement: Les emails seront affichés dans la console');
return null; return null;
} }
return nodemailer.createTransport({ if (!process.env.SMTP_HOST || !process.env.SMTP_USER || !process.env.SMTP_PASS) {
console.error('❌ Configuration SMTP manquante:', {
host: !!process.env.SMTP_HOST,
user: !!process.env.SMTP_USER,
pass: !!process.env.SMTP_PASS
});
return null;
}
console.log('📧 Création du transporteur SMTP:', process.env.SMTP_HOST);
transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST, host: process.env.SMTP_HOST,
port: parseInt(process.env.SMTP_PORT) || 587, port: parseInt(process.env.SMTP_PORT) || 587,
secure: process.env.SMTP_PORT === '465', // true pour le port 465, false pour les autres secure: process.env.SMTP_PORT === '465',
auth: { auth: {
user: process.env.SMTP_USER, user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS, pass: process.env.SMTP_PASS,
}, },
tls: { tls: {
rejectUnauthorized: false // Ignore les erreurs de certificat SSL rejectUnauthorized: false
} }
}); });
};
const transporter = createTransporter(); return transporter;
};
/** /**
* Fonction générique pour envoyer un email * Fonction générique pour envoyer un email
*/ */
const sendEmail = async ({ to, subject, html, text }) => { const sendEmail = async ({ to, subject, html, text }) => {
try { try {
const emailTransporter = getTransporter();
// En mode dev sans SMTP, afficher l'email dans la console // En mode dev sans SMTP, afficher l'email dans la console
if (!transporter) { if (!emailTransporter) {
console.log('📧 Email qui aurait été envoyé:'); console.log('📧 Email qui aurait été envoyé (pas de transporteur):');
console.log('To:', to); console.log('To:', to);
console.log('Subject:', subject); console.log('Subject:', subject);
console.log('Content:', text || html); console.log('Content:', text ? text.substring(0, 200) : 'HTML content');
return { success: true, mode: 'dev' }; return { success: true, mode: 'dev' };
} }
@ -55,12 +70,14 @@ const sendEmail = async ({ to, subject, html, text }) => {
html, html,
}; };
const info = await transporter.sendMail(mailOptions); console.log('📤 Envoi email à:', to, '- Sujet:', subject);
console.log('✅ Email envoyé:', info.messageId); const info = await emailTransporter.sendMail(mailOptions);
console.log('✅ Email envoyé avec succès:', info.messageId);
return { success: true, messageId: info.messageId }; return { success: true, messageId: info.messageId };
} catch (error) { } catch (error) {
console.error('❌ Erreur envoi email:', error); console.error('❌ Erreur envoi email:', error.message);
throw new Error('Erreur lors de l\'envoi de l\'email'); console.error('❌ Stack:', error.stack);
throw new Error('Erreur lors de l\'envoi de l\'email: ' + error.message);
} }
}; };