Skip to content

Commit

Permalink
Merge pull request #745 from haseebzaki-07/new_branch_6
Browse files Browse the repository at this point in the history
Add forgot password
  • Loading branch information
manikumarreddyu authored Nov 2, 2024
2 parents 668c4ad + edf5eb3 commit 665f351
Show file tree
Hide file tree
Showing 7 changed files with 237 additions and 9 deletions.
2 changes: 1 addition & 1 deletion backend/.env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ SMTP_EMAIL=
SMTP_PASSWORD=
JWT_SECRET=
EMAIL_USER=
EMAIL_PASS=
EMAIL_PASS=
99 changes: 98 additions & 1 deletion backend/controllers/authController.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const User = require("../model/user");
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const nodemailer = require('nodemailer');


exports.signupController = async (req, res) => {
Expand Down Expand Up @@ -31,7 +32,7 @@ exports.signinController = async (req, res) => {
const user = await User.findOne({ email });

if (!user) {
return res.status(401).json({ message: 'Invalid credentials' });
return res.status(401).json({ message: 'User not found' });
}

const isMatch = await bcrypt.compare(password, user.password);
Expand All @@ -57,3 +58,99 @@ exports.signinController = async (req, res) => {
}
};

exports.forgotPasswordController = async (req, res) => {
try {
const { email } = req.body;
const user = await User.findOne({ email });

if (!user) {
return res.status(404).json({ message: "User with this email does not exist." });
}

const otp = Math.floor(100000 + Math.random() * 900000).toString();
user.resetPasswordOTP = otp;
user.resetPasswordExpires = Date.now() + 10 * 60 * 1000; // OTP valid for 10 minutes

await user.save();

// Send email with OTP
const transporter = nodemailer.createTransport({
service: 'Gmail',
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASS,
},
});

const mailOptions = {
from: '"Agro-tech AI Support" <support@agrotechai.com>',
to: user.email,
subject: "Your Password Reset OTP",
html: `
<h3>Password Reset Request</h3>
<p>Hello ${user.firstName},</p>
<p>We received a request to reset your password. Use the OTP below to reset it. This OTP is valid for 10 minutes.</p>
<h2>${otp}</h2>
<p>If you did not request a password reset, please ignore this email.</p>
<p>Thank you,</p>
<p>The Rentalog Team</p>
`,
};

await transporter.sendMail(mailOptions);

res.status(200).json({ message: "OTP sent to your email address." });
} catch (error) {
console.error("Forgot password error:", error);
res.status(500).json({ message: "An error occurred. Please try again later." });
}
};


exports.verifyOtpController = async (req, res) => {
try {
const { email, otp } = req.body;
const user = await User.findOne({ email });

if (!user || !user.resetPasswordOTP || user.resetPasswordExpires < Date.now()) {
return res.status(400).json({ message: "OTP expired or invalid." });
}

if (user.resetPasswordOTP !== otp) {
return res.status(400).json({ message: "Incorrect OTP." });
}

res.status(200).json({ message: "OTP verified. Proceed to reset password." });
} catch (error) {
console.error("OTP verification error:", error);
res.status(500).json({ message: "An error occurred. Please try again later." });
}
};


exports.resetPasswordController = async (req, res) => {
try {
const { email, otp, newPassword } = req.body;
const user = await User.findOne({ email });


if (!user || user.resetPasswordOTP !== otp || user.resetPasswordExpires < Date.now()) {
return res.status(400).json({ message: "Invalid or expired OTP." });
}


const hashedPassword = await bcrypt.hash(newPassword, 10);
user.password = hashedPassword;

user.resetPasswordOTP = undefined;
user.resetPasswordExpires = undefined;

await user.save();

res.status(200).json({ message: "Password reset successfully." });
} catch (error) {
console.error("Reset password error:", error);
res.status(500).json({ message: "An error occurred. Please try again later." });
}
};

6 changes: 4 additions & 2 deletions backend/model/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ const userSchema = new mongoose.Schema({
password: { type: String, required: true },
role: {
type: String,
enum: ['admin', 'farmer', 'vendor', 'customer'], // Roles
default: 'customer', // Default role is 'customer'
enum: ['admin', 'farmer', 'vendor', 'customer'],
default: 'customer',
},
resetPasswordOTP: { type: String },
resetPasswordExpires: { type: Date },
});

// Hash password before saving
Expand Down
5 changes: 4 additions & 1 deletion backend/routes/auth.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
const express = require('express');
const router = express.Router();

const { signupController, signinController } = require('../controllers/authController');
const { signupController, signinController, forgotPasswordController, verifyOtpController, resetPasswordController } = require('../controllers/authController');

// Signup Route
router.post('/signup', signupController);

// Login Route
router.post('/signin', signinController );
router.post('/forgot-password', forgotPasswordController);
router.post('/verify-otp', verifyOtpController);
router.post('/reset-password', resetPasswordController);

module.exports = router;
2 changes: 2 additions & 0 deletions frontend/src/MainContent.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import CartPage from './AgroShopAI/pages/Cart';
import Wishlist from './AgroShopAI/pages/Wishlist';
import ShopNavbar from './AgroShopAI/components/ShopNavbar';
import ShopProfile from './AgroShopAI/pages/Profile'
import ForgotPasswordPage from './components/ForgotPassword';
const MainContent = () => {
UseScrollToTop();
const location = useLocation(); // Get the current route
Expand Down Expand Up @@ -122,6 +123,7 @@ const MainContent = () => {
<Route path="/login" element={<LoginPage />} />
<Route path="/profile" element={<Profile/>} />
<Route path="/signup" element={<SignUpPage />} />
<Route path="/forgot-password" element={<ForgotPasswordPage />} />
<Route path="/terms" element={<TermsAndConditions />} />
<Route path="/cookie-policy" element={<CookiePolicy />} />
<Route path="/news" element={<NewsForum />} />
Expand Down
123 changes: 123 additions & 0 deletions frontend/src/components/ForgotPassword.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { useState } from "react";
import axios from "axios";
import { Navigate } from "react-router-dom";
import { toast, ToastContainer } from "react-toastify";
import loginImage from "../assets/LoginImage.png";

const ForgotPasswordPage = () => {
const [email, setEmail] = useState("");
const [otp, setOtp] = useState("");
const [newPassword, setNewPassword] = useState("");
const [step, setStep] = useState(1);

const handleSendOtp = async () => {
try {
await axios.post("http://localhost:8080/auth/forgot-password", { email });
toast.success("OTP sent to your email address");
setStep(2); // Move to OTP verification step
} catch (error) {
toast.error(error.response?.data?.message || "Failed to send OTP");
}
};

const handleVerifyOtp = async () => {
try {
await axios.post("http://localhost:8080/auth/verify-otp", { email, otp });
toast.success("OTP verified. Enter your new password.");
setStep(3); // Move to password reset step
} catch (error) {
toast.error(error.response?.data?.message || "OTP verification failed");
}
};

const handleResetPassword = async () => {
try {
await axios.post("http://localhost:8080/auth/reset-password", { email, otp, newPassword });
toast.success("Password reset successfully. Please log in.");
setTimeout(() => {
<Navigate to="/login" replace />;
}, 2000);
} catch (error) {
toast.error(error.response?.data?.message || "Password reset failed");
}
};

return (
<div className="min-h-screen flex items-center justify-center bg-gradient-to-r from-green-400 to-blue-500 mt-10">
<ToastContainer position="top-center" autoClose={5000} hideProgressBar newestOnTop />
<div className="w-full max-w-5xl grid grid-cols-1 md:grid-cols-2 bg-white shadow-lg rounded-lg overflow-hidden">
<div className="hidden md:block">
<img src={loginImage} alt="Forgot Password Illustration" className="h-full w-full object-cover" />
</div>

<div className="p-10 flex flex-col justify-center">
<h2 className="text-4xl font-bold text-center text-green-600 mb-4">
Forgot Password
</h2>
<p className="text-center text-gray-600 mb-8">
{step === 1 && "Enter your email to receive an OTP"}
{step === 2 && "Enter the OTP sent to your email"}
{step === 3 && "Reset your password"}
</p>

{step === 1 && (
<div>
<label htmlFor="email" className="block text-sm font-medium text-green-600">Email</label>
<input
type="email"
id="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full px-4 py-2 mt-1 rounded-md bg-green-100 text-green-800 focus:ring focus:ring-green-400"
required
/>
<button onClick={handleSendOtp} className="w-full mt-4 py-2 bg-gradient-to-r from-green-500 to-blue-500 text-white rounded-md font-bold">
Send OTP
</button>
</div>
)}

{step === 2 && (
<div>
<label htmlFor="otp" className="block text-sm font-medium text-green-600">OTP</label>
<input
type="text"
id="otp"
value={otp}
onChange={(e) => setOtp(e.target.value)}
className="w-full px-4 py-2 mt-1 rounded-md bg-green-100 text-green-800 focus:ring focus:ring-green-400"
required
/>
<button onClick={handleVerifyOtp} className="w-full mt-4 py-2 bg-gradient-to-r from-green-500 to-blue-500 text-white rounded-md font-bold">
Verify OTP
</button>
</div>
)}

{step === 3 && (
<div>
<label htmlFor="newPassword" className="block text-sm font-medium text-green-600">New Password</label>
<input
type="password"
id="newPassword"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
className="w-full px-4 py-2 mt-1 rounded-md bg-green-100 text-green-800 focus:ring focus:ring-green-400"
required
/>
<button onClick={handleResetPassword} className="w-full mt-4 py-2 bg-gradient-to-r from-green-500 to-blue-500 text-white rounded-md font-bold">
Reset Password
</button>
</div>
)}

<p className="text-center text-sm mt-4">
<a href="/login" className="text-green-500 hover:underline">Back to Login</a>
</p>
</div>
</div>
</div>
);
};

export default ForgotPasswordPage;
9 changes: 5 additions & 4 deletions frontend/src/components/LoginPage.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useState } from "react";
import axios from "axios";
import { Navigate } from "react-router-dom";
import { Link, Navigate } from "react-router-dom";
import { toast, ToastContainer } from "react-toastify";
import { useAuth } from "../context/AuthContext";
import loginImage from "../assets/LoginImage.png";
Expand Down Expand Up @@ -144,9 +144,10 @@ const LoginPage = () => {
</form>
<p className="text-center text-sm mt-4">
Don’t have an account?{" "}
<a href="/signup" className="text-green-500 hover:underline">
Sign Up
</a>
<Link to="/signup" className="text-green-500 hover:underline">Sign Up</Link>
</p>
<p className="text-center text-sm mt-2">
<Link to="/forgot-password" className="text-green-500 hover:underline">Forgot Password?</Link>
</p>
</div>
</div>
Expand Down

0 comments on commit 665f351

Please sign in to comment.