Amol Borkar
Amol Borkar

Reputation: 2599

Mongoose schema Cannot read property 'password' of undefined

I was trying out passport local authentication by following a tutorial. Everything seems fine but I'm getting this error when I make a request using Postman:

[nodemon] 1.18.11
[nodemon] to restart at any time, enter `rs`
[nodemon] watching: *.*
[nodemon] starting `node server.js`
body-parser deprecated bodyParser: use individual json/urlencoded middlewares server.js:17:10
(node:6336) DeprecationWarning: current URL string parser is deprecated, and will be removed in a future version. To use the new parser, pass option { useNewUrlParser: true } to MongoClient.connect.
Started listening on PORT: 8080
events.js:167
      throw er; // Unhandled 'error' event
      ^

    TypeError: Cannot read property 'password' of undefined
        at model.userSchema.methods.validPassword.password [as validPassword] (F:\Web Projects\LocalAuth\userModel.js:20:50)
        at F:\Web Projects\LocalAuth\passport.js:34:21
        at F:\Web Projects\LT1Kqob5UDEML61gCyjnAcfMXgkdP3wGcgGdBcFel.js:4672:16
        at F:\Web Projects\LT1Kqob5UDEML61gCyjnAcfMXgkdP3wGcgGdBcFry.js:4184:12
        at process.nextTick (F:\Web Projects\LT1Kqob5UDEML61gCyjnAcfMXgkdP3wGcgGdBcFry.js:2741:28)
        at process._tickCallback (internal/process/next_tick.js:61:11)
    Emitted 'error' event at:
        at F:\Web Projects\LT1Kqob5UDEML61gCyjnAcfMXgkdP3wGcgGdBcFel.js:4674:13
        at F:\Web Projects\LT1Kqob5UDEML61gCyjnAcfMXgkdP3wGcgGdBcFry.js:4184:12
        at process.nextTick (F:\Web Projects\LT1Kqob5UDEML61gCyjnAcfMXgkdP3wGcgGdBcFry.js:2741:28)
        at process._tickCallback (internal/process/next_tick.js:61:11)
    [nodemon] app crashed - waiting for file changes before starting...

Here is my user schema:

const mongoose = require('mongoose');
const bcrypt = require('bcrypt-nodejs');
const Config = require ('./config');


mongoose.connect (Config.dbUrl);

let userSchema = new mongoose.Schema({
  local : {
    email: String,
    password: String,
  },
});

userSchema.methods.generateHash = password => {
  return bcrypt.hashSync(password, bcrypt.genSaltSync(8), null);
};

userSchema.methods.validPassword = password => {
  return bcrypt.compareSync(password, this.local.password);
};

module.exports = mongoose.model('User', userSchema);

And this is my server.js file:

const express = require ('express');
const session = require ('express-session');
const mongoose = require ('mongoose');
const bodyParser = require ('body-parser');
const cookieParser = require ('cookie-parser');
const morgan = require ('morgan');
const flash = require ('connect-flash');
const passport = require ('passport');

const PassHandler = require('./passport');

const app = express ();
const port = process.env.PORT || 8080;


app.use (morgan ('dev'));
app.use (bodyParser ({extended: false}));
app.use (cookieParser ());

app.use (
  session ({secret: 'borkar.amol', saveUninitialized: true, resave: true})
);

//Initialize Passport.js
app.use (passport.initialize ());
app.use (passport.session ());
app.use (flash ());

//Global Vars for flash messages
app.use((req, res, next) => {
  res.locals.successMessage = req.flash('successMessage');
  res.locals.errorMessage = req.flash('errorMessage');
  res.locals.error = req.flash('error');
  next();
});

PassHandler(passport);

//Middleware to check if the user is logged in.
const isLoggedIn = (req, res, next) => {
  if(req.isAuthenticated()) {
    return next();
  }

  res.status(400).json({ message: 'You are not authenticated to acces this route.' });
}

app.get('/', (req, res) => {
  res.json({ message: 'Local Auth API v0.1.0'});
});

app.post('/signup', passport.authenticate('local-signup', {
  successRedirect: '/user',
  failureRedirect: '/signup',
  failureFlash: true,
}));

app.post('/login', passport.authenticate('local-login', {
  successRedirect: '/user',
  failureRedirect: '/',
  failureFlash: true,
}));

app.get('/user', isLoggedIn, (req, res) => {
  res.json({ user: req.user, message: "User is logged in."});
});

app.listen (port, () => {
  console.log (`Started listening on PORT: ${port}`);
});

Here is the Passport strategy I'm using:

  passport.use (
    'local-login',
    new LocalStrategy (
      {
        usernameField: 'email',
        passwordField: 'password',
        passReqToCallback: true,
      },
      function (req, email, password, done) {
        User.findOne ({'local.email': email}, function (err, user) {
          if (err) return done (err);

          if (!user)
            return done (
              null,
              {message: 'User not found.'},
              req.flash ('errorMessage', 'No user found.')
            );
          if (!user.validPassword (password))
            return done (
              null,
              {message: 'Invalid email or password.'},
              req.flash ('errorMessage', 'Oops! Wrong password.')
            );

          // all is well, return successful user
          return done (null, user);
        });
      }
    )
  );

I've no idea whats going wrong to be honest. please help.

**Update:**The signup route and signup strategy is working fine. Only the /login route is giving problem.

Upvotes: 2

Views: 1996

Answers (4)

Bingxuan Ying
Bingxuan Ying

Reputation: 1

"In classic function expressions, the "this" keyword is bound to different values based on the context in which it is called. With arrow functions however, this is lexically bound. It means that it uses "this" from the code that contains the arrow function." - Quote from freeCodeCamp

Here, we are using "this" from the userSchema instead of the "this" from arrow function itself. So, you should change your arrow function to classic function expression.

Upvotes: 0

chidoman
chidoman

Reputation: 14

define it as a function instead

before

userSchema.methods.validPassword = password => {
  return bcrypt.compareSync(password, this.local.password);
};

after

userSchema.methods.validPassword = function (password) {
  return bcrypt.compareSync(password, this.local.password);
};

i dont know why it doesn't work with arrow callback

Upvotes: 0

Kal J
Kal J

Reputation: 11

I faced the same exact issue. I solved mine by moving the validatePassword method from the user schema to the passport strategy. It seemed the password was not being passed to the validatePassword method in the UserSchema. Here is my UserSchema.js

    // Pulling in required dependencies
const mongoose = require('mongoose');
const bcrypt   = require('bcrypt-nodejs');
const Schema = mongoose.Schema;

//Creat UserSchema
const UserSchema = new Schema({
    local: {
        email: String,
        password: String
    },
    role: {
        type: String,
        default: 'user',
    },
    books_downloaded: {
        booksId: {
            type: Array,
            required: false,
        },
    },
    books_needed: {
        type: Object,
        default: null,
    },
    created_at: {
        type: Date,
        default: Date.now,
    },
});

// methods=====================================================
// generating a hash
UserSchema.methods.generateHash = (password) => {
    return bcrypt.hashSync(password, bcrypt.genSaltSync(8), null);
}

// expose User model to the app
module.exports = mongoose.model('User', UserSchema);

And here is my passport strategy

// load all the things we need
const LocalStrategy = require('passport-local').Strategy;
const bcrypt   = require('bcrypt-nodejs');

//load up the user model
const User = require('../models/User');

