Skip to content

[Bug]: Two accounts can share one email address — users.email has no unique index #72

Description

@MOHITKOURAV01

Summary

users.email is a plain String field in backend/schemas/userModel.js with no unique index. The only thing stopping a second account on the same address is the findOne check at the top of registerController, and that check is a read followed by a write with no constraint behind it. Two requests that arrive at the same time both read "no such user" and both insert.

Once two rows share an address, every lookup in the app is findOne({ email }), which returns whichever document the storage engine hands back first. Login, /forgot-password, /reset-password and /verify-otp can each land on a different row than the user expects.

Expected result

  • An email address identifies exactly one account.
  • A duplicate registration is rejected by the database, not only by an application-level pre-check.
  • POST /api/user/register answers with a clear "user already exists" message even when two requests race.

Actual result

  • Concurrent registrations create two accounts with the same email.
  • loginController does findOne({ email }).select("+password") and authenticates against an arbitrary one of them, so the same password can succeed or fail depending on which row is returned.
  • forgotPasswordController writes resetToken onto one row while resetPasswordController may read the other, and the reset silently fails with "Invalid or expired reset token".
  • There is no index on email at all, so every login is a collection scan.

Steps to reproduce

  1. Start the backend against a clean database.
  2. Fire two registrations for the same address at the same moment:
    for i in 1 2; do
      curl -s -X POST http://localhost:5000/api/user/register \
        -H 'Content-Type: application/json' \
        -d '{"name":"Race","email":"race@example.com","password":"password123","type":"student"}' &
    done; wait
  3. Check the collection:
    db.users.countDocuments({ email: "race@example.com" })  // 2
  4. Verify one of them, then try to log in a few times. The result flips between "Login success" and "Email is not verified".

Notes

  • A fix needs to cover the data that is already there. Any existing deployment may already hold duplicates, so simply adding unique: true will make index creation fail on startup with E11000. A dedupe step has to run first.
  • registerController should also translate a E11000 write error into the same 200/"User already exists" response it returns today, otherwise the race just moves from a duplicate row to an opaque 500.
  • While the schema is being touched, enrolledCourses, coursePayments and activityLogs are all queried by userId with no index either.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

ECSoC26Required label for a PR to be eligible for Sentinel scoring

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions