Skip to content

OTP send endpoint returns success even when email delivery fails #70

Description

@amaydixit11

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

  1. Use the /send-otp endpoint with a valid email
  2. If the SMTP server is misconfigured or the email bounces
  3. The endpoint still returns {"status": true, "message": "OTP Sent to user@..."}
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinghelp wantedExtra attention is needed

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions