0

Basically, I'm trying to send the user a url to reset his password. I have imported everything from mailer.js correctly and everything worked fine until I got the response. It's saying

it can not read property response of undefined.


routes/users.js

router.post("/reset-password/:email", emailController.createResetPasswordToken)

utils/mailer.js

const nodemailer = require("nodemailer")

let transporter = nodemailer.createTransport({
    service: "gmail",
    auth: {
        user: process.env.EMAIL,
        pass: process.env.PASSWORD
    }
})

getPasswordResetURL = (user, token) => {
    return `http://localhost:3000/reset-password/${user._id}/${token}`
}

const resetPasswordTemplate = (user, url) => {
    const from = process.env.EMAIL
    const to = user.email
    const subject = "Password Reset"
    const html = `
        <p>Hey ${user.name || user.email},</p>
        <p>We heard that you forgot your password. Sorry about that!</p>
        <p>But don’t worry! You can use the following link to reset your password:</p>
        <a href=${url}>${url}</a>
        <p>If you don’t use this link within 1 hour, it will expire.</p>
    `
}

module.exports = { transporter, getPasswordResetURL, resetPasswordTemplate }

emailController.js

const User = require("../models/User")
const bcrypt = require("bcrypt")
const jwt = require("jsonwebtoken")
const validator = require("validator")
const { transporter, getPasswordResetURL, resetPasswordTemplate } = require("../utils/mailer")
module.exports = {

    createResetPasswordTokenAndSendMail: (req, res) => {
        const email = req.params.email
            User.findOne({ email }, (err, user) => {
                if (err) console.log(err)
                // res.json({user})
                const hashedPassword = user.password
                const createdAt = user.createdAt
                const userId = user._id
                // console.log(user.password, user.createdAt, userId )
                const secret = hashedPassword + "-" + createdAt
                const token = jwt.sign({ userId }, secret, {
                    expiresIn: 3600
                })
                // console.log(token)
                // console.log(user)
                const url = getPasswordResetURL(user, token) 
                // console.log(url)
                const emailTemplate = resetPasswordTemplate(user, url)
                // console.log(emailTemplate, "l26")

                const sendEmail = () => {
                    transporter.sendMail(emailTemplate, (err, info) => {
                        if (err) console.log(err)
                        // console.log(info, "L31")
                        console.log("email sent successfully", info.response)
                    })
                }
                sendEmail()
        })
    }

}

When I tested this API in Postman, I got this error in console:

{ Error: Missing credentials for "PLAIN"
    at SMTPConnection._formatError (/home/voidrealm/Desktop/myApp/node_modules/nodemailer/lib/smtp-connection/index.js:784:19)
    at SMTPConnection.login (/home/voidrealm/Desktop/myApp/node_modules/nodemailer/lib/smtp-connection/index.js:448:38)
    at connection.connect (/home/voidrealm/Desktop/myApp/node_modules/nodemailer/lib/smtp-transport/index.js:271:32)
    at SMTPConnection.once (/home/voidrealm/Desktop/myApp/node_modules/nodemailer/lib/smtp-connection/index.js:215:17)
    at Object.onceWrapper (events.js:313:30)
    at emitNone (events.js:106:13)
    at SMTPConnection.emit (events.js:208:7)
    at SMTPConnection._actionEHLO (/home/voidrealm/Desktop/myApp/node_modules/nodemailer/lib/smtp-connection/index.js:1313:14)
    at SMTPConnection._processResponse (/home/voidrealm/Desktop/myApp/node_modules/nodemailer/lib/smtp-connection/index.js:942:20)
    at SMTPConnection._onData (/home/voidrealm/Desktop/myApp/node_modules/nodemailer/lib/smtp-connection/index.js:749:14)
    at TLSSocket.SMTPConnection._onSocketData.chunk (/home/voidrealm/Desktop/myApp/node_modules/nodemailer/lib/smtp-connection/index.js:195:44)
    at emitOne (events.js:116:13)
    at TLSSocket.emit (events.js:211:7)
    at addChunk (_stream_readable.js:263:12)
    at readableAddChunk (_stream_readable.js:250:11)
    at TLSSocket.Readable.push (_stream_readable.js:208:10) code: 'EAUTH', command: 'API' }
/home/voidrealm/Desktop/myApp/controllers/emailController.js:32
                        console.log("email sent successfully", info.response)
                                                                    ^

TypeError: Cannot read property 'response' of undefined
    at transporter.sendMail (/home/voidrealm/Desktop/myApp/controllers/emailController.js:32:69)
    at transporter.send.args (/home/voidrealm/Desktop/myApp/node_modules/nodemailer/lib/mailer/index.js:226:21)
    at connection.login.err (/home/voidrealm/Desktop/myApp/node_modules/nodemailer/lib/smtp-transport/index.js:282:36)
    at SMTPConnection.login (/home/voidrealm/Desktop/myApp/node_modules/nodemailer/lib/smtp-connection/index.js:448:24)
    at connection.connect (/home/voidrealm/Desktop/myApp/node_modules/nodemailer/lib/smtp-transport/index.js:271:32)
    at SMTPConnection.once (/home/voidrealm/Desktop/myApp/node_modules/nodemailer/lib/smtp-connection/index.js:215:17)
    at Object.onceWrapper (events.js:313:30)
    at emitNone (events.js:106:13)
    at SMTPConnection.emit (events.js:208:7)
    at SMTPConnection._actionEHLO (/home/voidrealm/Desktop/myApp/node_modules/nodemailer/lib/smtp-connection/index.js:1313:14)
    at SMTPConnection._processResponse (/home/voidrealm/Desktop/myApp/node_modules/nodemailer/lib/smtp-connection/index.js:942:20)
    at SMTPConnection._onData (/home/voidrealm/Desktop/myApp/node_modules/nodemailer/lib/smtp-connection/index.js:749:14)
    at TLSSocket.SMTPConnection._onSocketData.chunk (/home/voidrealm/Desktop/myApp/node_modules/nodemailer/lib/smtp-connection/index.js:195:44)
    at emitOne (events.js:116:13)
    at TLSSocket.emit (events.js:211:7)
    at addChunk (_stream_readable.js:263:12)

Update

After configuring OAuth, I'm getting the same error:

Here's my updated code.

mailer.js

const nodemailer = require("nodemailer")

let transporter = nodemailer.createTransport({
    service: "gmail",
    auth: {
        type: "OAuth2",
        user: process.env.EMAIL,
        clientId: process.env.clientId,
        clientSecret: process.env.clientSecret,
        refreshToken: process.env.refreshToken,
        accessToken: process.env.accessToken,
        expiresIn: process.env.expiresIn
    }
})

getPasswordResetURL = (user, token) => {
    return `http://localhost:3000/reset-password/${user._id}/${token}`
}

const resetPasswordTemplate = (user, url) => {  
    from = process.env.EMAIL,
    to = user.email,
    subject = "Password Reset",
    auth = {
        user: user.email,
        refreshToken: process.env.refreshToken,
        accessToken: process.env.accessToken,
        expiresIn: process.env.expiresIn
    },
    html = `
        <p>We heard that you forgot your password. Sorry about that!</p>
        <p>But don’t worry! You can use the following link to reset your password:</p>
        <a href=${url}>${url}</a>
        <p>If you don’t use this link within 1 hour, it will expire.</p>
    `
    return {from, to, subject, html, auth}
}

module.exports = { transporter, getPasswordResetURL, resetPasswordTemplate }

emailController.js

const User = require("../models/User")
const bcrypt = require("bcrypt")
const jwt = require("jsonwebtoken")
const validator = require("validator")
const { transporter, getPasswordResetURL, resetPasswordTemplate } = require("../utils/mailer")

module.exports = {

    createResetPasswordTokenAndSendMail: (req, res) => {
        const email = req.params.email
            User.findOne({ email }, (err, user) => {
                if (err) console.log(err)
                // res.json({user})
                const hashedPassword = user.password
                const createdAt = user.createdAt
                const userId = user._id
                // console.log(user.password, user.createdAt, userId )
                const secret = hashedPassword + "-" + createdAt 
                const token = jwt.sign({ userId }, secret, {
                    expiresIn: 3600
                })
                // console.log(token)
                // console.log(user)
                const url = getPasswordResetURL(user, token) 
                // console.log(url)
                const emailTemplate = resetPasswordTemplate(user, url)
                console.log(emailTemplate, "l26")

                const sendEmail = () => {
                    transporter.sendMail(emailTemplate, (err, info) => {
                        if (err) console.log(err)
                        // console.log(info, "L31")
                        console.log("email sent successfully", info.response)
                    })
                }
                sendEmail()
        })
    }
}

