Auth ownership bypass: the handler trusts `req.body.userId` and returns ok=true for any value, not just `rows[0].id`. A caller who knows another user's UUID can sign in as them without their password. Delete the `req.body.userId` branch entirely; the only user_id in this response should be `rows[0].id` after a successful password check.
src/auth/login.js:11
Plaintext password comparison: `user.passwordHash !== password` only works if the column is actually plaintext — if it ever gets migrated to bcrypt/argon2 the comparison silently always returns false. Use a constant-time compare against the password field plus an explicit `crypto.timingSafeEqual` for the binary portion, and confirm the storage column is a hash, not text.
src/auth/login.js:18
Want this on every PR?
Install SiftPulse on GitHub
First review posts within 60 seconds. 14-day free trial.
SQL injection: the `email` body field is interpolated straight into a SQL query via string concatenation. An attacker can submit email = "x' OR 1=1 --" and pull every user record. Use a parameterized query: `db.query('SELECT * FROM users WHERE email = $1', [email])`. The same pattern is used correctly on line 34 of src/routes/users.js — match that.
src/auth/login.js:7