16

I have a userSchema that looks like this:

var userSchema = new Schema({
    name: {
      type: String
    , required: true
    , validate: [validators.notEmpty, 'Name is empty']
    }
  , username: {
      type: String
    , required: true
    , validate: [validators.notEmpty, 'Username is empty']
    }
  , email: {
      type: String
    , required: true
    , validate: [
        { validator: validators.notEmpty, msg: 'Email is empty' }
      , { validator: validators.isEmail, msg: 'Invalid email' }
      ]
    }
  , salt: String
  , hash: String
});

All of my validation is happening in the schema so far, and I'm wondering how to go about achieving this with password validation. The user inputs the password into two fields, and the model should check that they are the same as each other.

Does this kind of validation belong in the schema? I'm new to this sort of validation.

How should I validate the passwords?

7 Answers7

27

I eventually discovered that you can use a combination of virtual paths and the invalidate function to achieve this, as shown in this gist, for the very same purpose of matching passwords: https://gist.github.com/1350041

To quote directly:

CustomerSchema.virtual('password')
.get(function() {
  return this._password;
})
.set(function(value) {
  this._password = value;
  var salt = bcrypt.gen_salt_sync(12);
  this.passwordHash = bcrypt.encrypt_sync(value, salt);
});

CustomerSchema.virtual('passwordConfirmation')
.get(function() {
  return this._passwordConfirmation;
})
.set(function(value) {
  this._passwordConfirmation = value;
});

CustomerSchema.path('passwordHash').validate(function(v) {
  if (this._password || this._passwordConfirmation) {
    if (!val.check(this._password).min(6)) {
      this.invalidate('password', 'must be at least 6 characters.');
    }
    if (this._password !== this._passwordConfirmation) {
      this.invalidate('passwordConfirmation', 'must match confirmation.');
    }
  }

  if (this.isNew && !this._password) {
    this.invalidate('password', 'required');
  }
}, null);
12

I think password matching belongs in the client interface and should never get to the server (DB layer is already too much). It's better for the user experience not to have a server roundtrip just to tell the user that 2 strings are different.

As for thin controller, fat model... all these silver bullets out there should be shot back at the originator. No solution is good in any situation. Think everyone of them in their own context.

Bringing the fat model idea here, makes you use a feature (schema validation) for a totally different purpose (password matching) and makes your app dependent on the tech you're using now. One day you'll want to change tech and you'll get to something without schema validation at all... and then you'll have to remember that part of functionality of your app relied on that. And you'll have to move it back to the client side or to the controller.

sqreept
  • 5,236
  • 3
  • 21
  • 26
  • Great explanation. Thank you. I'm currently relying on the model to validate most of the data. However, like you say, for things like password checking it is impractical. I would be interested to know how I can continue to use model validation like above as well as password matching in the controller? Obviously password validation would come first, and if this was passed, only then would other validation take place. Would you recommend moving all of my validation to the controller or using the method of validation in controller and model? –  Dec 21 '12 at 15:23
  • On node.js the validator module is what you're looking for. Find it here: https://github.com/chriso/node-validator . As for recommandation: it really depends on what you're doing: If you share those model definitions between multiple apps with multiple developers than having more validation in the model will help. If it's just you and only one app, putting validation in the controller, although makes the controller more verbose, eliminates part of the magic and might lead to less debugging when you hit certain types of bugs. – sqreept Dec 21 '12 at 16:32
  • Thanks, I think that answers my queries. Posted in a separate thread here: http://stackoverflow.com/questions/13993669/where-to-validate-in-express-js-project-validation-at-the-database-layer-re –  Dec 21 '12 at 16:46
  • I am using the validator module already in my model: `validators.notEmpty`. –  Dec 21 '12 at 16:47
  • I think it's fine if you want to express an opinion on whether this should be done or not, but you should post it as a comment on the question, not as an answer to the question. – Adam Dec 29 '20 at 04:36
4

I know the thread is old but if it could save someone's time... My approach uses pre-validate hook and works perfectly for me

schema.virtual('passwordConfirmation')
    .get(function() {
      return this._passwordConfirmation;
    })
    .set(function(value) {
        this._passwordConfirmation = value;
    });

schema.pre('validate', function(next) {
    if (this.password !== this.passwordConfirmation) {
        this.invalidate('passwordConfirmation', 'enter the same password');
    }
    next();
});
Alexey
  • 980
  • 5
  • 12
2

I use express-validator before it ever gets down to the schema level in ./routes/signup.js:

exports.post = function(req, res){
  req.assert('email', 'Enter email').notEmpty().isEmail();
  req.assert('username', 'Enter username').notEmpty().isAlphanumeric().len(3,20);
  req.assert('password', 'Enter password').notEmpty().notContains(' ').len(5,20);

  res.locals.err = req.validationErrors(true);

  if ( res.locals.err ) {
    res.render('signup', { message: { error: 'Woops, looks like we need more info...'} });
    return;
  }

  ...//save
};
chovy
  • 72,281
  • 52
  • 227
  • 295
  • 4
    I was doing that, but I’ve learnt it's better to do it in the model. "Thin controller, fat model." –  Dec 21 '12 at 00:03
  • You can surface errors to the UI just as easily if you use a callback. –  Dec 21 '12 at 13:18
2

You can attach custom methods to your model instances by adding new function attributes to Schema.methods (you can also create Schema functions using Schema.statics.) Here's an example that validates a user's password:

userSchema.methods.checkPassword = function(password) {
    return (hash(password) === this.password);
};

// You could then check if a user's password is valid like so:
UserModel.findOne({ email: 'email@gmail.com' }, function(err, user) {
    if (user.checkPassword('secretPassword')) {
        // ... user is legit
    }
});
theabraham
  • 15,840
  • 9
  • 42
  • 41
  • Is this the right way to do it? The rest of the validation is done in the model, and errors surface from the `save` method (see code). I'm not sure I would combine errors from the model with errors like this one. `newUser.save(function (err) { if (err) { return res.redirect('/register'); } res.send('registered'); });` –  Dec 21 '12 at 13:22
1

The second verification password doesn't need to be submitted for registration. You could probably get away with validating the two fields are equal on the client-side.

Matthew Lucas
  • 443
  • 1
  • 5
  • 15
1

It's kind of late but for the sake of people having similar issues. i ran into a similar problem lately, and here was how i went about it; i used a library called joi

const joi = require('joi');
     ...
function validateUser(user){
  const schema = joi.object({
    username: joi.string().min(3).max(50).required(),
    email: joi.string().min(10).max(255).required().email(),
    password: joi.string().min(5).max(255).required(),
    password2: joi.string().valid(joi.ref('password')).required(),
  });

  return schema.validate(user);
}

exports.validate = validateUser;
Tron
  • 37
  • 7