diff --git a/README.md b/README.md index 361f5b6..73d5ed3 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,16 @@ Welcome to Foodies, your go-to college dining companion! Designed for seamless c ``` PORT=3000 DATABASE_URL="http://localhost:21713/foods" + EMAIL="The email from which forgot the password email will be sent" + MAILPASS="password for the email ( app password )" ``` + ### **STEP TO GENERATE APP PASSWORD** + 1) Enable 2-step verification if not + **In https://myaccount.google.com/security, do you see 2-step verification set to ON** + + 2) Generate APP PASSWORD by clicking on below link + **https://myaccount.google.com/apppasswords?rapt=AEjHL4PAhcbtFEpLwfNtVix3bfiGe71GdrW_Naiuvp_NVnMZyTd0UR07M2mVnEyWzkw9kB99YVhhfEVtjxTi3QWSZ39biK-zGwnghm0u778vwmlh6TFbmh4** + 6. **Start the Backend Server (in the terminal within the /server directory):**
In the terminal where you navigated to the /server directory, run the following command to start the backend server: diff --git a/server/controllers/Auth.js b/server/controllers/Auth.js index 085ed28..2fa1b24 100644 --- a/server/controllers/Auth.js +++ b/server/controllers/Auth.js @@ -3,6 +3,13 @@ const User = require("../models/studentLoginInfo"); const jwt = require("jsonwebtoken"); const Canteen = require("../models/canteenLoginInfo"); const Session = require("../models/session"); +const { + forgotPasswordToken, + verifyToken, + findUserByEmail, + findUserById, +} = require("../utils/PasswordTokenAndUser"); +const nodemailer = require("nodemailer"); require("dotenv").config(); @@ -10,8 +17,16 @@ exports.studentSignup = async (req, res) => { console.log("This is jwt", process.env.JWT_SECRET); try { console.log(req.body); - const { name, email, collegeName, accountType, password } = await req.body; - const existingUser = await User.findOne({ email }); + const { + name, + email, + collegeName, + accountType, + password, + } = await req.body; + const existingUser = await User.findOne({ + email, + }); if (existingUser) { return res.status(400).json({ @@ -23,7 +38,10 @@ exports.studentSignup = async (req, res) => { let hashedPassword; try { - hashedPassword = await bcrypt.hash(password, 10); + hashedPassword = await bcrypt.hash( + password, + 10 + ); } catch (error) { console.log(error); return res.status(500).json({ @@ -81,13 +99,25 @@ exports.studentLogin = async (req, res) => { accountType: user.accountType, }; - if (await bcrypt.compare(password, user.password)) { - let token = jwt.sign(payload, process.env.JWT_SECRET, { - expiresIn: "2h", - }); + if ( + await bcrypt.compare( + password, + user.password + ) + ) { + let token = jwt.sign( + payload, + process.env.JWT_SECRET, + { + expiresIn: "2h", + } + ); // creating a session - const session = new Session({ userId: user._id, token }); + const session = new Session({ + userId: user._id, + token, + }); await session.save(); user = user.toObject(); @@ -107,13 +137,18 @@ exports.studentLogin = async (req, res) => { // message: "User logged in succesfully", // }); - // Setting cookie + // Setting cookie res.cookie("token", token, { httpOnly: true, secure: true, maxAge: 3600000, }); - res.json({ success: true, message: "Logged in successfully", token, user }); + res.json({ + success: true, + message: "Logged in successfully", + token, + user, + }); } else { return res.status(403).json({ success: false, @@ -155,13 +190,18 @@ exports.studentLogout = async (req, res) => { const token = req.cookies?.token || - req?.header("Authorization")?.replace("Bearer ", ""); + req + ?.header("Authorization") + ?.replace("Bearer ", ""); if (token) { await Session.findOneAndDelete({ token }); res.clearCookie("token"); } - res.status(200).json({ success: true, message: "Logged out successfully" }); + res.status(200).json({ + success: true, + message: "Logged out successfully", + }); } catch (error) { console.log(error); return res.status(500).json({ @@ -172,11 +212,17 @@ exports.studentLogout = async (req, res) => { }; // Controller for changing the student password -exports.changeStudentPassword = async (req, res) => { +exports.changeStudentPassword = async ( + req, + res +) => { const { oldPassword, newPassword } = req.body; const user = await User.findById(req.user._id); - const isPasswordCorrect = await bcrypt.compare(oldPassword, user.password); + const isPasswordCorrect = await bcrypt.compare( + oldPassword, + user.password + ); if (!isPasswordCorrect) { return res.status(400).json({ @@ -185,7 +231,10 @@ exports.changeStudentPassword = async (req, res) => { }); } - const newHashedPassword = await bcrypt.hash(newPassword, 10); + const newHashedPassword = await bcrypt.hash( + newPassword, + 10 + ); user.password = newHashedPassword; user.save(); @@ -201,8 +250,16 @@ exports.changeStudentPassword = async (req, res) => { exports.canteenSignup = async (req, res) => { console.log("Received signup request with data:", req.body); try { - const { name, email, collegeName, accountType, password } = req.body; - const existingCanteen = await Canteen.findOne({ email }); + const { + name, + email, + collegeName, + accountType, + password, + } = req.body; + const existingCanteen = await Canteen.findOne( + { email } + ); if (existingCanteen) { console.log("User already exists with email:", email); @@ -215,7 +272,10 @@ exports.canteenSignup = async (req, res) => { let hashedPassword; try { - hashedPassword = await bcrypt.hash(password, 10); + hashedPassword = await bcrypt.hash( + password, + 10 + ); } catch (error) { console.error("Error in hashing password:", error); return res.status(500).json({ @@ -263,7 +323,9 @@ exports.canteenLogin = async (req, res) => { }); } - let canteen = await Canteen.findOne({ email }); + let canteen = await Canteen.findOne({ + email, + }); if (!canteen) { return res.status(401).json({ success: false, @@ -277,10 +339,19 @@ exports.canteenLogin = async (req, res) => { accountType: canteen.accountType, }; - if (await bcrypt.compare(password, canteen.password)) { - let token = jwt.sign(payload, process.env.JWT_SECRET, { - expiresIn: "2h", - }); + if ( + await bcrypt.compare( + password, + canteen.password + ) + ) { + let token = jwt.sign( + payload, + process.env.JWT_SECRET, + { + expiresIn: "2h", + } + ); canteen = canteen.toObject(); canteen.token = token; console.log(canteen); @@ -301,7 +372,10 @@ exports.canteenLogin = async (req, res) => { // }); // Create session - const session = new Session({ userId: canteen._id, token }); + const session = new Session({ + userId: canteen._id, + token, + }); await session.save(); // Set cookie @@ -310,8 +384,13 @@ exports.canteenLogin = async (req, res) => { secure: true, maxAge: 3600000, }); - res.json({ success: true, message: "Logged in successfully", token, canteen, cantId: canteen._id }); - + res.json({ + success: true, + message: "Logged in successfully", + token, + canteen, + cantId: canteen._id, + }); } else { return res.status(403).json({ success: false, @@ -353,13 +432,18 @@ exports.canteenLogout = async (req, res) => { const token = req.cookies?.token || - req?.header("Authorization")?.replace("Bearer ", ""); + req + ?.header("Authorization") + ?.replace("Bearer ", ""); if (token) { await Session.findOneAndDelete({ token }); res.clearCookie("token"); } - res.status(200).json({ success: true, message: "Logged out successfully" }); + res.status(200).json({ + success: true, + message: "Logged out successfully", + }); } catch (error) { console.log(error); return res.status(500).json({ @@ -370,11 +454,19 @@ exports.canteenLogout = async (req, res) => { }; // Canteen Reset Password -exports.changeCanteenPassword = async (req, res) => { +exports.changeCanteenPassword = async ( + req, + res +) => { const { oldPassword, newPassword } = req.body; - const user = await Canteen.findById(req.user._id); + const user = await Canteen.findById( + req.user._id + ); - const isPasswordCorrect = await bcrypt.compare(oldPassword, user.password); + const isPasswordCorrect = await bcrypt.compare( + oldPassword, + user.password + ); if (!isPasswordCorrect) { return res.status(400).json({ @@ -383,7 +475,10 @@ exports.changeCanteenPassword = async (req, res) => { }); } - const newHashedPassword = await bcrypt.hash(newPassword, 10); + const newHashedPassword = await bcrypt.hash( + newPassword, + 10 + ); user.password = newHashedPassword; user.save(); @@ -393,3 +488,158 @@ exports.changeCanteenPassword = async (req, res) => { message: "Password updated successfully.", }); }; + +// verify user for reset password +exports.forgotPassword = async (req, res) => { + try { + const { email } = req.body; + const existingUser = await findUserByEmail( + email + ); + + if (!existingUser) { + return res.status(400).json({ + success: false, + message: "User does not exist", + }); + } else { + const tokenReturn = + forgotPasswordToken(existingUser); + // const link = `http://localhost:3000/api/v1/newPassword/${existingUser._id}/${tokenReturn}`; + + const link = `https://foodies-web-app.vercel.app/api/v1/newPassword/${existingUser._id}/${tokenReturn}`; + + const transporter = + nodemailer.createTransport({ + service: "gmail", + auth: { + user: process.env.EMAIL, + pass: process.env.MAILPASS, + }, + }); + + const mailOptions = { + from: process.env.EMAIL, + to: email, + subject: "Password Reset Link", + html: ` +
+

Password Reset Request

+

Hello,

+

You have requested to reset your password. Please click the button below to reset your password:

+
+ Reset Password +
+

If you did not request this, please ignore this email.

+

Thank you,

+

FoodiesWeb

+
+

© 2024 Your Company Name. All rights reserved.

+
+ `, + }; + + await transporter.sendMail( + mailOptions, + function (error, info) { + if (error) { + console.log(error); + } + } + ); + + res.status(201).json({ + msg: "You should receive an email", + }); + } + } catch (error) { + console.error(error); + return res.status(500).json({ + success: false, + message: "User verification failed", + }); + } +}; + +//for verification of link +exports.verifyLink = async (req, res) => { + const { id, token } = req.params; + console.log(req.params); + + const oldUser = await findUserById(id); + if (!oldUser) { + return res.status(404).json({ + success: false, + message: "User not found!", + }); + } + + try { + console.log("Found user: ", oldUser); + const verify = verifyToken(oldUser, token); + console.log("VerifyToken result: ", verify); + + if (verify.id === id) { + res.status(201).json({ + email: verify.email, + status: "Verified", + }); + } else { + res.status(201).json({ + status: "Cannot Verify", + }); + } + } catch (error) { + res.status(201).json({ + status: "Not Verified", + }); + } +}; + +exports.resetPassword = async (req, res) => { + const { id, token } = req.params; + const { password } = req.body; + + console.log(password, " ", id, " ", token); + + try { + const oldUser = await findUserById(id); + + if (!oldUser) { + return res + .status(404) + .json("User not found"); + } + + const verify = verifyToken(oldUser, token); + if (verify.id !== id) { + return res + .status(201) + .json({ change: false }); + } + + const salt = await bcrypt.genSalt(10); + const newPassword = await bcrypt.hash( + password, + salt + ); + + if (oldUser instanceof User) { + await User.findByIdAndUpdate(id, { + password: newPassword, + }); + } else if (oldUser instanceof Canteen) { + await Canteen.findByIdAndUpdate(id, { + password: newPassword, + }); + } + + res.status(201).json({ change: true }); + } catch (error) { + console.log( + "Error while changing password: ", + error + ); + res.status(500).json("Some error occurred!"); + } +}; diff --git a/server/package.json b/server/package.json index 38bb8fe..c81181f 100644 --- a/server/package.json +++ b/server/package.json @@ -23,6 +23,7 @@ "jsonwebtoken": "^9.0.2", "mongoose": "^7.6.2", "multer": "^1.4.5-lts.1", + "nodemailer": "^6.9.13", "nodemon": "^3.0.1", "path": "^0.12.7" } diff --git a/server/routes/student.js b/server/routes/student.js index 3168ac9..e6f2054 100644 --- a/server/routes/student.js +++ b/server/routes/student.js @@ -7,6 +7,9 @@ router.post("/studentSignup", authController.studentSignup); router.post("/studentLogin", authController.studentLogin); router.post("/canteenSignup", authController.canteenSignup); router.post("/canteenLogin", authController.canteenLogin); +router.post("/VerifyUser",authController.forgotPassword); +router.get("/resetPassword/:id/:token",authController.verifyLink); +router.post("/newPassword/:id/:token",authController.resetPassword); router.get("/studentLogout", studentAuth, authController.studentLogout); router.get("/canteenLogout", auth, authController.canteenLogout); router.post( @@ -20,4 +23,5 @@ router.post( isCanteen, authController.changeCanteenPassword ); + module.exports = router; diff --git a/server/utils/PasswordTokenAndUser.js b/server/utils/PasswordTokenAndUser.js new file mode 100644 index 0000000..a11723b --- /dev/null +++ b/server/utils/PasswordTokenAndUser.js @@ -0,0 +1,51 @@ +const jwt = require("jsonwebtoken"); +const User = require("../models/studentLoginInfo"); +const Canteen = require("../models/canteenLoginInfo"); + +const forgotPasswordToken = (oldUser) => { + const secret = + process.env.JWT_SECRET + oldUser.password; + const token = jwt.sign( + { + email: oldUser.email, + id: oldUser._id, + }, + secret, + { + expiresIn: "2m", + } + ); + return token; +}; + +const verifyToken = (oldUser, token) => { + const secret = + process.env.JWT_SECRET + oldUser.password; + const verify = jwt.verify(token, secret); + + return verify; +}; + +const findUserByEmail = async (email) => { + let user = await User.findOne({ email }); + if (!user) { + user = await Canteen.findOne({ email }); + } + return user; +}; + +const findUserById = async (id) => { + let user = await User.findById(id); + if (!user) { + user = await Canteen.findById(id); + } + return user; +}; + + +module.exports = { + forgotPasswordToken, + verifyToken, + findUserById, + findUserByEmail, +}; diff --git a/src/App.js b/src/App.js index cd3ac29..fe46efe 100644 --- a/src/App.js +++ b/src/App.js @@ -11,6 +11,8 @@ import SectionPage from './pages/SectionPage'; import News from './pages/News'; import NotFound from './pages/NotFound'; import Loader from './components/Loader/Loader'; +import ForgotPassword from './pages/ForgotPassword'; +import ResetPassword from './pages/ResetPassword'; import { ThemeProvider } from './themeContext'; import EditProfile from './pages/EditProfile'; @@ -31,6 +33,8 @@ function App() { } /> } /> } /> + } /> + } /> } /> } /> } /> diff --git a/src/pages/ForgotPassword.jsx b/src/pages/ForgotPassword.jsx new file mode 100644 index 0000000..9541088 --- /dev/null +++ b/src/pages/ForgotPassword.jsx @@ -0,0 +1,101 @@ +import React, { useState } from "react"; +import axios from "axios"; +import { toast } from "react-hot-toast"; +import logo from "../assets/logo2.png"; +import Loader from "../components/Loader/Loader"; + + +function ForgotPassword() { + const [formData, setFormData] = useState({ + email: "", + }); + const [loading, setLoading] = useState(false); + + function changeHandler(event) { + setFormData((prevData) => ({ + ...prevData, + [event.target.name]: event.target.value, + })); + } + + function submitHandler(event) { + event.preventDefault(); + + setLoading(true); + + const apiUrl = `${process.env.REACT_APP_BASE_URL}/VerifyUser`; + // const apiUrl = `http://localhost:4000/api/v1/VerifyUser `; + + axios + .post(apiUrl, formData) + .then((response) => { + setLoading(false); + toast.success("Link sent to your email"); + }) + .catch((error) => { + console.log(error); + setLoading(false); + toast.error("Failed to send Link"); + }); + } + + return ( + <> + {loading ? ( + + ) : ( +
+
+
+ logo +

+ Connecting You to Your College + Canteens +

+
+
+
+
+
+
+ +
+
+

+ Recover Password +

+

+ Please enter your email +

+ +
+ +
+ +
+
+
+ )} + + ); +} + +export default ForgotPassword; diff --git a/src/pages/Login.jsx b/src/pages/Login.jsx index c46febc..4fbb1fe 100644 --- a/src/pages/Login.jsx +++ b/src/pages/Login.jsx @@ -32,10 +32,18 @@ function Login() { ? `${process.env.REACT_APP_BASE_URL}/studentLogin` : `${process.env.REACT_APP_BASE_URL}/canteenLogin`; + + // const apiUrl = `http://localhost:4000/api/v1/studentLogin`; + const apiUrl = `${process.env.REACT_APP_BASE_URL}/studentLogin`; + + + // Assuming the response contains a token + const token = response.data.token; try { const response = await axios.post(apiUrl, formData); const { token, cantId } = response.data; + localStorage.setItem("token", token); localStorage.setItem("canteenId", cantId); @@ -46,11 +54,36 @@ function Login() { toast.success("User Logged in "); navigate(`/section/${cantId}`); } + } + + else{ + const apiUrl = `${process.env.REACT_APP_BASE_URL}/canteenLogin`; + // const apiUrl = `http://localhost:4000/api/v1/canteenLogin`; + setLoading(true); + + axios + .post(apiUrl, formData) + .then((response) => { + setLoading(false); + localStorage.setItem("canteenId", response.data.cantId); + localStorage.setItem("token", response.data.token); + toast.success("User Logged in "); + navigate( + `/section/${response.data.cantId}` + ); + }) + .catch((error) => { + //Loader will show till the api fetching is done as show as promise is resolved the loader will be not shown + setLoading(false); + toast.error("Failed to login"); + }); + } catch (error) { toast.error("Failed To Login. Please try again."); console.error(error); } finally { setLoading(false); + } } @@ -134,6 +167,13 @@ function Login() { )} +
+ +

+ Forogt Password ? +

+ +
+ +
+
+ {lowerValidated ? ( + + + + ) : ( + + + + )} + At least one lowercase letter +
+
+ {upperValidated ? ( + + + + ) : ( + + + + )} + At least one uppercase letter +
+
+ {numberValidated ? ( + + + + ) : ( + + + + )} + At least one number +
+
+ {specialValidated ? ( + + + + ) : ( + + + + )} + At least one special character +
+
+ {lengthValidated ? ( + + + + ) : ( + + + + )} + At least 8 characters +
+
+ + + + + )} + + ); +} + +export default ResetPassword; diff --git a/src/pages/Signup.jsx b/src/pages/Signup.jsx index 01b6c62..52352a7 100644 --- a/src/pages/Signup.jsx +++ b/src/pages/Signup.jsx @@ -382,4 +382,4 @@ function Signup() { ); } -export default Signup; +export default Signup; \ No newline at end of file