implementation de nodemailer for email validation
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
const express = require("express");
|
||||
const cors = require("cors");
|
||||
|
||||
require("dotenv").config();
|
||||
|
||||
const authRoutes = require("./routes/auth.routes");
|
||||
const gameRoutes = require("./routes/game.routes");
|
||||
const employeeRoutes = require("./routes/employee.routes");
|
||||
const adminRoutes = require("./routes/admin.routes");
|
||||
|
||||
const swaggerUi = require("swagger-ui-express");
|
||||
const swaggerSpec = require("./docs/swagger");
|
||||
|
||||
@@ -15,24 +15,18 @@ const app = express();
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
|
||||
// routes
|
||||
app.use("/api/auth", authRoutes);
|
||||
app.use("/api/game", gameRoutes);
|
||||
app.use("/api/employee", employeeRoutes);
|
||||
app.use("/api/admin", adminRoutes);
|
||||
app.use(
|
||||
"/api-docs",
|
||||
swaggerUi.serve,
|
||||
swaggerUi.setup(swaggerSpec)
|
||||
);
|
||||
|
||||
// swagger
|
||||
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerSpec));
|
||||
|
||||
// test route
|
||||
app.get("/", (req, res) => {
|
||||
res.json({
|
||||
message: "API Thé Tip Top opérationnelle",
|
||||
});
|
||||
res.json({ message: "API Thé Tip Top opérationnelle" });
|
||||
});
|
||||
|
||||
const PORT = process.env.PORT || 5000;
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Serveur lancé sur http://localhost:${PORT}`);
|
||||
});
|
||||
module.exports = app;
|
||||
13
backend/src/config/mailer.js
Normal file
13
backend/src/config/mailer.js
Normal file
@@ -0,0 +1,13 @@
|
||||
const nodemailer = require("nodemailer");
|
||||
|
||||
const transporter = nodemailer.createTransport({
|
||||
host: process.env.MAIL_HOST,
|
||||
port: Number(process.env.MAIL_PORT),
|
||||
secure: false,
|
||||
auth: {
|
||||
user: process.env.MAIL_USER,
|
||||
pass: process.env.MAIL_PASS,
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = transporter;
|
||||
@@ -1,6 +1,8 @@
|
||||
const bcrypt = require("bcrypt");
|
||||
const jwt = require("jsonwebtoken");
|
||||
const crypto = require("crypto");
|
||||
const prisma = require("../config/prisma");
|
||||
const transporter = require("../config/mailer");
|
||||
|
||||
const register = async (req, res) => {
|
||||
try {
|
||||
@@ -32,6 +34,9 @@ const register = async (req, res) => {
|
||||
|
||||
const hashedPassword = await bcrypt.hash(password, 10);
|
||||
|
||||
const emailVerifyToken = crypto.randomBytes(32).toString("hex");
|
||||
const emailVerifyExpires = new Date(Date.now() + 1000 * 60 * 60); // 1h
|
||||
|
||||
const user = await prisma.user.create({
|
||||
data: {
|
||||
firstname,
|
||||
@@ -41,6 +46,9 @@ const register = async (req, res) => {
|
||||
birthdate: birthdate ? new Date(birthdate) : null,
|
||||
gender,
|
||||
marketingOptIn: marketingOptIn || false,
|
||||
isEmailVerified: false,
|
||||
emailVerifyToken,
|
||||
emailVerifyExpires,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
@@ -49,12 +57,28 @@ const register = async (req, res) => {
|
||||
email: true,
|
||||
role: true,
|
||||
marketingOptIn: true,
|
||||
isEmailVerified: true,
|
||||
createdAt: true,
|
||||
},
|
||||
});
|
||||
|
||||
const verifyUrl = `${process.env.APP_URL}/api/auth/verify-email/${emailVerifyToken}`;
|
||||
|
||||
await transporter.sendMail({
|
||||
from: process.env.MAIL_FROM,
|
||||
to: email,
|
||||
subject: "Validation de votre compte Thé Tip Top",
|
||||
html: `
|
||||
<h2>Bienvenue ${firstname}</h2>
|
||||
<p>Merci pour votre inscription.</p>
|
||||
<p>Veuillez cliquer sur le lien ci-dessous pour valider votre compte :</p>
|
||||
<a href="${verifyUrl}">Valider mon compte</a>
|
||||
<p>Ce lien expire dans 1 heure.</p>
|
||||
`,
|
||||
});
|
||||
|
||||
return res.status(201).json({
|
||||
message: "Inscription réussie.",
|
||||
message: "Inscription réussie. Un email de validation a été envoyé.",
|
||||
user,
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -65,6 +89,45 @@ const register = async (req, res) => {
|
||||
}
|
||||
};
|
||||
|
||||
const verifyEmail = async (req, res) => {
|
||||
try {
|
||||
const { token } = req.params;
|
||||
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
emailVerifyToken: token,
|
||||
emailVerifyExpires: {
|
||||
gt: new Date(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return res.status(400).json({
|
||||
message: "Lien de validation invalide ou expiré.",
|
||||
});
|
||||
}
|
||||
|
||||
await prisma.user.update({
|
||||
where: { id: user.id },
|
||||
data: {
|
||||
isEmailVerified: true,
|
||||
emailVerifyToken: null,
|
||||
emailVerifyExpires: null,
|
||||
},
|
||||
});
|
||||
|
||||
return res.status(200).json({
|
||||
message: "Email validé avec succès. Vous pouvez maintenant vous connecter.",
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Erreur verifyEmail:", error);
|
||||
return res.status(500).json({
|
||||
message: "Erreur serveur lors de la validation de l'email.",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const login = async (req, res) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
@@ -85,6 +148,12 @@ const login = async (req, res) => {
|
||||
});
|
||||
}
|
||||
|
||||
if (!user.isEmailVerified) {
|
||||
return res.status(403).json({
|
||||
message: "Veuillez valider votre adresse email avant de vous connecter.",
|
||||
});
|
||||
}
|
||||
|
||||
const isPasswordValid = await bcrypt.compare(password, user.password);
|
||||
|
||||
if (!isPasswordValid) {
|
||||
@@ -121,6 +190,114 @@ const login = async (req, res) => {
|
||||
}
|
||||
};
|
||||
|
||||
const forgotPassword = async (req, res) => {
|
||||
try {
|
||||
const { email } = req.body;
|
||||
|
||||
if (!email) {
|
||||
return res.status(400).json({
|
||||
message: "Email requis.",
|
||||
});
|
||||
}
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { email },
|
||||
});
|
||||
|
||||
// Sécurité : ne pas dire si l'email existe ou non
|
||||
if (!user) {
|
||||
return res.status(200).json({
|
||||
message: "Si un compte existe avec cet email, un lien de réinitialisation a été envoyé.",
|
||||
});
|
||||
}
|
||||
|
||||
const resetPasswordToken = crypto.randomBytes(32).toString("hex");
|
||||
const resetPasswordExpires = new Date(Date.now() + 1000 * 60 * 30); // 30 min
|
||||
|
||||
await prisma.user.update({
|
||||
where: { id: user.id },
|
||||
data: {
|
||||
resetPasswordToken,
|
||||
resetPasswordExpires,
|
||||
},
|
||||
});
|
||||
|
||||
const resetUrl = `${process.env.APP_URL}/api/auth/reset-password/${resetPasswordToken}`;
|
||||
|
||||
await transporter.sendMail({
|
||||
from: process.env.MAIL_FROM,
|
||||
to: user.email,
|
||||
subject: "Réinitialisation de votre mot de passe Thé Tip Top",
|
||||
html: `
|
||||
<h2>Réinitialisation du mot de passe</h2>
|
||||
<p>Bonjour ${user.firstname},</p>
|
||||
<p>Vous avez demandé la réinitialisation de votre mot de passe.</p>
|
||||
<p>Cliquez sur le lien ci-dessous pour définir un nouveau mot de passe :</p>
|
||||
<a href="${resetUrl}">Réinitialiser mon mot de passe</a>
|
||||
<p>Ce lien expire dans 30 minutes.</p>
|
||||
<p>Si vous n'êtes pas à l'origine de cette demande, ignorez cet email.</p>
|
||||
`,
|
||||
});
|
||||
|
||||
return res.status(200).json({
|
||||
message: "Si un compte existe avec cet email, un lien de réinitialisation a été envoyé.",
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Erreur forgotPassword:", error);
|
||||
return res.status(500).json({
|
||||
message: "Erreur serveur lors de la demande de réinitialisation.",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const resetPassword = async (req, res) => {
|
||||
try {
|
||||
const { token } = req.params;
|
||||
const { password } = req.body;
|
||||
|
||||
if (!password || password.length < 8) {
|
||||
return res.status(400).json({
|
||||
message: "Le nouveau mot de passe doit contenir au moins 8 caractères.",
|
||||
});
|
||||
}
|
||||
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
resetPasswordToken: token,
|
||||
resetPasswordExpires: {
|
||||
gt: new Date(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return res.status(400).json({
|
||||
message: "Lien de réinitialisation invalide ou expiré.",
|
||||
});
|
||||
}
|
||||
|
||||
const hashedPassword = await bcrypt.hash(password, 10);
|
||||
|
||||
await prisma.user.update({
|
||||
where: { id: user.id },
|
||||
data: {
|
||||
password: hashedPassword,
|
||||
resetPasswordToken: null,
|
||||
resetPasswordExpires: null,
|
||||
},
|
||||
});
|
||||
|
||||
return res.status(200).json({
|
||||
message: "Mot de passe réinitialisé avec succès. Vous pouvez maintenant vous connecter.",
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Erreur resetPassword:", error);
|
||||
return res.status(500).json({
|
||||
message: "Erreur serveur lors de la réinitialisation du mot de passe.",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const me = async (req, res) => {
|
||||
return res.json({
|
||||
user: req.user,
|
||||
@@ -131,4 +308,7 @@ module.exports = {
|
||||
register,
|
||||
login,
|
||||
me,
|
||||
verifyEmail,
|
||||
resetPassword,
|
||||
forgotPassword
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
const express = require("express");
|
||||
const { register, login, me } = require("../controllers/auth.controller");
|
||||
const { register, login, verifyEmail, forgotPassword, resetPassword, me } = require("../controllers/auth.controller");
|
||||
const authMiddleware = require("../middlewares/auth.middleware");
|
||||
const validate = require("../middlewares/validate.middleware");
|
||||
const { registerSchema, loginSchema } = require("../validations/auth.validation");
|
||||
@@ -63,7 +63,99 @@ router.post("/login", validate(loginSchema), login);
|
||||
* 200:
|
||||
* description: Profil utilisateur
|
||||
*/
|
||||
|
||||
router.get("/me", authMiddleware, me);
|
||||
/**
|
||||
* @swagger
|
||||
* /api/auth/verify-email/{token}:
|
||||
* get:
|
||||
* summary: Vérification de l'adresse email
|
||||
* tags:
|
||||
* - Auth
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: token
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* description: Token de validation envoyé par email
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Email vérifié avec succès.
|
||||
* 400:
|
||||
* description: Lien de validation invalide ou expiré.
|
||||
* 500:
|
||||
* description: Erreur serveur.
|
||||
*/
|
||||
|
||||
router.get("/verify-email/:token",verifyEmail);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/auth/forgot-password:
|
||||
* post:
|
||||
* summary: Demande de réinitialisation du mot de passe
|
||||
* tags:
|
||||
* - Auth
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required:
|
||||
* - email
|
||||
* properties:
|
||||
* email:
|
||||
* type: string
|
||||
* format: email
|
||||
* example: jean.dupont@gmail.com
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Si un compte existe avec cet email, un lien de réinitialisation a été envoyé.
|
||||
* 400:
|
||||
* description: Email requis.
|
||||
* 500:
|
||||
* description: Erreur serveur.
|
||||
*/
|
||||
|
||||
router.post("/forgot-password", forgotPassword);
|
||||
/**
|
||||
* @swagger
|
||||
* /api/auth/reset-password/{token}:
|
||||
* post:
|
||||
* summary: Réinitialisation du mot de passe
|
||||
* tags:
|
||||
* - Auth
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: token
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* description: Token de réinitialisation reçu par email
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required:
|
||||
* - password
|
||||
* properties:
|
||||
* password:
|
||||
* type: string
|
||||
* format: password
|
||||
* example: NouveauPassword123
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Mot de passe réinitialisé avec succès.
|
||||
* 400:
|
||||
* description: Lien invalide, expiré ou mot de passe invalide.
|
||||
* 500:
|
||||
* description: Erreur serveur.
|
||||
*/
|
||||
router.post("/reset-password/:token",resetPassword);
|
||||
|
||||
|
||||
module.exports = router;
|
||||
Reference in New Issue
Block a user