Console

{ Error: invalid_request
    at postRequest (/home/voidrealm/Desktop/myApp/node_modules/nodemailer/lib/xoauth2/index.js:259:33)
    at PassThrough.req.once (/home/voidrealm/Desktop/myApp/node_modules/nodemailer/lib/xoauth2/index.js:328:20)
    at Object.onceWrapper (events.js:313:30)
    at emitNone (events.js:111:20)
    at PassThrough.emit (events.js:208:7)
    at endReadableNT (_stream_readable.js:1064:12)
    at _combinedTickCallback (internal/process/next_tick.js:138:11)
    at process._tickCallback (internal/process/next_tick.js:180:9) code: 'EAUTH', command: 'AUTH XOAUTH2' }
/home/voidrealm/Desktop/myApp/controllers/emailController.js:33
                        console.log("email sent successfully", info.response)
                                                                    ^

TypeError: Cannot read property 'response' of undefined
    at transporter.sendMail (/home/voidrealm/Desktop/myApp/controllers/emailController.js:33:69)
    at transporter.send.args (/home/voidrealm/Desktop/myApp/node_modules/nodemailer/lib/mailer/index.js:226:21)
    at connection.login.err (/home/voidrealm/Desktop/myApp/node_modules/nodemailer/lib/smtp-transport/index.js:282:36)
    at _auth.oauth2.getToken (/home/voidrealm/Desktop/myApp/node_modules/nodemailer/lib/smtp-connection/index.js:1709:24)
    at generateCallback (/home/voidrealm/Desktop/myApp/node_modules/nodemailer/lib/xoauth2/index.js:111:13)
    at postRequest (/home/voidrealm/Desktop/myApp/node_modules/nodemailer/lib/xoauth2/index.js:259:24)
    at PassThrough.req.once (/home/voidrealm/Desktop/myApp/node_modules/nodemailer/lib/xoauth2/index.js:328:20)
    at Object.onceWrapper (events.js:313:30)
    at emitNone (events.js:111:20)
    at PassThrough.emit (events.js:208:7)
    at endReadableNT (_stream_readable.js:1064:12)
    at _combinedTickCallback (internal/process/next_tick.js:138:11)
    at process._tickCallback (internal/process/next_tick.js:180:9)

Note: I am getting all credentials values needed for auth, and saved it in my .env file.

akhildhiman
  • 642
  • 9
  • 23
  • You get back `Missing credentials for "PLAIN"` error first, this mean `if (err) console.log(err)` has been executed, then `info ` will be null. – hoangdv Feb 22 '20 at 14:27

2 Answers2

0

According to this answer, Gmail / Google app email service requires OAuth2 for authentication. PLAIN text password will require disabling security features manually on the google account.

So make sure you either disable security features from your account or use oAuth2.

Also, I'm not sure which version of Nodemail you're using, but the info argument does not contain a response prop. It has an envelope and a messageId property.

Nick Rameau
  • 1,258
  • 14
  • 23
0

You havn't declared the transporter anywhere.

Try to execute this below code,

var transporter = nodemailer.createTransport({
  service: 'gmail',
  auth: {
    user: process.env.EMAIL,
    pass: process.env.PASSWORD
  }
});

var mailOptions = {
  const from = process.env.EMAIL
    const to = user.email
    const subject = "Password Reset"
    const html = `
        <p>Hey` + user.name  user.email+`,</p>
        <p>We heard that you forgot your password. Sorry about that!</p>
        <p>But don’t worry! You can use the following link to reset your password:</p>
        <a href=`url`>`url</a>
        <p>If you don’t use this link within 1 hour, it will expire.</p>
    `
};

transporter.sendMail(mailOptions, function(error, info){
  if (error) {
    console.log(error);
  } else {
    console.log('Email sent: ' + info.response);
  }
});

EDITED
Change your html format as i do in the above code.

Pushprajsinh Chudasama
  • 7,772
  • 4
  • 20
  • 43