Skip to content

Commit

Permalink
Merge pull request #151 from kaali001/kaali001/144/forgot-password
Browse files Browse the repository at this point in the history
feat: Implemented forgot password.      issue: #144
  • Loading branch information
dvjsharma authored Aug 5, 2024
2 parents 37d912e + 7bf025d commit e85d996
Show file tree
Hide file tree
Showing 14 changed files with 344 additions and 16 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,9 @@ Ensure that `Node.js` and `MySQL` are installed on your machine.


2. Replace `user` and `password` from `DATABASE_URL="mysql://user:password@localhost:3306/drawn2shoe" ` with your credential of mysql in the `.env` file.


3. Replace `yourmail@gmail.com` and `your_password` with a actual Email address and Password. Make sure your two-factor-authentication is on for this mail. This address would be used to send reset links for forgot password. (This step is only necessary if you working on forgot password else leave it as it is.)


<!-- 5. **Setting up the Database:**

Expand Down
31 changes: 31 additions & 0 deletions client/package-lock.json

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

4 changes: 4 additions & 0 deletions client/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import Categories from "./pages/Categories/categories";
import Customize from "./pages/Customize/customize";
import Shop from "./pages/Shop/shop";
import Login from "./pages/Login/login";
import ResetPassword from "./components/Reset-Password";
import ForgotPassword from "./components/Forgot-Password";
import Signup from "./pages/Signup/signup";
import Layout_retailer from "./components/layout-retailer";
import Designer_home from "./pages/Designers";
Expand Down Expand Up @@ -33,6 +35,8 @@ function App() {
<Route path="customize" element={<Customize />} />
<Route path="shop" element={<Shop />} />
<Route path="login" element={<Login />} />
<Route path="/forgot-password" element={<ForgotPassword />} />
<Route path="/reset-password/:token/:id" element={<ResetPassword />} />
<Route path="signup" element={<Signup />} />
<Route path="designers" element={<Designer_home />} />
<Route path="about" element={<About />} />
Expand Down
54 changes: 54 additions & 0 deletions client/src/components/Forgot-Password/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import React, { useState } from "react";
import axios from "axios";
import toast from "react-hot-toast";

const ForgotPassword = () => {
const [email, setEmail] = useState("");

const handleSubmit = async (e) => {
e.preventDefault();
try {
const response = await axios.post(
"http://localhost:3000/api/users/forgot-password",
{ email },
{
headers: {
"Content-Type": "application/json",
}
}
);
toast.success(response.data.message);
} catch (error) {
toast.error(error.response.data.message);
}
};

return (
<div className="flex items-center justify-center min-h-screen bg-slate-100">
<div className="bg-white p-8 rounded-lg shadow-md w-full max-w-md">
<h2 className="text-3xl font-bold text-center my-4 mb-12 text-gray-600">Find Your Account</h2>
<form onSubmit={handleSubmit} className="space-y-6">
<div className="my-3">
<label className="block text-lg text-gray-400 mb-2">Enter your email below.</label>
<input
type="email"
className="appearance-none border pl-4 border-gray-300 shadow-sm focus:shadow-md focus:placeholder-gray-600 transition rounded-md w-full py-3 text-gray-600 leading-tight focus:outline-none focus:ring-gray-600 focus:shadow-outline"
value={email}
placeholder="Email"
onChange={(e) => setEmail(e.target.value)}
required
/>
</div>
<button
type="submit"
className="w-full py-2 px-4 bg-indigo-500 text-white rounded-md hover:bg-indigo-600"
>
Send Reset Link
</button>
</form>
</div>
</div>
);
};

export default ForgotPassword;
76 changes: 76 additions & 0 deletions client/src/components/Reset-Password/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import React, { useState } from "react";
import { useParams } from "react-router-dom";
import axios from "axios";
import toast from "react-hot-toast";
import { Navigate } from "react-router-dom";
const ResetPassword = () => {
const { token } = useParams();
const { id } = useParams();
const [password, setPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [reset, setreset] = useState(false);

const handleSubmit = async (e) => {
e.preventDefault();
if (password !== confirmPassword) {
return toast.error("Passwords do not match");
}
try {
const response = await axios.post(
`http://localhost:3000/api/users/reset-password/${token}/${id}`,
{ password },
{
headers: {
"Content-Type": "application/json",
}
}
);
toast.success(response.data.message);
setreset(true);
} catch (error) {
toast.error(error.response.data.message);
}
};

if (reset) {
return <Navigate replace to="/login" />;
}

return (
<div className="flex items-center justify-center min-h-screen bg-slate-100">
<div className="bg-white p-8 rounded-lg shadow-md w-full max-w-md">
<h2 className="text-2xl font-bold text-center mb-6">Reset Password</h2>
<form onSubmit={handleSubmit}>
<div className="mb-4">
<label className="block text-gray-700">New Password:</label>
<input
type="password"
className="appearance-none border pl-4 border-gray-300 shadow-sm focus:shadow-md focus:placeholder-gray-600 transition rounded-md w-full py-3 text-gray-600 leading-tight focus:outline-none focus:ring-gray-600 focus:shadow-outline"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</div>
<div className="mb-4">
<label className="block text-gray-700">Confirm Password:</label>
<input
type="password"
className="appearance-none border pl-4 border-gray-300 shadow-sm focus:shadow-md focus:placeholder-gray-600 transition rounded-md w-full py-3 text-gray-600 leading-tight focus:outline-none focus:ring-gray-600 focus:shadow-outline"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
required
/>
</div>
<button
type="submit"
className="w-full py-2 px-4 bg-indigo-500 text-white rounded-md hover:bg-indigo-600"
>
Reset Password
</button>
</form>
</div>
</div>
);
};

export default ResetPassword;
32 changes: 21 additions & 11 deletions client/src/pages/Login/login.jsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { React, useState } from "react";
import React, { useState } from "react";
import { Link, Navigate } from "react-router-dom";
import axios from "axios";
import toast from "react-hot-toast";
import {useDispatch} from "react-redux";
import { useDispatch } from "react-redux";
import { logIn } from "../../redux/auth-slice";

const Login = () => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [authenticated, setAuthenticated] = useState(false);
const dispatch = useDispatch();

const loginf = async (e, email, password) => {
e.preventDefault();
if (!email && !password)
Expand All @@ -30,17 +31,18 @@ const Login = () => {
withCredentials: true,
}
);
dispatch(logIn({email:email}));
dispatch(logIn({ email: email }));
toast(data.message);
setAuthenticated(true);
} catch (error) {
toast.error(error.response.data.message);
// console.error(error);
}
};

