Mongoose force validation only if data is present - node.js

I am creating a user model that will create different Strategies like, local, facebook, gmail... and i want every object to do his own validation but if i does not put values in lets say local, i does not want to validate this fields and get an error.
For example:
var UserSchema = new Schema({
local: {
email : {
type : String,
required: true,
validate: emailValidator,
index : {
unique: true
}
},
firstName: {
type : String,
validate: nameValidator,
required: true
},
password : {
type : String,
validate: passwordValidator,
required: true
}
},
facebook: {
id : String,
email: String,
name : String
}
});
Now when i want to save some user that come from facebook like this:
var newUser = new User();
newUser.facebook.id = profile.id;
newUser.facebook.name = profile.name.givenName;
newUser.facebook.email = profile.emails[0].value;
newUser.save(function( err, user ) {
if( err ) console.log(err);
done(null, user);
});
I will get an error because the local object validation failed. So how i can make them not depend on each other and still validate the data when insert the values?

You will have to create a custom validator and check if the proper strategy is set:
var emailValidator = [
{
validator: function(value) {
if(!this.local) return true;
return value;
},
msg: 'Email is required.'
},
{
validator: function(value) {
/* your current validation */
},
msg: 'Your error message...'
}
];
Just move your current emailValidator logic to the second function in this array and do this to the other required fields.

Related

Mongoose - Is it possible to access schema properties inside a mutation?

