4

I am trying to create a route to register users for my application, but I ran into a problem. When hitting the /register route, I get the following error:

TypeError: user.setPassword is not a function at 

Here is my code:

models/Users.js

var mongoose = require('mongoose');
var crypto = require('crypto');
var jwt = require('jsonwebtoken');

var UserSchema = new mongoose.Schema({
  username: {type: String, lowercase: true, unique: true},
  hash: String,
  salt: String
});

UserSchema.methods.setPassword = function(password){
  this.salt = crypto.randomBytes(16).toString('hex');
  this.hash = crypto.pbkdf2Sync(password, this.salt, 1000, 64).toString('hex');
};

UserSchema.methods.validPassword = function(password) {
  var hash = crypto.pbkdf2Sync(password, this.salt, 1000, 64).toString('hex');
  return this.hash === hash;
};

UserSchema.methods.generateJWT = function() {

  // set expiration to 60 days
  var today = new Date();
  var exp = new Date(today);
  exp.setDate(today.getDate() + 60);

  return jwt.sign({
    _id: this._id,
    username: this.username,
    exp: parseInt(exp.getTime() / 1000),
  }, 'SECRET');
};

mongoose.model('User', UserSchema);

routes/index.js

var express = require('express');
var router = express.Router();
var passport = require('passport');
var mongoose = require('mongoose'); 


var User = mongoose.model('User');

router.post('/register', function(req, res, next){
  if(!req.body.username || !req.body.password){
    return res.status(400).json({message: 'Please fill out all fields'});
  }

  var user = new User();

  user.username = req.body.username;
  user.setPassword(req.body.password);

  user.save(function (err){
    if(err){ return next(err); }

    return res.json({token: user.generateJWT()})
  });
});

module.exports = router;

app.js

var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');

//MongoDB Setup
var mongoose = require('mongoose');
require('./models/Users');
mongoose.connect('mongodb://localhost/images');

var passport = require('passport');
require('./config/passport');

var routes = require('./routes/index');
var app = express();
app.use(passport.initialize());
.
.
.
module.exports = app;

I'm fairly new to the MEAN stack, and after scouring the code for a few hours I can't see why things are going wrong.

3
  • 1
    I like how you are storing the salt and hash. Haven't seen this done this way before. Most code i see only just stores a password in the db Commented Jul 8, 2016 at 15:04
  • 1
    It's safer this way, even if a leak happened, no plaintext is stored. I got it from thinkster.io. Commented Jul 8, 2016 at 15:05
  • thanks for the link to thinkster.io too bad they don't have an rss feed i would like to add their tuts to feedly Commented Jul 8, 2016 at 16:55

3 Answers 3

5

Below is my code and it works for me.

-----User Model
const mongoose = require('mongoose');
const validator = require('validator');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');

const userSchema = new mongoose.Schema({
    name:{
        type: String,
        required: [true, 'Pleae enter your name']
    },
    email:{
        type: String,
        required: [true, 'Please enter your email address'],
        unique: true,
        validate: [validator.isEmail, 'Please enter a valid email address']
    },
    role: {
        type: String,
        enum:{
            values: ['user', 'employer'],
            message : 'Please select your role'
        },
        //required: [true, 'Please select role that is required'],
        default: 'user'
    },
    password :{
        type: String,
        required: [true, 'Please enter password for your account'],
        minlength: [8, 'Your password must be a t leeast 8 characters long'],
        select: false
    },
    createdAt: {
        type: Date,
        default: Date.now
    },
    resetPasswordToken: String,
    resetPasswordExpire: Date
});

//Encryting Passwords before Saving
userSchema.pre('save', async function(next){
    this.password = await bcrypt.hash(this.password, 10);
});

//Return JSON web token
userSchema.methods.getJwtToken = function(){
    return jwt.sign({id: this._id}, process.env.JWT_SECRET, {
        expiresIn: process.env.JWT_EXPIRES_TIME
    });
}

//Compare password in database
userSchema.methods.comparePassword = async function(enterPassword){
    return await bcrypt.compare(enterPassword, this.password);
}


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







-----Auth Controller
const User = require('../models/users');
const catchAsyncErrors = require('../middlewares/catchAsyncErrors');
const ErrorHandler = require('../utils/errorHandler');

//Register a new user ==> /api/v1/user/register

exports.registerUser = catchAsyncErrors(async(req, res, next) => {
    const { name, email, password, role} = req.body;
    
    const user = await User.create({
        name,
        email,
        password,
        role
    });

    //Create JWT Token
    const token = user.getJwtToken();

    res.status(200).json({
        succes: true,
        message: 'User created succesfully',
        data: user,
        token: token
    })
});



//Loguin user => /api/v1/login
exports.loginUser = catchAsyncErrors( async(req, res, next) =>{
    const { email, password } = req.body;

    if(!email || !password){
        return next (new ErrorHandler('Please enter email and password'), 400);
    }

    //Finding user in database
    const user = await (await User.findOne({email})).isSelected('+password');

    if(!user){
        return next(new ErrorHandler('Invalid Email or Password', 401));
    }

    //Check if passwoerd is correct
    const isPasswordMatched = await user.comparePassword(password);

    if(!isPasswordMatched){
        return next (new ErrorHandler('Invalid Email or Password', 401));
    }

    //Create JSOBN Web Token
    const token = user.getJwtToken();

    res.status(200).json({
        succes: true,
        token
    })

})

Sign up to request clarification or add additional context in comments.

Comments

2

Try to do this:

models/Users.js

var mongoose = require('mongoose');
var crypto = require('crypto');
var jwt = require('jsonwebtoken');

var UserSchema = new mongoose.Schema({
  username: {type: String, lowercase: true, unique: true},
  hash: String,
  salt: String
});

UserSchema.methods.setPassword = function(password){
  this.salt = crypto.randomBytes(16).toString('hex');
  this.hash = crypto.pbkdf2Sync(password, this.salt, 1000, 64).toString('hex');
};

UserSchema.methods.validPassword = function(password) {
  var hash = crypto.pbkdf2Sync(password, this.salt, 1000, 64).toString('hex');
  return this.hash === hash;
};

UserSchema.methods.generateJWT = function() {

  // set expiration to 60 days
  var today = new Date();
  var exp = new Date(today);
  exp.setDate(today.getDate() + 60);

  return jwt.sign({
    _id: this._id,
    username: this.username,
    exp: parseInt(exp.getTime() / 1000),
  }, 'SECRET');
};

// exports the user schema
module.exports = mongoose.model('User', UserSchema);

routes/index.js

var express = require('express');
var router = express.Router();
var passport = require('passport');
var mongoose = require('mongoose'); 
var User = require('models/user'); // require the user model in the correct path

// line removed
//var User = mongoose.model('User');

router.post('/register', function(req, res, next){
  if(!req.body.username || !req.body.password){
    return res.status(400).json({message: 'Please fill out all fields'});
  }

  var user = new User();

  user.username = req.body.username;
  user.setPassword(req.body.password);

  user.save(function (err){
    if(err){ return next(err); }

    return res.json({token: user.generateJWT()})
  });
});

module.exports = router;

Let me know if this works.

1 Comment

The proposed fix throws the following error: User is not a function
1

Funny: Always make sure your files are not saved in some funky location. I had a copy of Users.js in my stylesheets/ folder and that was the copy I was working on this entire time. The copy in models/ in the meantime was full of little buggy things that were easy to spot.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.