Skip to content

Commit

Permalink
Merge pull request #369 from nishant0708/forgot
Browse files Browse the repository at this point in the history
Feat:want to implement forgot password #353
  • Loading branch information
usha-madithati authored Jul 4, 2024
2 parents ab7af95 + 64be3fb commit 126682f
Show file tree
Hide file tree
Showing 9 changed files with 379 additions and 2 deletions.
4 changes: 3 additions & 1 deletion backend/.env.sample
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# sample env file with some imp info

# PORT = 6352
# MONGO_URL = YOUR_MONGODB_URL
# MONGO_URL = YOUR_MONGODB_URL
# EMAIL=""
# EMAIL_PASSWORD="" //this is app password
6 changes: 6 additions & 0 deletions backend/Schemas/User.Schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ const UserSchema = new mongoose.Schema({
type: String,
default: "3 days",
},
resetPasswordToken: {
type: String,
},
resetPasswordExpires: {
type: Date,
},
});

const User = mongoose.model("User", UserSchema);
Expand Down
91 changes: 91 additions & 0 deletions backend/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import jwt from "jsonwebtoken";
import bcrypt from "bcrypt";
import User from "./Schemas/User.Schema.js";
import client from "./twilioConfig.js";
import crypto from "crypto"; // Add this line
import nodemailer from "nodemailer"; // Add this line

dotenv.config();

Expand Down Expand Up @@ -341,6 +343,95 @@ app.post("/login", async (req, res) => {
res.status(500).send({ message: "Error logging in", error: error.message });
}
});
// Configure nodemailer for email sending
const transporter = nodemailer.createTransport({
service: "Gmail",
auth: {
user: process.env.EMAIL,
pass: process.env.EMAIL_PASSWORD,
},
});

// Route to handle forgot password
app.post("/forgot-password", async (req, res) => {
const { email } = req.body;

try {
const user = await User.findOne({ email });
if (!user) {
return res.status(404).json({ message: "User not found" });
}

const token = crypto.randomBytes(20).toString("hex");

user.resetPasswordToken = token;
user.resetPasswordExpires = Date.now() + 3600000; // 1 hour

await user.save();

const resetURL = `http://localhost:3000/user/reset-password/${token}`;

const mailOptions = {
to: user.email,
from: process.env.EMAIL,
subject: "Password Reset",
text: `You are receiving this because you (or someone else) have requested the reset of the password for your account.\n\n
Please click on the following link, or paste this into your browser to complete the process:\n\n
${resetURL}\n\n
If you did not request this, please ignore this email and your password will remain unchanged.\n`,
};

transporter.sendMail(mailOptions, (err, response) => {
if (err) {
console.error("There was an error: ", err);
res.status(500).json({ message: "Error sending email", error: err });
} else {
res.status(200).json({
success: true,
message: "Password reset email sent successfully",
});
}
});
} catch (error) {
res.status(500).json({ message: "Server error", error: error.message });
}
});

// Route to handle reset password
app.post("/user/reset-password/:token", async (req, res) => {
const { token } = req.params;
const { password } = req.body;

try {
const user = await User.findOne({
resetPasswordToken: token,
resetPasswordExpires: { $gt: Date.now() },
});

if (!user) {
return res.status(400).json({ message: "Password reset token is invalid or has expired." });
}

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

user.password = hashedPassword;
user.resetPasswordToken = undefined;
user.resetPasswordExpires = undefined;

await user.save();

res.status(200).json({
success: true,
message: "Password reset successful!",
});
} catch (error) {
res.status(500).json({ message: "Server error", error: error.message });
}
});





