Bug Description
The OTP send endpoint sends emails asynchronously via callback but never waits for delivery success before storing the OTP and returning a success response.
Affected File
backend/resources/auth/otpResource.js:40-53
Problem
// Lines 40-49: Email send is callback-based, not awaited
transporter.sendMail(mailOptions, (error, info) => {
if (error) {
console.error(error);
// Don't return here, request might have timed out?
// Or should we return error to client?
} else {
console.log("Message sent: %s", info.response);
}
});
// Lines 52-53: OTP stored IMMEDIATELY regardless of email delivery
await Otp.deleteMany({ email });
await Otp.create({ email, otp });
return res.status(200).json({ status: true, message: `${messages.otpSent} to ${email}` });
The email send is fire-and-forget. If the SMTP server rejects the email, the OTP is still stored in the database and the user receives a "success" response. The user then tries the OTP they never received, causing confusion.
Additionally, there's no validation that the loginForRole from the request body matches the user's actual roles before sending an OTP (lines 26-34 skip this check when the user doesn't exist).
Steps to Reproduce
- Use the
/send-otp endpoint with a valid email
- If the SMTP server is misconfigured or the email bounces
- The endpoint still returns
{"status": true, "message": "OTP Sent to user@..."}
- The user waits for an OTP that never arrives
Proposed Fix
Wrap sendMail in a Promise and only store the OTP if delivery succeeds:
const sendMail = (options) => new Promise((resolve, reject) => {
transporter.sendMail(options, (error, info) => {
if (error) reject(error);
else resolve(info);
});
});
try {
await sendMail(mailOptions);
await Otp.deleteMany({ email });
await Otp.create({ email, otp });
res.status(200).json({ status: true, message: `${messages.otpSent} to ${email}` });
} catch (emailError) {
console.error("Failed to send OTP email:", emailError);
res.status(500).json({ status: false, message: "Failed to send OTP email. Please try again later." });
}
Priority
MEDIUM — Users can't log in if email delivery fails silently. Also a UX issue.
Bug Description
The OTP send endpoint sends emails asynchronously via callback but never waits for delivery success before storing the OTP and returning a success response.
Affected File
backend/resources/auth/otpResource.js:40-53Problem
The email send is fire-and-forget. If the SMTP server rejects the email, the OTP is still stored in the database and the user receives a "success" response. The user then tries the OTP they never received, causing confusion.
Additionally, there's no validation that the
loginForRolefrom the request body matches the user's actual roles before sending an OTP (lines 26-34 skip this check when the user doesn't exist).Steps to Reproduce
/send-otpendpoint with a valid email{"status": true, "message": "OTP Sent to user@..."}Proposed Fix
Wrap
sendMailin a Promise and only store the OTP if delivery succeeds:Priority
MEDIUM — Users can't log in if email delivery fails silently. Also a UX issue.