// expose this function to our app
module.exports = passport => {
    /**
     * passport session setup =======================
     * required for persistent login sessions
     * serialize and unserialize users out of session
     */
    //serialize the user for the session
    passport.serializeUser((user, done) => {
        done(null, user.id);
    });

    //deserialize the user
    passport.deserializeUser((id, done) => {
        User.findById(id, (err, user) => {
            done(err, user);
        });
    });

    /**
     * LOCAL SIGNUP
     * using named strategies
     */
    // local signup
    passport.use(
        'local-signup',
        new LocalStrategy(
            {
                usernameField: 'email',
                passwordField: 'password',
                passReqToCallback: true,
            },
            (req, email, password, done) => {
                process.nextTick(() => {

                    // find a user whose email is the same as the forms email
                    User.findOne({ 'local.email': email }, (err, user) => {
                        if (err) return done(err);
                        // check to see if theres already a user with that email
                        if (user) {
                            return done(null, false, req.flash('signupMessage', 'That email is already taken.'));
                        } else {
                            // if there is no user with that email
                            // create the user
                            var newUser = new User();

                            // set the user's local credentials
                            newUser.local.email = email;
                            newUser.local.password = newUser.generateHash(password);

                            // save the user
                            newUser.save(err => {
                                if (err) throw err;
                                return done(null, newUser);
                            });
                        }
                    });
                });
            }
        )
    );

    // =========================================================================
    // LOCAL LOGIN =============================================================
    passport.use(
        'local-login',
        new LocalStrategy(
            {
                usernameField: 'email',
                passwordField: 'password',
                passReqToCallback: true,
            },
            (req, email, password, done) => {

                // checking to see if the user trying to login already exists
                User.findOne({ 'local.email': email }, function(err, user) {
                    // if there are any errors, return the error before anything else
                    if (err) return done(err);

                    // if no user is found, return the message
                    if (!user) return done(null, false, req.flash('loginMessage', 'No 
                     user found.'));

                    // if the user is found but the password is wrong
                        let correctPassword = 
                  bcrypt.compareSync(password,user.local.password);

                    if (!correctPassword)
                        return done(null, false, req.flash('loginMessage', 'Oops! 
                  Wrong password.'));

                    // If all is well, return successful user
                    return done(null, user);
                });
            }
        )
    );
};

Upvotes: 1

Amol Borkar
Amol Borkar

Reputation: 2599

I rewrote most of the script, and it works now! Here is the code.

const express = require ('express');
const session = require ('express-session');
const bodyParser = require ('body-parser');
const cookieParser = require ('cookie-parser');
const mongoose = require ('mongoose');
const passport = require ('passport');
const LocalStrategy = require ('passport-local').Strategy;
const User = require ('./UserModel');

const dbUrl = 'url';
const port = process.env.PORT || 9000;
const app = express ();

app.use (bodyParser.json ());
app.use (bodyParser.urlencoded ({extended: true}));
app.use (cookieParser ());
app.use (
  session ({secret: 'borkar.amol', saveUninitialized: false, resave: false})
);

app.use (passport.initialize ());
app.use (passport.session ());

mongoose.connect (dbUrl, {useNewUrlParser: true}, () => {
  console.log ('Successfully connected to hosted database.');
});

passport.serializeUser ((User, done) => {
  //console.log ('SERIALIZEUSER: ', User._id);
  done (null, User._id);
});

passport.deserializeUser ((id, done) => {
  User.findById (id, (err, User) => {
    //console.log ('DESERIALIZEUSER: ', User);
    done (err, User);
  });
});

passport.use (
  'signup',
  new LocalStrategy (
    {
      usernameField: 'email',
      passwordField: 'password',
    },
    (email, password, done) => {
      process.nextTick (() => {
        User.findOne ({email: email}, (err, foundUser) => {
          if (err) return done (err);

          if (foundUser) {
            return done (null, false, {
              message: 'The email is already registered.',
            });
          } else {
            let newUser = new User ();
            newUser.email = email;
            newUser.password = newUser.hashPassword (password);

            newUser.save (err => {
              if (err) console.error ('Error when writing to database', err);
            });

            return done (null, newUser, {
              message: 'User has been registered successfully.',
            });
          }
        });
      });
    }
  )
);

passport.use (
  'login',
  new LocalStrategy (
    {
      usernameField: 'email',
    },
    (email, password, done) => {
      User.findOne ({email: email}, (err, foundUser) => {
        if (err) return done (err);

        if (!foundUser) {
          return done (null, false, {
            message: 'Invalid Username or Password.',
          });
        }

        if (!foundUser.comparePassword (password)) {
          return done (null, false, {
            message: 'Invalid Username or Password.',
          });
        }

        return done (null, foundUser);
      });
    }
  )
);

//Routes --->

app.get ('/user', (req, res, next) => {
  if (req.isAuthenticated ()) {
    res.json ({user: req.user});
    return next ();
  }

  res.status (400).json ({message: 'Request is not authenticated.'});
});

app.post (
  '/signup',
  passport.authenticate ('signup', {
    successRedirect: '/user',
    failureMessage: true,
    successMessage: true,
  })
);

app.post (
  '/login',
  passport.authenticate ('login', {
    successRedirect: '/user',
    failureMessage: true,
    successMessage: true,
  })
);

app.post ('/logout', (req, res) => {
  if (req.user) {
    req.session.destroy ();
    req.logout ();
    res.clearCookie ('connect.sid');
    return res.json ({message: 'User is now logged out.'});
  } else {
    return res.json ({message: 'Error: No user to log out.'});
  }
});

app.listen (port, () => {
  console.log (`Started listening on PORT: ${port}`);
});

Upvotes: 0

Related Questions