From 69912143f45d69a6c5315dc6b07a484a6379d723 Mon Sep 17 00:00:00 2001 From: Somnath Chattaraj Date: Thu, 10 Oct 2024 15:42:51 +0530 Subject: [PATCH 1/3] Add otp route --- backend/package-lock.json | 17 +++++ backend/package.json | 2 + .../20241010094547_dev/migration.sql | 19 +++++ backend/prisma/schema.prisma | 13 ++++ backend/src/controllers/otpController.ts | 76 +++++++++++++++++++ backend/src/controllers/userControllers.ts | 1 + backend/src/routes/otpRoute.ts | 10 +++ 7 files changed, 138 insertions(+) create mode 100644 backend/prisma/migrations/20241010094547_dev/migration.sql create mode 100644 backend/src/controllers/otpController.ts create mode 100644 backend/src/routes/otpRoute.ts diff --git a/backend/package-lock.json b/backend/package-lock.json index 7f209f7..a0c3d00 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -15,6 +15,7 @@ "@types/cors": "^2.8.17", "@types/jsonwebtoken": "^9.0.6", "@types/nodemailer": "^6.4.15", + "@types/otp-generator": "^4.0.2", "@types/ws": "^8.5.12", "academic-email-verifier": "^3.1.0", "bcrypt": "^5.1.1", @@ -28,6 +29,7 @@ "jsonwebtoken": "^9.0.2", "nodemailer": "^6.9.14", "nodemon": "^3.1.4", + "otp-generator": "^4.0.1", "prettier": "^3.3.3", "ws": "^8.18.0", "zod": "^3.23.8" @@ -423,6 +425,12 @@ "@types/node": "*" } }, + "node_modules/@types/otp-generator": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/otp-generator/-/otp-generator-4.0.2.tgz", + "integrity": "sha512-9+qqWzuFb332hXPbLgjUyOXlbcaTQkmkmqQjTduvNuOmPV5fW+iLv70JsVEhdUy0DWi4kY34++HDCaWl6N0AYg==", + "license": "MIT" + }, "node_modules/@types/qs": { "version": "6.9.15", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz", @@ -2193,6 +2201,15 @@ "node": ">= 0.8.0" } }, + "node_modules/otp-generator": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/otp-generator/-/otp-generator-4.0.1.tgz", + "integrity": "sha512-2TJ52vUftA0+J3eque4wwVtpaL4/NdIXDL0gFWFJFVUAZwAN7+9tltMhL7GCNYaHJtuONoier8Hayyj4HLbSag==", + "license": "MIT", + "engines": { + "node": ">=14.10.0" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", diff --git a/backend/package.json b/backend/package.json index 3aa09c8..d4d1dea 100644 --- a/backend/package.json +++ b/backend/package.json @@ -26,6 +26,7 @@ "@types/cors": "^2.8.17", "@types/jsonwebtoken": "^9.0.6", "@types/nodemailer": "^6.4.15", + "@types/otp-generator": "^4.0.2", "@types/ws": "^8.5.12", "academic-email-verifier": "^3.1.0", "bcrypt": "^5.1.1", @@ -39,6 +40,7 @@ "jsonwebtoken": "^9.0.2", "nodemailer": "^6.9.14", "nodemon": "^3.1.4", + "otp-generator": "^4.0.1", "prettier": "^3.3.3", "ws": "^8.18.0", "zod": "^3.23.8" diff --git a/backend/prisma/migrations/20241010094547_dev/migration.sql b/backend/prisma/migrations/20241010094547_dev/migration.sql new file mode 100644 index 0000000..2877ce9 --- /dev/null +++ b/backend/prisma/migrations/20241010094547_dev/migration.sql @@ -0,0 +1,19 @@ +-- AlterTable +ALTER TABLE "User" ADD COLUMN "pic" TEXT DEFAULT 'https://i1.sndcdn.com/artworks-000338788569-fxot50-t500x500.jpg'; + +-- CreateTable +CREATE TABLE "Otp" ( + "id" SERIAL NOT NULL, + "otp" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "expiresAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Otp_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE INDEX "Otp_userId_idx" ON "Otp"("userId"); + +-- AddForeignKey +ALTER TABLE "Otp" ADD CONSTRAINT "Otp_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("user_id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index 985f142..163d067 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -23,8 +23,21 @@ model User { chatRooms ChatRoom[] @relation("UserChatRooms") sentMessages Message[] @relation("SentMessages") Like Like[] + Otp Otp[] } +model Otp { + id Int @id @default(autoincrement()) + otp String + userId String + user User @relation(fields: [userId], references: [user_id]) + createdAt DateTime @default(now()) + expiresAt DateTime + + @@index([userId]) +} + + model College { college_id String @id @default(uuid()) name String diff --git a/backend/src/controllers/otpController.ts b/backend/src/controllers/otpController.ts new file mode 100644 index 0000000..704123e --- /dev/null +++ b/backend/src/controllers/otpController.ts @@ -0,0 +1,76 @@ +import asyncHandler from "express-async-handler"; +import otp from "otp-generator"; +import prisma from "../lib/prisma"; +import sendMail from "../mail/sendMail"; + +export const otpGenerator = asyncHandler(async (req: any, res: any) => { + + const otpCode = otp.generate(6, { upperCaseAlphabets: false, specialChars: false }); + try { + + const response = await prisma.otp.create({ + data: { + otp: otpCode, + user: { + connect: { + user_id: req.user.user_id, + }, + }, + expiresAt: new Date(Date.now() + 10 * 60 * 1000), + }, + }); + + + const email = await prisma.user.findUnique({ + where: { + user_id: req.user.user_id, + }, + select: { + email: true, + }, + }); + + if (!email) { + return res.status(404).json({ message: "User not found" }); + } + + await sendMail(email.email, otpCode); + res.status(200).json({ message: "OTP generated successfully and email sent" }); + } catch (error) { + console.error(error); + res.status(500).json({ message: "Error in generating OTP" }); + } +}); + + +export const verifyOtp = asyncHandler(async (req: any, res: any) => { + const { otp } = req.body; + try { + const otpData = await prisma.otp.findFirst({ + where: { + otp: otp, + // @ts-ignore + user_id: req.user.user_id, + expiresAt: { + gte: new Date(), + }, + }, + }); + + if (!otpData) { + return res.status(400).json({ message: "Invalid OTP" }); + } + + await prisma.otp.delete({ + where: { + id: otpData.id, + }, + }); + + res.status(200).json({ message: "OTP verified successfully" }); + } catch (error) { + console.error(error); + res.status(500).json({ message: "Error in verifying OTP" }); + } +}); + diff --git a/backend/src/controllers/userControllers.ts b/backend/src/controllers/userControllers.ts index 227c096..4208765 100644 --- a/backend/src/controllers/userControllers.ts +++ b/backend/src/controllers/userControllers.ts @@ -604,6 +604,7 @@ const updateDetails = asyncHandler(async (req: Request, res: Response) => { }, data: { username, + // @ts-ignore pic, }, }); diff --git a/backend/src/routes/otpRoute.ts b/backend/src/routes/otpRoute.ts new file mode 100644 index 0000000..e3b271d --- /dev/null +++ b/backend/src/routes/otpRoute.ts @@ -0,0 +1,10 @@ +import express from "express"; +import { calAvgRating } from "../controllers/ratingController"; +import { otpGenerator, verifyOtp } from "../controllers/otpController"; +import checkAuth from "../middleware/checkAuth"; + +export const Otprouter = express.Router(); + +Otprouter.post('/', checkAuth ,otpGenerator); +Otprouter.get('/verify', checkAuth,verifyOtp); + From 9840bfeccb9c8ade04c6fba6edaae097648b48ec Mon Sep 17 00:00:00 2001 From: Somnath Chattaraj Date: Thu, 10 Oct 2024 16:10:58 +0530 Subject: [PATCH 2/3] Add route for forget password --- backend/src/controllers/otpController.ts | 58 +++++++++++++++--------- backend/src/index.ts | 2 + backend/src/routes/otpRoute.ts | 12 ++--- 3 files changed, 45 insertions(+), 27 deletions(-) diff --git a/backend/src/controllers/otpController.ts b/backend/src/controllers/otpController.ts index 704123e..71e74f4 100644 --- a/backend/src/controllers/otpController.ts +++ b/backend/src/controllers/otpController.ts @@ -2,39 +2,34 @@ import asyncHandler from "express-async-handler"; import otp from "otp-generator"; import prisma from "../lib/prisma"; import sendMail from "../mail/sendMail"; +import bcrypt from "bcrypt"; export const otpGenerator = asyncHandler(async (req: any, res: any) => { + const {email} = req.body; const otpCode = otp.generate(6, { upperCaseAlphabets: false, specialChars: false }); try { - + const user = await prisma.user.findFirst({ + where: { + email, + }, + }); + if (!user) { + return res.status(404).json({ message: "User not found" }); + } const response = await prisma.otp.create({ data: { otp: otpCode, user: { connect: { - user_id: req.user.user_id, + email, }, }, expiresAt: new Date(Date.now() + 10 * 60 * 1000), }, }); - - - const email = await prisma.user.findUnique({ - where: { - user_id: req.user.user_id, - }, - select: { - email: true, - }, - }); - - if (!email) { - return res.status(404).json({ message: "User not found" }); - } - - await sendMail(email.email, otpCode); + const htmlContent = `

Your OTP is ${otpCode}

`; + await sendMail(htmlContent, email); res.status(200).json({ message: "OTP generated successfully and email sent" }); } catch (error) { console.error(error); @@ -44,13 +39,16 @@ export const otpGenerator = asyncHandler(async (req: any, res: any) => { export const verifyOtp = asyncHandler(async (req: any, res: any) => { - const { otp } = req.body; + const { otp, email } = req.body; + try { const otpData = await prisma.otp.findFirst({ where: { otp: otp, - // @ts-ignore - user_id: req.user.user_id, + + user: { + email, + }, expiresAt: { gte: new Date(), }, @@ -74,3 +72,21 @@ export const verifyOtp = asyncHandler(async (req: any, res: any) => { } }); +export const changePassword = asyncHandler(async (req: any, res: any) => { + const {email, password} = req.body; + const hashedPassword = await bcrypt.hash(password, 8); + try { + const response = await prisma.user.update({ + where: { + email, + }, + data: { + password: hashedPassword, + }, + }); + res.status(200).json({ message: "Password changed successfully" }); + } catch (error) { + console.error(error); + res.status(500).json({ message: "Error in changing password" }); + } +}); diff --git a/backend/src/index.ts b/backend/src/index.ts index d8c9dcc..c7ddbd7 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -12,6 +12,7 @@ import reviewRoutes from "./routes/reviewRoutes"; import ratingRoutes from "./routes/ratingRoute"; import postsRoutes from "./routes/postsRoutes"; import roomRouter from "./routes/roomRoutes"; +import Otprouter from "./routes/otpRoute"; // import { getCommunities } from "./controllers/postController"; @@ -39,6 +40,7 @@ app.use("/api/rating", ratingRoutes); app.use("/api/chat", chatRoutes); // Use the chat routes app.use("/api/post", postsRoutes); app.use("/api/room", roomRouter); +app.use("/api/otp", Otprouter); // app.get("/api/post/communities", getCommunities); app.get("/api/logout", (req: Request, res: Response) => { res.clearCookie("Authorization").json({ message: "Logged out successfully" }); diff --git a/backend/src/routes/otpRoute.ts b/backend/src/routes/otpRoute.ts index e3b271d..24022e9 100644 --- a/backend/src/routes/otpRoute.ts +++ b/backend/src/routes/otpRoute.ts @@ -1,10 +1,10 @@ import express from "express"; import { calAvgRating } from "../controllers/ratingController"; -import { otpGenerator, verifyOtp } from "../controllers/otpController"; -import checkAuth from "../middleware/checkAuth"; +import { changePassword, otpGenerator, verifyOtp } from "../controllers/otpController"; -export const Otprouter = express.Router(); - -Otprouter.post('/', checkAuth ,otpGenerator); -Otprouter.get('/verify', checkAuth,verifyOtp); +const Otprouter = express.Router(); +Otprouter.post('/' ,otpGenerator); +Otprouter.get('/verify',verifyOtp); +Otprouter.post('/change', changePassword); +export default Otprouter; From 4f152df7caf1a1b70a7b3ef074a0f19a03e76f96 Mon Sep 17 00:00:00 2001 From: Somnath Chattaraj Date: Thu, 10 Oct 2024 16:51:43 +0530 Subject: [PATCH 3/3] Add ForgetPassword Page --- frontend/src/components/Login.jsx | 3 +- .../forgotPassword/ForgetPassword.jsx | 209 ++++++++++++++++++ frontend/src/components/routes/mainroute.jsx | 5 + 3 files changed, 216 insertions(+), 1 deletion(-) create mode 100644 frontend/src/components/forgotPassword/ForgetPassword.jsx diff --git a/frontend/src/components/Login.jsx b/frontend/src/components/Login.jsx index f15ac2d..19bf4b6 100644 --- a/frontend/src/components/Login.jsx +++ b/frontend/src/components/Login.jsx @@ -14,7 +14,7 @@ import { import { FcGoogle } from "react-icons/fc"; // Google icon import { FaGithub } from "react-icons/fa"; // GitHub icon import axios from "axios"; -import { useNavigate } from "react-router-dom"; +import { Link, useNavigate } from "react-router-dom"; import { auth, googleProvider, githubProvider } from "../firebase.js"; import { signInWithPopup } from "firebase/auth"; import { InfinitySpin } from "react-loader-spinner"; @@ -221,6 +221,7 @@ const LoginPage = () => { > Login + Forgot Password? + + + + + + Enter OTP + setOtp(e.target.value)} + required + bg="gray.100" + focusBorderColor="teal.500" + /> + + + + + + + New Password + setPassword(e.target.value)} + required + bg="gray.100" + focusBorderColor="teal.500" + /> + + + + + + ) +} + +export default ForgetPassword; diff --git a/frontend/src/components/routes/mainroute.jsx b/frontend/src/components/routes/mainroute.jsx index 83d143a..62a0f24 100644 --- a/frontend/src/components/routes/mainroute.jsx +++ b/frontend/src/components/routes/mainroute.jsx @@ -20,6 +20,7 @@ import { JoinRoom1 } from "../chatroomui/joinRoom1"; import { Header } from "../homePage/HomePage"; import Navbar from "../MainNavbar"; import Logout from "../Logout"; +import ForgetPassword from "../forgotPassword/ForgetPassword"; const Test = () => { const [userId, setUserId] = useState(""); @@ -52,6 +53,10 @@ const Mainrouter = createBrowserRouter([ path: "login", element: , }, + { + path: "forgetPassword", + element: + } ], }, {