简体   繁体   中英

Forgot password functionality using NodeJs/Knex/Nodemailer and it is not working properly

Note: this is my first time posting, if you have feedback please let me know

Goal: I am building some endpoints that let a user reset their password if they forgot it. Flow would look like this:

  1. User doesn't know password so they click on forgot password.
  2. User types in email and clicks send
  3. User receives email with link to reset password. Clicks on link and is redirected to type in their new password.
  4. They click 'save' and they are redirected to login to sign in with their new password

I am using Insomnia to hit the endpoints for testing.

Things that are working:

  • When providing an email to reset password, Nodemailer does send out an email.
  • When updating the password it does show 'password updated' and gives a 200 status.

Bugs:

  • After trying to log in with that new password, it is not saving to the database. Only the old password will allow you to log back in.

Things I have tried:

  • I tried changing my user.model to use my findByEmail function and ran into some weird bugs, which then led me down a rabbit hold of issues.
  • I tried console logging quite a few things to see if I could trace the path.
  • I tried changing the user.update function but was not able to get it to work.

Here is my code: Any guidance would be appreciated. If you need to look at any other files please let me know.

Forgot.password.js

 const router = require('express').Router(); const crypto = require('crypto') const User = require('../models/users.model') const nodemailer = require('nodemailer') router.post('/forgotpassword', (req, res) => { let { email } = req.body console.log(req.body) // if (req.body.email === '') { // res.status(400).json({ message: 'Email is required'}) // } console.error(req.body.email) User.findBy({ email }) .first() .then(user => { if (user === null) { res.status(403).json({ message: 'Email not in db' }) } else { const token = crypto.randomBytes(20).toString('hex') User.update({ resetPasswordToken: token, resetPasswordExpires: Date.now() + 3600000, }) const transporter = nodemailer.createTransport({ service: 'gmail', auth: { user: `${process.env.EMAIL_USER}`, pass: `${process.env.EMAIL_PASS}` } }) const mailOptions = { from: `${process.env.EMAIL_USER}`, to: `${user.email}`, subject: '[Promoquo] Reset Password Link', text: 'You are receiving this because you (or someone else) have requested the reset of the password for your account.\\n\\n' + 'Please click on the following link, or paste this into your browser to complete the process within one hour of receiving it:\\n\\n' + `http://localhost:5000/reset/${token}\\n\\n` + 'If you did not request this, please ignore this email and your password will remain unchanged.\\n', } transporter.sendMail(mailOptions, (err, res) => { if (err) { console.log('ERROR coming from forgot.password js and it sucks', err) } else { console.log('here is the res', res) res.status(200).json({ message: 'recovery email sent hell yes' }) } }) } res.status(200).json({ message: 'Reset password email has been sent WOOHOO 🎉' }) }) .catch(error => { res.status(500).json({ message: 'ERROR on last catch forgotpassword.js, likely no user exists', error }) console.log(error) }) }) module.exports = router

Update.password.js

 const router = require('express').Router(); const passport = require('passport') const bcrypt = require('bcrypt') const User = require('../models/users.model') const BCRYPT_SALT_ROUNDS = 12 router.put('/updatePasswordViaEmail', (req, res) => { User.find({ where: { username: req.body.username, resetPasswordToken: req.body.resetPasswordToken, resetPasswordExpires: Date.now() + 3600000, } }) .then(user => { if (user == null) { console.error('password reset link has expired') res.status(403).json({ message: 'Password reset link is invalid or has expired' }) } else if (user != null) { console.log('user exists in db') bcrypt.hash(req.body.password, BCRYPT_SALT_ROUNDS) .then(hashedPassword => { User.update({ password: hashedPassword, resetPasswordToken: null, resetPasswordExpires: null, }) }) .then(() => { console.log('log for THEN updating password') res.status(200).json({ message: 'password updated' }) }) } else { console.error('no user exists in db to update') res.status(401).json({ message: 'no user exists in db to update'}) } }) }) module.exports = router

Users.model.js

 const db = require('../dbConfig') module.exports = { add, find, findBy, findById, findByEmail, findByType, update }; function find() { return db('users').select('id', 'username', 'email', 'password'); } function findBy(filter) { return db('users').where(filter); } async function add(user) { const [id] = await db('users').insert(user); return findById(id); } function findById(id) { return db('users').where({ id }).first(); } function findByEmail(email) { return db('users').where({ email }).first(); } function findByType(type) { return db('users').where({ type }).first(); } function update(changes, id) { return db('users').where({ id }).update(changes) }

20200913211559_users.js (this is the table)

 exports.up = function(knex) { return knex.schema.createTable('users', tbl => { tbl.increments(); tbl.string('firstname', 30).notNullable(); tbl.string('lastname', 30).notNullable(); tbl.string('username', 30).notNullable() tbl.string('email', 50).notNullable() tbl.string('password', 128).notNullable(); tbl.string('type').notNullable(); tbl.boolean('confirmed').defaultTo('false'); tbl.string('resetPasswordToken'); tbl.date('resetPasswordExpires'); }) }; exports.down = function(knex) { return knex.schema.dropTableIfExists('users') };

Your User.update() lines aren't running (you either need to return their promises into the chains of promises, or hook into their callbacks). async/await is your friend here to avoid "callback hell."

const user = await User.find({
  where: {
    username: req.body.username,
    resetPasswordToken: req.body.resetPasswordToken,
    resetPasswordExpires: Date.now() + 3600000,
  }
})
if (!user) { /* ... */ }
const token = crypto.randomBytes(20).toString('hex')
await User.update({ // await here!
  resetPasswordToken: token,
  resetPasswordExpires: Date.now() + 3600000,
})

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM