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

feat: Implemented forgot password. issue: #144 #151

Merged
merged 2 commits into from
Aug 5, 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
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 `[email protected]` 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 protected]
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
Loading