From 3de7bdaf0da4011eb3cebd736c08fb3e8f304df2 Mon Sep 17 00:00:00 2001 From: Aditya Yadav <217367477+adityayadav176@users.noreply.github.com> Date: Thu, 11 Jun 2026 12:08:55 +0530 Subject: [PATCH 1/5] Add Backend Controller And RoutesFor Email Verification --- backend/.env.example | 2 + backend/package-lock.json | 10 +++ backend/package.json | 1 + backend/src/controllers/auth.controller.js | 78 ++++++++++++++++++++++ backend/src/models/user.model.js | 6 ++ backend/src/routes/auth.route.js | 4 +- backend/src/utils/nodemailer.js | 11 +++ 7 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 backend/src/utils/nodemailer.js diff --git a/backend/.env.example b/backend/.env.example index 7b62781..f9e24b6 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -9,3 +9,5 @@ GOOGLE_CLIENT_ID=your_google_client_id VAPID_PUBLIC_KEY=your_vapid_public_key VAPID_PRIVATE_KEY=your_vapid_private_key VAPID_SUBJECT=mailto:your@email.com +EMAIL=your_email@gmail.com +EMAIL_PASSWORD=your_google_email_password \ No newline at end of file diff --git a/backend/package-lock.json b/backend/package-lock.json index 7859583..ffdf3c6 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -20,6 +20,7 @@ "helmet": "^8.1.0", "jsonwebtoken": "^9.0.3", "mongoose": "^9.6.1", + "nodemailer": "^8.0.11", "socket.io": "^4.8.3", "web-push": "^3.6.7" }, @@ -1457,6 +1458,15 @@ "url": "https://opencollective.com/node-fetch" } }, + "node_modules/nodemailer": { + "version": "8.0.11", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-8.0.11.tgz", + "integrity": "sha512-nrO/pDAUKl+wXX+lx16tDLbnm0fW6sK/x8mgohaCpg+CdCEl482bD4tCuAZk2DyliruiNTIZxRCoWkDqJEnAiA==", + "license": "MIT-0", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/nodemon": { "version": "3.1.14", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.14.tgz", diff --git a/backend/package.json b/backend/package.json index a71d36e..c60b3b8 100644 --- a/backend/package.json +++ b/backend/package.json @@ -23,6 +23,7 @@ "helmet": "^8.1.0", "jsonwebtoken": "^9.0.3", "mongoose": "^9.6.1", + "nodemailer": "^8.0.11", "socket.io": "^4.8.3", "web-push": "^3.6.7" }, diff --git a/backend/src/controllers/auth.controller.js b/backend/src/controllers/auth.controller.js index 2688b5b..c2fd1ef 100644 --- a/backend/src/controllers/auth.controller.js +++ b/backend/src/controllers/auth.controller.js @@ -3,6 +3,7 @@ import { OAuth2Client } from "google-auth-library"; import User from "../models/user.model.js"; import { generateTokenAndSetCookie } from "../lib/utils.js"; import cloudinary from "../lib/cloudinary.js"; +import transporter from "../utils/nodemailer.js" let googleClient; @@ -279,4 +280,81 @@ export async function subscribeToPush(req, res) { console.error("subscribeToPush:", err.message); res.status(500).json({ message: "Could not save push subscription" }); } +} + +export const emailVerification = async(req, res) => { + try { + const {email} = req.body + + if(!email) { + return res.status(400).json({message: "Email Required"}); + } + const user = await User.findOne({email}); + + if (!user) { + return res.status(404).json({ + success: false, + message: "No account found with this email" + }); +} + + const otp = Math.floor(100000 + Math.random() * 900000).toString(); + + user.otp = otp; + user.otpExpires = Date.now() + 5 * 60 * 1000; + await user.save(); + + await transporter.sendMail({ + from: process.env.EMAIL, + to: email, + subject: "Email Verification OTP", + html: `
Your Otp Is :${otp}
+This Otp Is Valid For 5 Minutes. Use This Otp TO Verify Your Account
` + }); + + res.status(200).json({message: "Otp Send Successfully"}); + } catch (error) { + console.error("EmailVerification Error :", error.message); + res.status(500).json({message: "Failed To Verified Email"}); + } +} + +export const verifyOtp = async(req, res) => { + try { + const {email, otp} = req.body; + + if(!email) { + return res.status(400).json({message: "Email Required"}); + } + + if(!otp) { + return res.status(400).json({message: "Otp Required"}); + } + + const user = await User.findOne({email}); + + if(!user) { + return res.status(404).json({message: "User Not Found"}); + } + + if(user.otp !== otp) { + return res.status(400).json({message: "Invalid Otp"}); + } + + if(user.otpExpires < Date.now()) { + return res.status(400).json({message: "Otp Expired"}); + } + + user.isVerified = true; + user.otp = null; + user.otpExpires = null; + + await user.save(); + + res.status(200).json({message: "Email Verified"}); + } catch (error) { + res.status(500).json({message: "Error While Verified Email Account", error}); + console.log(error); + } } \ No newline at end of file diff --git a/backend/src/models/user.model.js b/backend/src/models/user.model.js index 46935a6..11b7a7c 100644 --- a/backend/src/models/user.model.js +++ b/backend/src/models/user.model.js @@ -33,10 +33,16 @@ const userSchema = new mongoose.Schema({ type: mongoose.Schema.Types.Mixed, default: null, }, + isVerified: { + type: Boolean, + default: false + }, lastSeen: { type: Date, default: Date.now, }, + otp: String, + otpExpires: Date }, { timestamps: true }); userSchema.index({ name: "text" }); diff --git a/backend/src/routes/auth.route.js b/backend/src/routes/auth.route.js index 3db8be9..8d3e65f 100644 --- a/backend/src/routes/auth.route.js +++ b/backend/src/routes/auth.route.js @@ -1,7 +1,7 @@ import express from "express"; import protectRoute from "../middleware/auth.middleware.js"; import { validateSignup, validateLogin } from "../middleware/validate.js"; -import { signup, login, logout, googleAuth, updateProfile, updateProfilePicture, checkAuth, subscribeToPush } from "../controllers/auth.controller.js"; +import { signup, login, logout, googleAuth, updateProfile, updateProfilePicture, checkAuth, subscribeToPush, emailVerification, verifyOtp } from "../controllers/auth.controller.js"; const router = express.Router(); @@ -13,5 +13,7 @@ router.put("/update-profile", protectRoute, updateProfile); router.put("/update-profile-picture", protectRoute, updateProfilePicture); router.get("/check", protectRoute, checkAuth); router.post("/push-subscribe", protectRoute, subscribeToPush); +router.post("/send-verification-email-otp", emailVerification); +router.post("/Verified-account", verifyOtp); export default router; \ No newline at end of file diff --git a/backend/src/utils/nodemailer.js b/backend/src/utils/nodemailer.js new file mode 100644 index 0000000..9977241 --- /dev/null +++ b/backend/src/utils/nodemailer.js @@ -0,0 +1,11 @@ +import nodemailer from 'nodemailer' + +const transporter = nodemailer.createTransport({ + service: 'gmail', + auth: { + user: process.env.EMAIL, + pass: process.env.EMAIL_PASSWORD + } +}); + +export default transporter \ No newline at end of file From 2d9f3c12042df7779020be138ca9ae455a9efd68 Mon Sep 17 00:00:00 2001 From: Aditya Yadav <217367477+adityayadav176@users.noreply.github.com> Date: Thu, 11 Jun 2026 12:15:09 +0530 Subject: [PATCH 2/5] Add SendVerifyEmailOtp And VErifyEmail Page In Frontend --- frontend/pages/SendVerifyEmailOtp.jsx | 70 +++++++++++++++ frontend/pages/VerifyEmail.jsx | 117 ++++++++++++++++++++++++++ 2 files changed, 187 insertions(+) create mode 100644 frontend/pages/SendVerifyEmailOtp.jsx create mode 100644 frontend/pages/VerifyEmail.jsx diff --git a/frontend/pages/SendVerifyEmailOtp.jsx b/frontend/pages/SendVerifyEmailOtp.jsx new file mode 100644 index 0000000..fb6b3c3 --- /dev/null +++ b/frontend/pages/SendVerifyEmailOtp.jsx @@ -0,0 +1,70 @@ +import { useState } from "react"; +import { useNavigate } from "react-router-dom"; +import axios from "axios"; +import React from 'react' +import toast from "react-hot-toast" + +function SendVerifyEmailOtp() { + const [email, setEmail] = useState(""); + const [loading, setLoading] = useState(false); + + const navigate = useNavigate(); + + const handleSendOtp = async (e) => { + e.preventDefault(); + + try { + setLoading(true); + + const res = await axios.post( + "http://localhost:5001/api/auth/send-verification-email-otp", + { email } + ); + + toast.success(res.data.message); + + localStorage.setItem("verifyEmail", email); + + navigate("/verify-email"); + } catch (error) { + toast.error( + error.response?.data?.message || + "Failed to send OTP" + ); + } finally { + setLoading(false); + } + }; + + return ( ++ Enter the OTP sent to +
+ ++ {email} +
+ + + + +Your OTP is: ${otp}
`, + }); + + res.status(200).json({ + message: "OTP sent successfully", }); -} - const otp = Math.floor(100000 + Math.random() * 900000).toString(); + } catch (error) { + res.status(500).json({ + message: "Failed to send OTP", + }); + } +}; - user.otp = otp; - user.otpExpires = Date.now() + 5 * 60 * 1000; - await user.save(); +export const verifyOtp = async (req, res) => { + try { + const { otp } = req.body; - await transporter.sendMail({ - from: process.env.EMAIL, - to: email, - subject: "Email Verification OTP", - html: `Your Otp Is :${otp}
-This Otp Is Valid For 5 Minutes. Use This Otp TO Verify Your Account
` - }); + const user = await User.findById(req.user._id); - res.status(200).json({message: "Otp Send Successfully"}); - } catch (error) { - console.error("EmailVerification Error :", error.message); - res.status(500).json({message: "Failed To Verified Email"}); + if (!user) { + return res.status(404).json({ + message: "User not found", + }); } -} -export const verifyOtp = async(req, res) => { - try { - const {email, otp} = req.body; - - if(!email) { - return res.status(400).json({message: "Email Required"}); - } - - if(!otp) { - return res.status(400).json({message: "Otp Required"}); - } - - const user = await User.findOne({email}); - - if(!user) { - return res.status(404).json({message: "User Not Found"}); - } - - if(user.otp !== otp) { - return res.status(400).json({message: "Invalid Otp"}); - } - - if(user.otpExpires < Date.now()) { - return res.status(400).json({message: "Otp Expired"}); - } - - user.isVerified = true; - user.otp = null; - user.otpExpires = null; - - await user.save(); - - res.status(200).json({message: "Email Verified"}); - } catch (error) { - res.status(500).json({message: "Error While Verified Email Account", error}); - console.log(error); - } -} \ No newline at end of file + if (user.otp !== otp) { + return res.status(400).json({ + message: "Invalid OTP", + }); + } + + if (user.otpExpires < Date.now()) { + return res.status(400).json({ + message: "OTP expired", + }); + } + + user.isVerified = true; + user.otp = null; + user.otpExpires = null; + + await user.save(); + + res.status(200).json({ + message: "Email verified successfully", + }); + + } catch (error) { + res.status(500).json({ + message: "Verification failed", + }); + } +}; \ No newline at end of file diff --git a/frontend/pages/SendVerifyEmailOtp.jsx b/frontend/pages/SendVerifyEmailOtp.jsx index fb6b3c3..3a16d86 100644 --- a/frontend/pages/SendVerifyEmailOtp.jsx +++ b/frontend/pages/SendVerifyEmailOtp.jsx @@ -1,70 +1,47 @@ import { useState } from "react"; -import { useNavigate } from "react-router-dom"; import axios from "axios"; -import React from 'react' -import toast from "react-hot-toast" +import toast from "react-hot-toast"; +import { useNavigate } from "react-router-dom"; function SendVerifyEmailOtp() { - const [email, setEmail] = useState(""); - const [loading, setLoading] = useState(false); - - const navigate = useNavigate(); - - const handleSendOtp = async (e) => { - e.preventDefault(); - - try { - setLoading(true); - - const res = await axios.post( - "http://localhost:5001/api/auth/send-verification-email-otp", - { email } - ); - - toast.success(res.data.message); - - localStorage.setItem("verifyEmail", email); - - navigate("/verify-email"); - } catch (error) { - toast.error( - error.response?.data?.message || - "Failed to send OTP" - ); - } finally { - setLoading(false); - } - }; - - return ( -- {email} -
-