if (authenticated) {
return <Navigate replace to="/" />;
}

return (
<>
<div className="">
Expand All @@ -56,7 +58,7 @@ const Login = () => {
</div>
<div className="relative">
<input
className="appearance-none border pl-12 border-gray-100 shadow-sm focus:shadow-md focus:placeholder-gray-600 transition rounded-md w-full py-3 text-gray-600 leading-tight focus:outline-none focus:ring-gray-600 focus:shadow-outline"
className="appearance-none border pl-12 border-gray-100 shadow-sm focus:shadow-md focus:placeholder-gray-600 transition rounded-md w-full py-3 text-gray-600 leading-tight focus:outline-none focus:ring-gray-600 focus:shadow-outline"
id="email"
type="text"
placeholder="Email"
Expand All @@ -77,9 +79,9 @@ const Login = () => {
</div>
<div className="relative mt-3">
<input
className="appearance-none border pl-12 border-gray-100 shadow-sm focus:shadow-md focus:placeholder-gray-600 transition rounded-md w-full py-3 text-gray-600 leading-tight focus:outline-none focus:ring-gray-600 focus:shadow-outline"
className="appearance-none border pl-12 border-gray-100 shadow-sm focus:shadow-md focus:placeholder-gray-600 transition rounded-md w-full py-3 text-gray-600 leading-tight focus:outline-none focus:ring-gray-600 focus:shadow-outline"
id="username"
type="text"
type="password"
placeholder="Password"
name="password"
onChange={(e) => {
Expand Down Expand Up @@ -117,14 +119,22 @@ const Login = () => {
Sign in
</button>
</div>
<div className="flex items-center justify-center mt-4">
<Link
to="/forgot-password"
className="text-indigo-500 hover:underline"
>
Forgot Password?
</Link>
</div>
<hr className="m-4" />
<div className="flex items-center justify-center mt-5">
<span className=" text-gray-500">
DO NOT HAVE A ACCOUNT ?!
</span>{" "}
<span className="text-gray-500">
DO NOT HAVE AN ACCOUNT?!
</span>
<Link
to="/signup"
className="text-white py-2 px-4 ml-3 uppercase rounded bg-green-400 hover:bg-green-500 shadow hover:shadow-lg font-medium transition transform "
className="text-white py-2 px-4 ml-3 uppercase rounded bg-green-400 hover:bg-green-500 shadow hover:shadow-lg font-medium transition transform"
>
REGISTER
</Link>
Expand Down
5 changes: 4 additions & 1 deletion server/.env
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,7 @@



DATABASE_URL="mysql://user:password@localhost:3306/drawn2shoe"
DATABASE_URL="mysql://user:password@localhost:3306/drawn2shoe"

EMAIL=yourmail@gmail.com
PASSWORD=your_password
66 changes: 66 additions & 0 deletions server/controllers/forgotPassword.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { prisma } from "../app.js";
import bcrypt from "bcryptjs";
import { v4 as uuidv4 } from "uuid";
import { sendResetEmail } from "../utils/email.js";

export const requestPasswordReset = async (req, res) => {
const { email } = req.body;
const user = await prisma.mainuser.findUnique({ where: { email } });
if (!user) {
return res.status(404).json({ message: "User not found" });
}

const token = uuidv4();
const hashedToken = await bcrypt.hash(token, 10);


// Ensure any previous tokens for this user are deleted
await prisma.passwordResetToken.deleteMany({
where: { userId: user.email }
});

const passwd = await prisma.passwordResetToken.create({
data: {
token: hashedToken,
userId: user.email,
expiresAt: new Date(Date.now() + 3600000), // 1 hour from now
}
});

const resetUrl = `http://localhost:5173/reset-password/${token}/${passwd.id}`;
await sendResetEmail(email, resetUrl);

res.status(200).json({ message: "Password reset link has been sent to your email" });
};
export const resetPassword = async (req, res) => {
const { token } = req.params;
const { password } = req.body;
const {id} = req.params;
const resetToken = await prisma.passwordResetToken.findUnique({
where: {
expiresAt: { gte: new Date() },

id:parseInt(id, 10)
}
});

if (!resetToken) {
return res.status(400).json({ message: "Invalid or expired token1" });
}

const isValid = await bcrypt.compare(token, resetToken.token);
if (!isValid) {
return res.status(400).json({ message: "Invalid or expired token2" });
}

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

await prisma.mainuser.update({
where: { email: resetToken.userId },
data: { passwd: hashedPassword }
});

await prisma.passwordResetToken.delete({ where: { id: resetToken.id } });

res.status(200).json({ message: "Password has been reset successfully" });
};
Loading

0 comments on commit e85d996

Please sign in to comment.