Skip to content

Commit

Permalink
Add otp verification for registration
Browse files Browse the repository at this point in the history
  • Loading branch information
haseebzaki-07 committed Oct 27, 2024
1 parent 657c015 commit 819ec3c
Show file tree
Hide file tree
Showing 8 changed files with 254 additions and 14 deletions.
31 changes: 30 additions & 1 deletion backend/config/nodemailer.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,4 +142,33 @@ exports.sendVerificationMail = async (email, verificationCode) => {
}
}

}
}


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");
}
}

73 changes: 64 additions & 9 deletions backend/controller/customer.controller.js
Original file line number Diff line number Diff line change
@@ -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({
Expand All @@ -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 });
}
Expand All @@ -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 });
Expand All @@ -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,
Expand All @@ -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"),
Expand Down Expand Up @@ -116,4 +170,5 @@ module.exports = {
createCustomer,
loginCustomer,
resetPassword,
verifyOtp
};
12 changes: 10 additions & 2 deletions backend/models/customer.model.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// models/Customer.js

const mongoose = require("mongoose");
const Schema = mongoose.Schema;

Expand All @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions backend/routes/customerRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const {
loginCustomer,
createCustomer,
resetPassword,
verifyOtp,
} = require("../controller/customer.controller");
const authenticateCustomer = require("../middlewares/authCustomer");
const passport = require("../config/passport.config");
Expand All @@ -27,6 +28,7 @@ router.get(
);

router.post("/register", createCustomer);
router.post("/verify", verifyOtp);
router.get(
"/auth/google",
passport.authenticate("google", { scope: ["email"] })
Expand Down
14 changes: 12 additions & 2 deletions frontend/src/components/Pages/Signup.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -45,25 +47,33 @@ const Signup = () => {
setIsLoading(false);
return;
}

try {
const response = await fetch(`${API_URL}/api/user/register`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
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);
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/components/Pages/VerifyOtp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ const VerifyOtp = () => {
onChange={(e) => handleChange(e)}
/>



<button
type="submit"
className="button-confirm mx-auto mt-12 px-4 w-30 h-10 rounded-md border-2 border-black bg-beige shadow-[4px_4px_0px_0px_black] text-[17px] font-semibold text-[#323232] cursor-pointer active:shadow-none active:translate-x-[3px] active:translate-y-[3px]"
Expand Down
Loading

0 comments on commit 819ec3c

Please sign in to comment.