Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented otp based forgot password #282

Merged
merged 1 commit into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions backend/config/nodemailer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}`,
);
}
}

}
86 changes: 86 additions & 0 deletions backend/controller/forgotPassword.controller.js
Original file line number Diff line number Diff line change
@@ -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();

RamakrushnaBiswal marked this conversation as resolved.
Show resolved Hide resolved
existingCustomer.verificationCode = verifyCode;
await existingCustomer.save();

sendVerificationMail(req.body.email, verifyCode)

RamakrushnaBiswal marked this conversation as resolved.
Show resolved Hide resolved
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;

RamakrushnaBiswal marked this conversation as resolved.
Show resolved Hide resolved
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

RamakrushnaBiswal marked this conversation as resolved.
Show resolved Hide resolved
const customer = await Customer.findOne({ _id: id });
if (!customer) {
return res.status(401).json({ error: "User not found" });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Use Consistent HTTP Status Codes for 'User Not Found'

The status code 401 Unauthorized is inappropriate when a user is not found. Consistently using 404 Not Found for non-existent resources improves API clarity.

Update the status code to 404 for consistency:

-  return res.status(401).json({ error: "User not found" });
+  return res.status(404).json({ error: "User not found" });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return res.status(401).json({ error: "User not found" });
return res.status(404).json({ error: "User not found" });

}
const hashedPassword = await bcrypt.hash(password, 10);
customer.password = hashedPassword;
await customer.save();
RamakrushnaBiswal marked this conversation as resolved.
Show resolved Hide resolved
res.json({ message: "Password reset successful" });
} catch (error) {
res.status(501).json({ error: "Internal server error" });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Correct the HTTP Status Code for Internal Server Errors

The status code 501 Not Implemented is used when a server does not recognize the request method or lacks the ability to fulfill it. For internal server errors, use 500 Internal Server Error.

Change the status code to 500:

-  res.status(501).json({ error: "Internal server error" });
+  res.status(500).json({ error: "Internal server error" });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
res.status(501).json({ error: "Internal server error" });
res.status(500).json({ error: "Internal server error" });

}
}

module.exports = {
verifyEmail,
verifyOTP,
resetPassword,
};
4 changes: 4 additions & 0 deletions backend/models/customer.model.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ const customerSchema = new Schema(
message: (props) => `${props.value} is not a valid email address!`,
},
},
verificationCode: {
type: String,
default: ""
},
RamakrushnaBiswal marked this conversation as resolved.
Show resolved Hide resolved
role: {
type: String,
default: "customer",
Expand Down
15 changes: 15 additions & 0 deletions backend/routes/forgotRouter.js
Original file line number Diff line number Diff line change
@@ -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;
RamakrushnaBiswal marked this conversation as resolved.
Show resolved Hide resolved
3 changes: 2 additions & 1 deletion backend/routes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand All @@ -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;
111 changes: 111 additions & 0 deletions frontend/src/components/Pages/EmailVerify.jsx
Original file line number Diff line number Diff line change
@@ -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);
};
RamakrushnaBiswal marked this conversation as resolved.
Show resolved Hide resolved

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);
}
};
RamakrushnaBiswal marked this conversation as resolved.
Show resolved Hide resolved

return (
<div className="w-screen h-screen flex items-center justify-center pt-10">
<img src={photo} alt="login" loading="lazy" className=" w-3/4 absolute" />
<form
onSubmit={(e) => handleSubmit(e)}
className="form z-10 p-16 bg-lightblue flex flex-col items-start justify-center gap-5 rounded-lg border-2 border-black shadow-[4px_4px_0px_0px_black] bg-[#f1e9dc]"
>
<span className="block text-[#666] font-semibold text-2xl ">
Hey User,
</span>
<div className="title text-[#323232] font-black text-7xl mb-6">
Reset Your<br></br> Password
<br />
</div>

{error && (
<div className="w-full p-2 bg-red-100 text-red-700 border border-red-400 rounded-md">
{error}
</div>
)}

<input
className="input w-full h-10 rounded-md border-2 border-black bg-beige shadow-[4px_4px_0px_0px_black] text-[15px] font-semibold text-[#323232] p-2.5 focus:outline-none focus:border-[#2d8cf0] placeholder-[#666] placeholder-opacity-80"
name="email"
placeholder="Email"
type="email"
aria-required="true"
autoComplete="email"
onChange={(e) => handleChange(e)}
/>

<h3 className="flex items-center justify-between w-full">
<span className="block text-[#666] font-semibold text-xl transform hover:scale-110 hover:-translate-y-1 hover:text-green-500 transition">
<Link to={'/login'}>Go Back To Login Page</Link>
</span>
</h3>

<button
type="submit"
className="button-confirm mx-auto mt-12 px-4 w-30 h-10 rounded-md border-2 border-black bg-beige shadow-[4px_4px_0px_0px_black] text-[17px] font-semibold text-[#323232] cursor-pointer active:shadow-none active:translate-x-[3px] active:translate-y-[3px]"
disabled={isLoading}
>
{isLoading ? 'Submitting...' : 'Let’s go →'}
</button>
</form>
</div>
);
};

export default EmailVerify;
7 changes: 6 additions & 1 deletion frontend/src/components/Pages/Login.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ const Login = () => {
<img src={photo} alt="login" loading="lazy" className=" w-3/4 absolute" />
<form
onSubmit={(e) => handleSubmit(e)}
className="form z-10 p-16 bg-lightblue flex flex-col items-start justify-center gap-5 rounded-lg border-2 border-black shadow-[4px_4px_0px_0px_black] bg-[#f1e9dc]"
className="form z-10 p-16 bg-lightblue flex flex-col items-start justify-center gap-4 rounded-lg border-2 border-black shadow-[4px_4px_0px_0px_black] bg-[#f1e9dc]"
>
<div className="title text-[#323232] font-black text-7xl mb-6">
Welcome,
Expand Down Expand Up @@ -93,6 +93,11 @@ const Login = () => {
{hidden ? <FaEyeSlash/> : <FaEye/>}
</button>
</div>

<div className="transform hover:text-red-500 transition">
<Link to={'/email-verify'}>Forgot Password?</Link>
</div>

<h3 className="flex items-center justify-between w-full">
Dont have an account?
<span className="block text-[#666] font-semibold text-xl transform hover:scale-110 hover:-translate-y-1 hover:text-green-500 transition">
Expand Down
Loading
Loading