// Start the server
const server = app.listen(PORT, () => {
Expand Down
9 changes: 9 additions & 0 deletions backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"express": "^4.19.2",
"jsonwebtoken": "^9.0.2",
"mongoose": "^8.4.0",
"nodemailer": "^6.9.14",
"nodemon": "^3.1.1",
"twilio": "^5.1.1"
},
Expand Down
4 changes: 4 additions & 0 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import AdminD from "./Dashboards/AdminD";
import Settings from "./pages/Settings";
import AccountSettings from "./settings/AccountSettings";
import NotFoundPage from "./pages/PNF";
import ForgotPassword from "./pages/Forgotpassword.js";
import ResetPassword from "./pages/ResetPassword.js";

const App = () => {
return (
Expand All @@ -26,6 +28,8 @@ const App = () => {
<Route path="/user/add-products" element={<PForm />} />
<Route path="/user/signup" element={<SignUp />} />
<Route path="/user/login" element={<Login />} />
<Route path="/user/forgot-password" element={<ForgotPassword />} />
<Route path="/user/reset-password/:token" element={<ResetPassword />} />
<Route element={<PrivateRoute />}>
<Route path="/admin/dashboard" element={<AdminD></AdminD>} />
<Route path="/scanqr" element={<QRCodeVerification />} />
Expand Down
121 changes: 121 additions & 0 deletions src/pages/Forgotpassword.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import React, { useState } from "react";
import axios from "axios";
import { toast, ToastContainer } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
import Navbar from "../components/Navbar";
import { RotatingLines } from "react-loader-spinner";
import { useNavigate } from "react-router-dom";

const ForgotPassword = () => {
const navigate=useNavigate();
const [email, setEmail] = useState("");
const [errors, setErrors] = useState({});
const [loading, setLoading] = useState(false);

const validateEmail = (email) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
};

const handleSubmit = async (e) => {
e.preventDefault();
const newErrors = {};

if (!validateEmail(email)) {
newErrors.email = "Invalid email address.";
}

setErrors(newErrors);

if (Object.keys(newErrors).length === 0) {
setLoading(true);
try {
const response = await axios.post(
"https://smartserver-scbe.onrender.com/forgot-password",
{ email }
);

if (response && response.data.success) {
toast.success("Password reset email sent!");
navigate("/user/login")

}
} catch (error) {
if (error.response) {
setErrors({ server: error.response.data.message });
toast.error(error.response.data.message);
} else if (error.request) {
setErrors({
server: "Server not responding. Please try again later.",
});
toast.error("Server not responding. Please try again later.");
} else {
setErrors({ server: "An error occurred. Please try again." });
toast.error("An error occurred. Please try again.");
}
} finally {
setLoading(false);
}
}
};

return (
<>
<Navbar />
<ToastContainer />
<div className="flex items-center justify-center min-h-screen bg-gray-100">
<div className="w-full max-w-md p-8 space-y-6 bg-white rounded-lg shadow-2xl">
<div className="flex justify-center">
<img
src="https://i.postimg.cc/jSY8z9x8/S-3-2.png"
alt="Logo"
className="w-16 h-16"
/>
</div>
<h2 className="text-2xl font-bold text-center text-green-600">
Forgot Password
</h2>
<p className="text-center text-gray-600">
Enter your email to receive a password reset link
</p>
<form className="space-y-4" onSubmit={handleSubmit}>
<div>
<label className="block text-gray-700">Email</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full px-4 py-2 mt-2 border rounded-md focus:outline-none focus:ring-1 focus:ring-green-600"
placeholder="Email"
/>
{errors.email && (
<p className="mt-1 text-sm text-red-600">{errors.email}</p>
)}
</div>
{errors.server && (
<p className="mt-1 text-sm text-red-600">{errors.server}</p>
)}
<button
className="w-full px-4 py-2 text-white bg-green-600 rounded-md hover:bg-green-700"
disabled={loading}
>
{loading ? (
<RotatingLines
strokeColor="white"
strokeWidth="5"
animationDuration="0.75"
width="24"
visible={true}
/>
) : (
"Submit"
)}
</button>
</form>
</div>
</div>
</>
);
};

export default ForgotPassword;
8 changes: 7 additions & 1 deletion src/pages/Login.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,13 +171,19 @@ const Login = () => {
"Login"
)}
</button>
<div className="flex justify-center mt-4">
<div className="flex justify-between mt-4">
<Link
to="/user/signup"
className="text-green-600 hover:underline"
>
Sign Up
</Link>
<Link
to="/user/forgot-password"
className="text-green-600 hover:underline"
>
Forgot Password?
</Link>
</div>
</form>
</div>
Expand Down
Loading

0 comments on commit 126682f

Please sign in to comment.