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:
parent
de9e4cd337
commit
2eddd7aa1a
3
.env
3
.env
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user