From 819ec3c736ee8be34c32ab1e01771dad42b14f23 Mon Sep 17 00:00:00 2001 From: haseebzaki-07 Date: Sun, 27 Oct 2024 23:25:44 +0530 Subject: [PATCH] Add otp verification for registration --- backend/config/nodemailer.js | 31 +++- backend/controller/customer.controller.js | 73 ++++++++-- backend/models/customer.model.js | 12 +- backend/routes/customerRouter.js | 2 + frontend/src/components/Pages/Signup.jsx | 14 +- frontend/src/components/Pages/VerifyOtp.tsx | 2 + .../components/Pages/verifyRegisterOtp.jsx | 132 ++++++++++++++++++ frontend/src/router/index.jsx | 2 + 8 files changed, 254 insertions(+), 14 deletions(-) create mode 100644 frontend/src/components/Pages/verifyRegisterOtp.jsx diff --git a/backend/config/nodemailer.js b/backend/config/nodemailer.js index eb354cbc..923bc45c 100644 --- a/backend/config/nodemailer.js +++ b/backend/config/nodemailer.js @@ -142,4 +142,33 @@ exports.sendVerificationMail = async (email, verificationCode) => { } } -} \ No newline at end of file +} + + +exports.sendRegisterVerificationMail = async (email, verificationCode) => { + const emailText = ` + Dear Customer, + + Please use this verification code for verifying your account. Here's your code': + + RCode: ${verificationCode} + + Thank you for choosing our service. We are happy to help you. + + Best regards, + PlayCafe +`; + + try { + await transporter.sendMail({ + from: process.env.EMAIL_USER, + to: email, + subject: 'Your OTP Verification Code', + text: emailText, + }); + } catch (error) { + console.error("Error sending OTP email:", error); + throw new Error("Failed to send OTP email"); + } +} + diff --git a/backend/controller/customer.controller.js b/backend/controller/customer.controller.js index 4937e2af..73c25ceb 100644 --- a/backend/controller/customer.controller.js +++ b/backend/controller/customer.controller.js @@ -1,8 +1,10 @@ /* eslint-disable no-unused-vars */ const bcrypt = require("bcrypt"); +const crypto = require('crypto'); const { z } = require("zod"); const Customer = require("../models/customer.model"); const jwt = require("jsonwebtoken"); +const { sendRegisterVerificationMail } = require("../config/nodemailer"); // Define the schema const customerSchema = z.object({ @@ -12,9 +14,7 @@ const customerSchema = z.object({ }); async function createCustomer(req, res) { - // Validate the request body const validation = customerSchema.safeParse(req.body); - if (!validation.success) { return res.status(400).json({ error: validation.error.errors }); } @@ -25,25 +25,71 @@ async function createCustomer(req, res) { } try { + + const otp = crypto.randomInt(100000, 999999).toString(); + const otpExpiry = new Date(Date.now() + 5 * 60 * 1000); // 5 mins from now + const hashedPassword = await bcrypt.hash(req.body.password, 10); const customer = new Customer({ name: req.body.name, email: req.body.email, password: hashedPassword, + otp, + otpExpiry, + isVerified: false, }); await customer.save(); - res.status(201).json({ message: "Customer created successfully" }); + + await sendRegisterVerificationMail(req.body.email, otp); + + res.status(201).json({ message: "OTP sent to your email. Verify to complete registration." }); } catch (error) { + console.error("Error creating customer:", error); res.status(500).json({ error: "Internal server error" }); } } + +async function verifyOtp(req, res) { + const { email, otp } = req.body; + + try { + const customer = await Customer.findOne({ email }); + + + if (!customer || customer.isVerified) { + return res.status(400).json({ error: "Invalid request or already verified" }); + } + + + if (customer.otp !== otp) { + return res.status(400).json({ error: "Invalid OTP" }); + } + if (new Date() > customer.otpExpiry) { + return res.status(400).json({ error: "OTP expired. Please register again." }); + } + + + customer.isVerified = true; + customer.otp = undefined; + customer.otpExpiry = undefined; + await customer.save(); + + res.status(200).json({ message: "Registration successful!" }); + } catch (error) { + console.error("Error verifying OTP:", error); + res.status(500).json({ error: "Internal server error" }); + } +} + + async function loginCustomer(req, res) { const customerLoginSchema = z.object({ email: z.string().email("Invalid email address"), password: z.string().min(6, "Password must be at least 6 characters long"), }); - // Validate the request body + + const validation = customerLoginSchema.safeParse(req.body); if (!validation.success) { return res.status(400).json({ error: validation.error.errors }); @@ -54,24 +100,30 @@ async function loginCustomer(req, res) { if (!customer) { return res.status(401).json({ error: "Invalid email or password" }); } - const validPassword = await bcrypt.compare( - req.body.password, - customer.password - ); + + // Check if the customer is verified + if (!customer.isVerified) { + return res.status(403).json({ error: "Account not verified. Please verify your email." }); + } + + const validPassword = await bcrypt.compare(req.body.password, customer.password); if (!validPassword) { return res.status(401).json({ error: "Invalid email or password" }); } + const payload = { - sub: customer._id, // User ID + sub: customer._id, name: customer.name, // Optional role: "customer", // Optional email: customer.email, // Optional }; + const token = jwt.sign( payload, process.env.JWT_SECRET, { expiresIn: "1h" } // Expires in 1 hour ); + res.json({ message: "Login successful", token, @@ -83,10 +135,12 @@ async function loginCustomer(req, res) { }, }); } catch (error) { + console.error("Error during login:", error); res.status(500).json({ error: "Internal server error" }); } } + async function resetPassword(req, res) { const customerResetPasswordSchema = z.object({ email: z.string().email("Invalid email address"), @@ -116,4 +170,5 @@ module.exports = { createCustomer, loginCustomer, resetPassword, + verifyOtp }; diff --git a/backend/models/customer.model.js b/backend/models/customer.model.js index 1622fc0b..8b3dba44 100644 --- a/backend/models/customer.model.js +++ b/backend/models/customer.model.js @@ -1,5 +1,3 @@ -// models/Customer.js - const mongoose = require("mongoose"); const Schema = mongoose.Schema; @@ -22,6 +20,16 @@ const customerSchema = new Schema( type: String, default: "", }, + otp: { + type: String, + }, + otpExpiry: { + type: Date, + }, + isVerified: { + type: Boolean, + default: false, + }, role: { type: String, default: "customer", diff --git a/backend/routes/customerRouter.js b/backend/routes/customerRouter.js index 5a57513c..14802eef 100644 --- a/backend/routes/customerRouter.js +++ b/backend/routes/customerRouter.js @@ -3,6 +3,7 @@ const { loginCustomer, createCustomer, resetPassword, + verifyOtp, } = require("../controller/customer.controller"); const authenticateCustomer = require("../middlewares/authCustomer"); const passport = require("../config/passport.config"); @@ -27,6 +28,7 @@ router.get( ); router.post("/register", createCustomer); +router.post("/verify", verifyOtp); router.get( "/auth/google", passport.authenticate("google", { scope: ["email"] }) diff --git a/frontend/src/components/Pages/Signup.jsx b/frontend/src/components/Pages/Signup.jsx index b10ce1c5..342d4a26 100644 --- a/frontend/src/components/Pages/Signup.jsx +++ b/frontend/src/components/Pages/Signup.jsx @@ -25,6 +25,8 @@ const Signup = () => { const handleSubmit = async (e) => { e.preventDefault(); setIsLoading(true); + + // Input validation if (!data.email || !data.password || !data.name) { setError('Please fill in all fields'); setIsLoading(false); @@ -45,6 +47,7 @@ const Signup = () => { setIsLoading(false); return; } + try { const response = await fetch(`${API_URL}/api/user/register`, { method: 'POST', @@ -52,18 +55,25 @@ const Signup = () => { body: JSON.stringify(data), }); const result = await response.json(); + if (!response.ok) { setIsLoading(false); setError(result.error); return; } - alert('Registered successfully! Please log in.'); - navigate('/'); + + + alert('OTP sent to your email. Verify to complete registration.'); + navigate('/otp-verify'); + } catch (error) { setError(error.message); console.error('Error:', error); + } finally { + setIsLoading(false); // Ensure loading state is reset after request } }; + useEffect(() => { window.scrollTo(0, 0); diff --git a/frontend/src/components/Pages/VerifyOtp.tsx b/frontend/src/components/Pages/VerifyOtp.tsx index d633d00b..135bca55 100644 --- a/frontend/src/components/Pages/VerifyOtp.tsx +++ b/frontend/src/components/Pages/VerifyOtp.tsx @@ -79,6 +79,8 @@ const VerifyOtp = () => { onChange={(e) => handleChange(e)} /> + + + + + ); +}; + +export default OtpRegisterVerify; diff --git a/frontend/src/router/index.jsx b/frontend/src/router/index.jsx index a14a06db..83ca6a13 100644 --- a/frontend/src/router/index.jsx +++ b/frontend/src/router/index.jsx @@ -21,6 +21,7 @@ import Admin from '../components/Pages/Admin'; import VerifyOtp from '../components/Pages/VerifyOtp'; import EmailVerify from '../components/Pages/EmailVerify'; import Membership from '../components/Membership'; +import OtpRegisterVerify from '../components/Pages/verifyRegisterOtp'; const router = createBrowserRouter( createRoutesFromElements( }> @@ -38,6 +39,7 @@ const router = createBrowserRouter( } /> } /> } /> + } /> } />