Skip to content

Commit 9890a9b

Browse files
committed
feat: OTP service add of Gmail; remove service of Resend
1 parent f968a87 commit 9890a9b

7 files changed

Lines changed: 135 additions & 162 deletions

File tree

package-lock.json

Lines changed: 11 additions & 74 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@
3131
"jsonwebtoken": "^9.0.3",
3232
"mongoose": "^9.0.0",
3333
"morgan": "^1.10.1",
34-
"razorpay": "^2.9.6",
35-
"resend": "^6.9.2"
34+
"nodemailer": "^8.0.1",
35+
"razorpay": "^2.9.6"
3636
},
3737
"devDependencies": {
3838
"@typescript-eslint/eslint-plugin": "^8.49.0",

src/controllers/authcontroller.js

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import bcrypt from 'bcrypt';
22
import jwt from 'jsonwebtoken';
33
import User from '../models/User.js';
4-
import { sendOtpEmail } from "../services/email.service.js";
5-
import { createAndStoreOtp, verifyStoredOtp } from "../services/otp.service.js";
4+
import { createAndSendOTP, verifyOTP } from '../services/otp.service.js';
65

76
export const login = async (req, res) => {
87
try {
@@ -59,41 +58,48 @@ export const login = async (req, res) => {
5958
}
6059
};
6160

62-
export const sendOTP = async (req, res) => {
61+
export const sendOTP = async (req, res, next) => {
6362
try {
6463
const { email } = req.body;
6564

66-
const otp = await createAndStoreOtp(email);
67-
await sendOtpEmail(email, otp);
65+
if (!email) {
66+
return res.status(400).json({
67+
success: false,
68+
message: 'Email is required'
69+
});
70+
}
71+
72+
await createAndSendOTP(email);
6873

6974
res.status(200).json({
7075
success: true,
71-
message: "OTP sent successfully"
76+
message: 'OTP sent successfully'
7277
});
7378

7479
} catch (error) {
75-
res.status(500).json({
76-
success: false,
77-
message: error.message
78-
});
80+
next(error);
7981
}
8082
};
8183

82-
export const verifyOTP = async (req, res) => {
84+
export const validateOTP = async (req, res, next) => {
8385
try {
8486
const { email, otp } = req.body;
8587

86-
await verifyStoredOtp(email, otp);
88+
if (!email || !otp) {
89+
return res.status(400).json({
90+
success: false,
91+
message: 'Email and OTP are required'
92+
});
93+
}
94+
95+
await verifyOTP(email, otp);
8796

8897
res.status(200).json({
8998
success: true,
90-
message: "OTP verified successfully"
99+
message: 'OTP verified successfully'
91100
});
92101

93102
} catch (error) {
94-
res.status(400).json({
95-
success: false,
96-
message: error.message
97-
});
103+
next(error);
98104
}
99105
};

src/models/otp.model.js

Lines changed: 17 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,20 @@
1-
import mongoose from "mongoose";
1+
import mongoose from 'mongoose';
22

3-
const otpSchema = new mongoose.Schema(
4-
{
5-
email: {
6-
type: String,
7-
required: true,
8-
index: true
9-
},
10-
otpHash: {
11-
type: String,
12-
required: true
13-
},
14-
expiresAt: {
15-
type: Date,
16-
required: true
17-
},
18-
attempts: {
19-
type: Number,
20-
default: 0
21-
}
3+
const otpSchema = new mongoose.Schema({
4+
email: {
5+
type: String,
6+
required: true,
7+
index: true
228
},
23-
{ timestamps: true }
24-
);
25-
26-
otpSchema.index({ expiresAt: 1 }, { expireAfterSeconds: 0 });
9+
otp: {
10+
type: String,
11+
required: true
12+
},
13+
expiresAt: {
14+
type: Date,
15+
required: true,
16+
index: { expires: 0 }
17+
}
18+
}, { timestamps: true });
2719

28-
export default mongoose.model("Otp", otpSchema);
20+
export default mongoose.model('OTP', otpSchema);

src/routes/auth.routes.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const router = express.Router();
77

88
router.post('/login', authController.login);
99
router.post('/send-otp', authController.sendOTP);
10-
router.post('/verify-otp', authController.verifyOTP);
10+
router.post('/verify-otp', authController.validateOTP);
1111

1212
router.get('/health', (req, res) => {
1313
res.json({

src/services/email.service.js

Lines changed: 56 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,62 @@
1-
import { Resend } from "resend";
1+
import nodemailer from 'nodemailer';
22

3-
const resend = new Resend(process.env.RESEND_API_KEY);
3+
const transporter = nodemailer.createTransport({
4+
service: 'gmail',
5+
auth: {
6+
user: process.env.GMAIL_EMAIL,
7+
pass: process.env.GMAIL_APP_PASSWORD
8+
}
9+
});
10+
11+
export const sendOTPEmail = async (email, otp) => {
12+
await transporter.sendMail({
13+
from: `"HMS Team" <${process.env.GMAIL_EMAIL}>`,
14+
to: email,
15+
replyTo: process.env.GMAIL_EMAIL,
16+
subject: 'Your One-Time Password (OTP) – Secure Verification',
17+
18+
text: `Your OTP is ${otp}. It is valid for 5 minutes. Do not share this code with anyone.`,
419

5-
export const sendOtpEmail = async (to, otp) => {
6-
return await resend.emails.send({
7-
from: "HMS <onboarding@resend.dev>",
8-
to,
9-
subject: "Your OTP Code",
1020
html: `
11-
<div style="font-family: Arial;">
12-
<h2>OTP Verification</h2>
13-
<p>Your OTP is:</p>
14-
<h1>${otp}</h1>
15-
<p>This OTP expires in 5 minutes.</p>
21+
<div style="font-family: Arial, sans-serif; background-color: #f4f6f8; padding: 20px;">
22+
<div style="max-width: 600px; margin: 0 auto; background-color: #ffffff; padding: 30px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.05);">
23+
24+
<h2 style="color: #333333; margin-bottom: 10px;">Verification Required</h2>
25+
26+
<p style="color: #555555; font-size: 14px; line-height: 1.6;">
27+
Dear User,
28+
</p>
29+
30+
<p style="color: #555555; font-size: 14px; line-height: 1.6;">
31+
We received a request to verify your identity. Please use the One-Time Password (OTP) below to proceed:
32+
</p>
33+
34+
<div style="text-align: center; margin: 25px 0;">
35+
<span style="display: inline-block; font-size: 28px; font-weight: bold; letter-spacing: 4px; color: #1a73e8; background-color: #f1f3f4; padding: 12px 24px; border-radius: 6px;">
36+
${otp}
37+
</span>
38+
</div>
39+
40+
<p style="color: #555555; font-size: 14px; line-height: 1.6;">
41+
This OTP is valid for <strong>5 minutes</strong>.
42+
For security reasons, please do not share this code with anyone.
43+
</p>
44+
45+
<p style="color: #555555; font-size: 14px; line-height: 1.6;">
46+
If you did not request this verification, you may safely ignore this email.
47+
</p>
48+
49+
<hr style="border: none; border-top: 1px solid #eeeeee; margin: 30px 0;" />
50+
51+
<p style="color: #888888; font-size: 12px; line-height: 1.5;">
52+
This is an automated message. Please do not reply to this email.
53+
</p>
54+
55+
<p style="color: #888888; font-size: 12px;">
56+
© ${new Date().getFullYear()} HMS Team. All rights reserved.
57+
</p>
58+
59+
</div>
1660
</div>
1761
`
1862
});

0 commit comments

Comments
 (0)