diff --git a/backend/config/nodemailer.js b/backend/config/nodemailer.js index 255f0030..eb354cbc 100644 --- a/backend/config/nodemailer.js +++ b/backend/config/nodemailer.js @@ -101,3 +101,45 @@ exports.sendReservationConfirmation = async (email, reservationDetails) => { } } }; + +exports.sendVerificationMail = async (email, verificationCode) => { + const emailText = ` + Dear Customer, + + Please use this verification code for resetting your password. Here's your code': + + RCode: ${verificationCode} + + Thank you for choosing our service. We are happy to help you. + + Best regards, + PlayCafe + `; + + try { + await transporter.sendMail({ + from: process.env.EMAIL_USER, + to: email, + subject: "Password Reset Verification Code", + text: emailText, + }); + logger.info("Verification code sent successfully via email", { + email, + }); + } catch (error) { + logger.error("Failed to send verification code email", { + error, + email, + }); + if (error.code === "ECONNREFUSED") { + throw new Error( + "Failed to connect to email server. Please try again later.", + ); + } else { + throw new Error( + `Failed to send verification email: ${error.message}`, + ); + } + } + +} \ No newline at end of file diff --git a/backend/controller/forgotPassword.controller.js b/backend/controller/forgotPassword.controller.js new file mode 100644 index 00000000..61b025a1 --- /dev/null +++ b/backend/controller/forgotPassword.controller.js @@ -0,0 +1,86 @@ +/* eslint-disable no-unused-vars */ +const bcrypt = require("bcrypt"); +const { z } = require("zod"); +const Customer = require("../models/customer.model"); +const { sendVerificationMail } = require("../config/nodemailer"); + + +// Define the schema +const forgotPasswordSchema = z.object({ + email: z.string().email("Invalid email address"), +}); + +async function verifyEmail(req, res) { + // Validate the request body + const validation = forgotPasswordSchema.safeParse(req.body); + + if (!validation.success) { + return res.status(400).json({ error: validation.error.errors }); + } + + const existingCustomer = await Customer.findOne({ email: req.body.email }); + if (!existingCustomer) { + return res.status(404).json({ error: "Email is not registered" }); + } + + const verifyCode = Math.floor(100000 + Math.random() * 900000).toString(); + + existingCustomer.verificationCode = verifyCode; + await existingCustomer.save(); + + sendVerificationMail(req.body.email, verifyCode) + + try { + res.status(201).json({ id: existingCustomer._id, success: true }); + } catch (error) { + res.status(500).json({ error: "Internal server error" }); + } +} + +async function verifyOTP(req, res) { + try { + const { id, otp } = req.body; + + const existingCustomer = await Customer.findOne({ _id: id }); + + if (!existingCustomer) { + return res.status(404).json({ error: "User not found" }); + } + + if(existingCustomer.verificationCode !== otp){ + return res.status(400).json({ error: "Invalid verification code", success: false }); + } + + existingCustomer.verificationCode = "" + await existingCustomer.save(); + + return res.status(200).json({ message: "verified", success: true}) + } catch (error) { + return res.status(500).json({ message: "Internal server error", success: false}) + } + + +} + +async function resetPassword(req, res) { + try { + const {id, password} = req.body + + const customer = await Customer.findOne({ _id: id }); + if (!customer) { + return res.status(401).json({ error: "User not found" }); + } + const hashedPassword = await bcrypt.hash(password, 10); + customer.password = hashedPassword; + await customer.save(); + res.json({ message: "Password reset successful" }); + } catch (error) { + res.status(501).json({ error: "Internal server error" }); + } +} + +module.exports = { + verifyEmail, + verifyOTP, + resetPassword, +}; diff --git a/backend/models/customer.model.js b/backend/models/customer.model.js index 7b196dd7..cd446f41 100644 --- a/backend/models/customer.model.js +++ b/backend/models/customer.model.js @@ -17,6 +17,10 @@ const customerSchema = new Schema( message: (props) => `${props.value} is not a valid email address!`, }, }, + verificationCode: { + type: String, + default: "" + }, role: { type: String, default: "customer", diff --git a/backend/routes/forgotRouter.js b/backend/routes/forgotRouter.js new file mode 100644 index 00000000..14024474 --- /dev/null +++ b/backend/routes/forgotRouter.js @@ -0,0 +1,15 @@ +const express = require("express"); +const { + verifyEmail, + verifyOTP, + resetPassword, +} = require("../controller/forgotPassword.controller"); + +const router = express.Router(); +require("dotenv").config(); + +router.post("/verify-email", verifyEmail); +router.post("/verify-otp", verifyOTP); +router.post("/resetpassword", resetPassword); + +module.exports = router; diff --git a/backend/routes/index.js b/backend/routes/index.js index 3893069a..63bcbbfb 100644 --- a/backend/routes/index.js +++ b/backend/routes/index.js @@ -29,7 +29,7 @@ try { } router.get("/", (req, res) => { - res.json({ + return res.json({ message: "Welcome to the restaurant API!", version: "1.0.0", endpoints: { @@ -46,5 +46,6 @@ router.use("/feedback", feedbackRouter); router.use("/user", require("./customerRouter")); router.use("/reservation", require("./reservationRouter")); router.use("/newsletter", require("./newsletterRoute")); +router.use("/forgot", require("./forgotRouter")) module.exports = router; diff --git a/frontend/src/components/Pages/EmailVerify.jsx b/frontend/src/components/Pages/EmailVerify.jsx new file mode 100644 index 00000000..00a952ab --- /dev/null +++ b/frontend/src/components/Pages/EmailVerify.jsx @@ -0,0 +1,111 @@ +import { Link, useNavigate } from 'react-router-dom'; +import photo from '../../assets/login.png'; +import React, { useState } from 'react'; +import { message } from 'antd'; + +const EmailVerify = () => { + const API_URL = import.meta.env.VITE_BACKEND_URL || 'http://localhost:3000'; + const navigate = useNavigate(); // Use useNavigate for navigation + const [email, setEmail] = useState("") + + const handleChange = (e) => { + setEmail(e.target.value); + }; + + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + // Helper function for email validation + const isValidEmail = (email) => { + // Basic email regex, consider using a more robust solution in production + return /\S+@\S+\.\S+/.test(email); + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + setIsLoading(true); + setError(null); + + // Add input validation // Basic validation examples + if (!isValidEmail(email)) { + setError('Please enter a valid email address'); + return; + } + + try { + const response = await fetch(`${API_URL}/api/forgot/verify-email`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + email: email + }), + }); + const result = await response.json(); + if (!response.ok) { + throw new Error(result.message || 'Reset password failed'); + } + console.log(result); + + + // Display success message and navigate to login + message.success('Password reset successfully! Please log in.'); + navigate(`/verifyotp/${result.id}`); + } catch (err) { + setError(err.message); + } finally { + setIsLoading(false); + } + }; + + return ( +