Newbie here. I'm trying to create a movie recommendation app and I've been struggling with this authorization problem for a while now. My goal is to have a single user schema with a Boolean 'isAdmin' property to differentiate between ordinary users and admin users. The problem is that when I attempt to query the isAdmin variable in the mutation to ensure that the logged in user passed in from the context has the necessary privileges to perform the operation, I get undefined.
User Schema
const userSchema = new mongoose.Schema({
username: {
type: String,
index: {
unique: true
}
},
email: {
type: String,
required: true,
index: {
unique: true
}
},
password: {
type: String,
required: true
},
contributions: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Movie'
},
isAdmin: {
type: Boolean,
required: true,
default: false
}
})
newMovie mutation
newMovie: async (parent, args, { models, user }) => {
if (!user){
throw new AuthenticationError('You must be signed in to submit a new movie')
}
console.log(user.isAdmin)
if (user && user.isAdmin === false) {
throw new ForbiddenError('You are not qualified')
}
return await models.Movie.create({
title: args.title,
year: args.year,
addedBy: mongoose.Types.ObjectId(user.id)
})
}
I attempted to console log user.isAdmin to see the value but instead I'm getting undefined. I have also tried using enum values 'user and admin' for a property 'roles' with the same result.
After struggling for hours, I realized that loading the user from the context returns an object with only the ObjectId and iat which I believe is the object's date of creation. To access the rest of the properties of the context user, I had to search for the user in models and assign that to a variable from which I can access the rest of the properties.
newMovie: async (parent, args, { models, user }) => {
if(!user){
throw new AuthenticationError('You must be logged in')
}
active = await models.User.findById(user.id)
if(active && active.role !== "ADMIN"){
throw new ForbiddenError('Only users can leave reviews')
}
return await models.Movie.create({
title: args.title,
year: args.year,
addedBy: mongoose.Types.ObjectId(active.id)
})
This was the working solution for this problem.

Why User email validation failed? Mongoose ValidationError:

I wanted to extend my UserSchema to validate email
const userSchema = new mongoose.Schema(
{
username: {
type: String,
},
name: {
type: String,
},
email: {
type: String,
trim: true,
required: [true, 'Please add an email'],
unique: true,
lowercase: true,
},
I installed email-validator.
userSchema.path('email').validate((email) => {
if (!validator.validate('email')) { return false; }
if (!email) { return false; }
if (email.length === 0) { return false; }
return true;
}, 'Email must have a valid format!');
I made a POST request
"username" : "KoleSOmbor",
"name" : "Niki",
"email" : "koprivica#gmail.com",
Error
POST /api/v1/users 500 18.706 ms - 618
ValidationError: User validation failed: email: Email must have a valid format!
Why?
SOLVED
if (!validator.validate(email)) { return false; }
Works fine.

Manually populating Mongodb field on document creation with Mongoose

Following the Mongoose documentation, I was able to create two docs, but am unable to populate one with the other.
Despite manually setting the 'account' value to reference the other document, my database doesn't seem to create the relation.
Below is the code I've used:
UserAuth.findOne({ email }, (err, user) => {
if (err) return done(err);
if (user) {
return done(null, false,
{ message: 'It appears that email address has already been used to sign up!' });
}
// Create the user account
const newAccount = new UserAccount({
name: {
first: req.body.firstName,
last: req.body.lastName,
},
});
newAccount.save((err) => {
if (err) throw err;
// Create the user authentication
const newAuth = new UserAuth({
email,
account: newAccount,
});
newAuth.password = newAuth.generateHash(password);
newAuth.save((err) => {
if (err) throw err;
return done(null, newAuth);
});
return done(null, newAccount);
});
});
Collections:
User Auth
const UserAuthSchema = new Schema({
email: {
type: String,
required: true,
},
password: {
type: String,
required: true,
},
account: {
type: Schema.Types.ObjectId,
ref: 'User',
},
});
module.exports = mongoose.model('UserAuth', UserAuthSchema);
User Account
const UserSchema = new Schema({
name: {
first: {
type: String,
required: true,
},
last: {
type: String,
required: true,
},
},
team: {
type: Schema.Types.ObjectId,
ref: 'Team',
},
image: {
type: String,
default: 'assets/default_user.png',
},
});
module.exports = mongoose.model('User', UserSchema);
It looks like the part:
// Create the user authentication
const newAuth = new UserAuth({
email,
account: newAccount,
});
should be:
// Create the user authentication
const newAuth = new UserAuth({
email,
account: newAccount._id,
});
And then, when you query the collection, you have to say which field should be populate, as shown in (Mongoose documentation)[http://mongoosejs.com/docs/populate.html]
Ad please check that the types of the 2 linked fields are the same as mentioned in the documentation.

Mongoose - asynchronous validation of schema object

I am trying to determine how to do asynchronous validation for a Mongoose schema - specifically in this case the username. TMK, to ensure that the username is unique, we have to manually query the database to see if the same username already exists. This is an asynchronous query. However the methodology of having a 'validate:' property for each schema item, seems to ask for a synchronous validation function. In other words, this line:
validate: [validation.usernameValidator, 'not a valid username']
seems to require that usernameValidator be synchronous, and the problem is I need it to be async, for the reason aforementioned.
So, I have a Mongoose schema for a User like so:
var validation = {
usernameValidator: function (candidate) {
return true;
},
passwordValidator: function (candidate) {
return true;
}
};
userSchema = mongoose.Schema({
username: {
type: String,
isUnique: true,
required: true,
validate: [validation.usernameValidator, 'not a valid username']
},
passwordHash: {
type: String,
required: true,
validate: [validation.passwordValidator, 'not a valid password']
},
email: {
type: String,
isUnique: true,
required: true,
validate: [validation.emailValidator, 'not a valid email address']
}
});
userSchema.pre('save', function (next) {
var self = this;
if (!self.isModified('passwordHash')) {
return next();
}
bcrypt.hash(self.passwordPreHash, SALT_WORK_FACTOR, function (err, hash) {
if (err) {
return next(err);
}
else if(hash == null){
return next(new Error('null/undefined hash'));
}
else {
self.passwordHash = hash;
next();
}
});
});
//is the following function my best bet?
userSchema.path('username').validate(function (value, respond){
this.findOne({ username: value }, function (err, user){
if(user) respond(false);
});
}, 'This username has been already registered');
is my only option to leave out the validation.usernameValidator methodology, and validate username with userSchema.path('username').validate..?
Mongoose should handle this provided that you specify unique: true on that field.
For example
userSchema = mongoose.Schema({
username: {
type: String,
unique: true,
required: true
},
passwordHash: {
type: String,
required: true
},
email: {
type: String,
unique: true,
required: true
}
});
ADDITION:
Mongoose will declare a unique index provided that you specify such in your schema(as done in example above). This prevents having to query into mongodb to see if another document has a field of the same value. You can read about it here.
You can read more about Unique Indexes for mongodb here, if you'd like to learn more about their behaviour.
Note: A validation error will not be throw if a non-unique value is provided. See the mongoose docs for more info on this.

Model with unique attribute is being created twice

I am using this passport-generate-auth module, and I am trying to get my grasp around understanding this whole thing.
So, in my User model, I've got
var User = {
schema: true,
attributes: {
username: {
type: 'string',
unique: true,
required: true
},
email: {
type: 'email',
unique: true,
required: true
},
password: {
type: 'string',
required: true,
minLength: 8
},
}
};
module.exports = User;
And when I call
exports.register = function (req, res, next) {
var email = req.param('email')
, username = req.param('username')
, password = req.param('password');
User.create({
username: username
, email: email
, password: password
}, function (err, user) {
if (err) {
if (err.code === 'E_VALIDATION') {
if (err.invalidAttributes.email) {
req.flash('error', 'Error.Passport.Email.Exists');
} else {
req.flash('error', 'Error.Passport.User.Exists');
}
}
return next(err);
}
});
};
};
when providing username and email that already exist in the database, the new entry is stored in the DB, instead of giving me an error msg.
Isn't User.create() supposed to take care of checking in the schema attributes rules whether they are unique and then check the records in the DB for a record with a value that already exists?

Resources