Skip to content

Commit

Permalink
Fully functional OTP verification for signup
Browse files Browse the repository at this point in the history
  • Loading branch information
Joyosmit committed May 29, 2024
1 parent f8e0ea5 commit 6b4f92e
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 79 deletions.
73 changes: 71 additions & 2 deletions backend/src/helpers/sendMail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import nodemailer from "nodemailer";
// nodemailer = require('nodemailer');

export const sendVerificationEmail = async (email: string, otp: number) => {
console.log("Email: ",process.env.EMAIL_USER, process.env.EMAIL_PASS)
console.log("Email: ", process.env.EMAIL_USER, process.env.EMAIL_PASS)
let transporter = nodemailer.createTransport({
service: 'Gmail',
auth: {
Expand All @@ -14,10 +14,79 @@ export const sendVerificationEmail = async (email: string, otp: number) => {
});

let info = await transporter.sendMail({
from: '"Your App" <[email protected]>',
from: '"Style Share" <[email protected]>',
to: email,
subject: "Email Verification",
text: `Your OTP for email verification is ${otp}`,
// This html is a simple html template for the email body
html: `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>OTP Verification</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
margin: 0;
padding: 0;
}
.container {
width: 100%;
max-width: 600px;
margin: 0 auto;
background-color: #ffffff;
padding: 20px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
border-radius: 8px;
}
.header {
background-color: #4CAF50;
color: white;
padding: 10px 0;
text-align: center;
border-radius: 8px 8px 0 0;
}
.content {
margin: 20px 0;
text-align: center;
}
.otp {
font-size: 24px;
font-weight: bold;
margin: 20px 0;
color: #4CAF50;
}
.footer {
text-align: center;
color: #888888;
font-size: 12px;
margin-top: 20px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>OTP Verification</h1>
</div>
<div class="content">
<p>Hello,</p>
<p>Thank you for signing up. To complete your registration, please use the following OTP (One Time Password) to verify your email address:</p>
<div class="otp">${otp}</div>
<p>This OTP is valid for 10 minutes. Please do not share it with anyone.</p>
</div>
<div class="footer">
<p>If you did not request this OTP, please ignore this email.</p>
<p>Thank you!</p>
</div>
</div>
</body>
</html>
`
});

console.log("Message sent: %s", info.messageId);
Expand Down
82 changes: 58 additions & 24 deletions backend/src/routes/user/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,39 +39,63 @@ export const userSignupController = async (req: Request, res: Response) => {
});

if (existingUser) {
return res.status(411).json({
error: {
message: "Username or email already in use.",
},
});
if (!existingUser.otp) {
return res.status(411).json({
error: {
message: "Username or email already in use.",
},
});

}
}

const passwordHash = await createHash(data.password);

const otp = crypto.randomInt(100000, 999999); // Generate a 6-digit OTP

const passwordHash = await createHash(data.password);

const user = await prisma.user.create({
data: {
email: data.email,
passwordHash,
username: data.username,
// @ts-ignore
otp,
},
select: {
id: true,
email: true,
username: true,
},
});
const otp = crypto.randomInt(100000, 999999); // Generate a 6-digit OTP

let user;
if(!existingUser){
user = await prisma.user.create({
data: {
email: data.email,
passwordHash,
username: data.username,
// @ts-ignore
otp,
},
select: {
id: true,
email: true,
username: true,
},
});
}
else if(existingUser && existingUser.otp){
user = await prisma.user.update({
where: {
id: existingUser.id
},
data: {
otp: otp,
passwordHash,
},
select: {
id: true,
email: true,
username: true,
}
})
}


// Send OTP to user's email
await sendVerificationEmail(user.email, otp);
await sendVerificationEmail(user!.email, otp);

const token = createJWT({
id: user.id,
username: user.username,
id: user!.id,
username: user!.username,
});

res.status(201).json({
Expand Down Expand Up @@ -186,6 +210,7 @@ export const userSigninController = async (req: Request, res: Response) => {
email: data.email,
},
select: {
otp: true,
id: true,
username: true,
email: true,
Expand All @@ -201,6 +226,15 @@ export const userSigninController = async (req: Request, res: Response) => {
});
}

if (user.otp) {
console.log("Email really not verified")
return res.status(411).json({
error: {
message: "Email not verified",
},
});
}

const matchPassword = await validatePassword(
data.password,
user.passwordHash
Expand Down
86 changes: 34 additions & 52 deletions frontend/src/pages/Signup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ const Signup = () => {
});
const [userId, setUserId] = useState<string | null>(null);
const [otpStr, setOtpStr] = useState<string>(""); // to store otp string
const [otpSent, setOtpSent] = useState<boolean>(false); // to store otp sent status
// const [otpSent, setOtpSent] = useState<boolean>(false); // to store otp sent status
const [token, setToken] = useState<string>(""); // to store token


const setTokenState = useSetRecoilState(tokenState);
Expand All @@ -32,10 +33,8 @@ const Signup = () => {
});

console.log(response);
setTokenState(response.data?.token);
setUserId(response.data?.user?.id); // to send while verifying otp
localStorage.setItem("token", response.data?.token || "");
// navigate('/app');
setToken(response.data?.token);
} catch (e) {
const axiosError = e as AxiosError<{
error: {
Expand All @@ -51,30 +50,6 @@ const Signup = () => {
return e;
});
}

// try{
// const response = await axios.post("/api/v1/user/verify", {
// userId,
// otp: parseInt(otpStr),
// })
// console.log(response);
// navigate('/app');
// }catch(e){
// const axiosError = e as AxiosError<{
// error: {
// message: string;
// };
// }>;
// // console.log(e);
// setError((e) => {
// if (axiosError?.response?.data?.error)
// return axiosError?.response?.data?.error as typeof e;

// e.message = "An unexpected error occurred";
// return e;
// });
// }

};

const handleOtpSubmit = async (e: React.MouseEvent<HTMLButtonElement>) => {
Expand All @@ -85,6 +60,8 @@ const Signup = () => {
otp: parseInt(otpStr),
})
console.log(response);
setTokenState(token);
localStorage.setItem("token", token || "");
navigate('/app');
} catch (e) {
const axiosError = e as AxiosError<{
Expand Down Expand Up @@ -162,40 +139,45 @@ const Signup = () => {
/>
</div>
<div className="mb-4">
<label htmlFor="otp" className="block text-gray-200">
OTP
</label>
<input
type="string"
id="otp"
className="form-input mt-1 p-2 block w-full rounded-lg bg-gray-700"
placeholder="Enter 6 digit OTP here"
value={otpStr}
disabled={!userId}
onChange={(e) => setOtpStr(e.target.value)}
required
/>
<button
className={`bg-blue-500 text-white py-2 px-4 rounded-md hover:bg-blue-600 ${!userId ? 'cursor-not-allowed' : ''}`}
// onClick={handleOtpSubmit}
onClick={handleOtpSubmit}
disabled={!userId}
type="submit"
className="bg-blue-500 mb-3 text-white py-2 px-4 rounded-md hover:bg-blue-600"
disabled={userId!==null}
>
Verify Otp
Get OTP
</button>
<label htmlFor="otp" className="block text-gray-200">
OTP
</label>
<div className="w-full h-fit flex items-center justify-between">
<input
type="string"
id="otp"
className="form-input h-10 mt-1 p-2 w-[65%] rounded-lg bg-gray-700"
placeholder="Enter 6 digit OTP here"
value={otpStr}
disabled={!userId}
onChange={(e) => setOtpStr(e.target.value)}
required
/>
<button
className={`bg-blue-500 h-10 text-white py-2 px-4 rounded-md ${!userId ? 'cursor-not-allowed bg-blue-300 text-rose-500' : 'hover:bg-blue-600'}`}
// onClick={handleOtpSubmit}
onClick={handleOtpSubmit}
disabled={!userId}
>
Verify Otp
</button>

</div>
<p className="text-sm font-semibold mb-2 text-red-600">
{error.email}
</p>
</div>
<p className="text-sm font-semibold mb-2 text-red-600">
{error.password}
</p>
<button
type="submit"
className="bg-blue-500 text-white py-2 px-4 rounded-md hover:bg-blue-600"
>
Sign Up
</button>

</form>
<p className="mt-4 text-sm text-white">
Already have an account?{" "}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/store/atoms/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const userState = selector({
Authorization: `Bearer ${token}`,
}
})

console.log("DATA:",data)
return data?.user;
} catch (error) {
return null;
Expand Down

0 comments on commit 6b4f92e

Please sign in to comment.