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?
Related
I'm trying to build a user model, but I want to make sure that username and email are unique. When I created the first user everything was ok, but when I try to create the second user with the same information, I got the some error that I can handle in when I will save, but the duplicate key wasn't there to handle it.
This is my schema file code:
const UserSchema = new Schema({
// this username with SchemaType of string
username: {
type: String,
lowercase: true,
required: [true, "username is required"],
unique: true,
trim: true,
minlength: [4, "try to user longer name"],
maxlength: [60, "your name is way too long"],
},
// virtual name
name: {
// name have two properties
// first is first and refer to first-name
// second is last and refer to last-name
first: {
type: String,
lowercase: true,
trim: true,
minlength: 4,
maxlength: 20
},
last: {
type: String,
lowercase: true,
trim: true,
minlength: 4,
maxlength: 20
}
},
password: {
type: String,
required: [true, "password is required"]
},
email: {
type: String,
required: [true, "email is required"],
unique: true
},
active: {
type: Boolean,
default: true
},
admin: {
type: Boolean,
default: false
},
meta: {
update: {
type: Date,
default: Date.now()
},
timestamp: {
type: Date,
default: Date.now()
}
}
});
UserSchema.virtual("fullname").get(function () {
// return the concatenation of first and last
return this.name.first + " " + this.name.last;
});
// Create User Model
const User = mongoose.model("User", UserSchema);
module.exports = User;
And this is my router code where I tried to handle it:
router.post("/register", (request, response) => {
const user = {
username: request.body.username,
email: request.body.email,
password: request.body.password
};
if (!user.email && !user.username && !user.password) {
return response.json({
"message": "please fill the whole information"
});
}
// put user info in model
const newUser = new User({
username: user.username,
email: user.email,
password: user.password
})
newUser.validate((err) => {
console.log(err);
});
// save User in model
newUser.save()
// return response with info
return response.status(201).json(user);
})
I think the explanation here is quite a simple one. You are specifying the unique attribute in your schema for multiple fields, so mongo will not allow you to create multiple entries with the same information. This is quite obvious.
Also, I noticed a bit of irregularity in your code. The save method you are calling returns a promise, which means the event loop will not block your code and the response will be returned immediately. For this, you either need to handle your response inside the then block or use async await throughout your code.
I would suggest the following changes:
router.post("/register", (request, response) => {
const user = {
username: request.body.username,
email: request.body.email,
password: request.body.password
};
if (!user.email && !user.username && !user.password) {
return response.json({
"message": "please fill the whole information"
});
}
// put user info in model
const newUser = new User({
username: user.username,
email: user.email,
password: user.password
})
newUser.validate((err) => {
if(err) {
response.status(403).json({ message: 'Your custom error message' });
}
newUser.save().then(res => {
return response.status(201).json(user);
}).catch(e => {
return response.status(500).json({ message: 'Your custom error message' });
})
});
})
I need a basic email verification after a user signs up.
my pseudo code for this is something like
1.user signs up
2.his data is stored in database.
3.a token is generated using crypto.
4.token is then send to email id provided.
5.user clicks the link a account is verified.
meanwhile a separate sequelize schema is created that stores the email id and the token.
now my problem is how to implement this in my project
passport.use('local-signup', new LocalStrategy(
{
usernameField: 'email',
passwordField: 'password',
passReqToCallback: true // allows us to pass back the entire request to the callback
},
function (req, email, password, done) {
var generateHash = function (password) {
return bCrypt.hashSync(password, bCrypt.genSaltSync(8), null);
};
User.findOne({
where: {
email: email.toLowerCase()
}
}).then(function (user) {
if (user) {
return done(null, false, {
message: 'That email is already taken'
});
}
else {
var userPassword = generateHash(password);
var data =
{
email: email.toLowerCase(),
password: userPassword,
firstname: req.body.firstname,
lastname: req.body.lastname,
mobileno: req.body.mobileno,
//verified_email: false,
//verified_mob: false
};
User.create(data).then(function (newUser, created) {
if (!newUser) {
return done(null, false);
}
if (newUser) {
return done(null, newUser);
}
});
}
});
}
));
i am new to nodejs but with all my understanding i guess things need to be impemented in
if (newUser) {
return done(null, newUser);
}
any guidance is appreciated.
my user schema..
module.exports = function (sequelize, Sequelize) {
var User = sequelize.define('user', {
id: {
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
firstname: {
type: Sequelize.STRING,
notEmpty: true
},
lastname: {
type: Sequelize.STRING,
notEmpty: true
},
//username: { type: Sequelize.STRING },
//about: { type: Sequelize.TEXT },
mobileno: {
type: Sequelize.STRING
},
email: {
type: Sequelize.STRING,
validate: { isEmail: true }
},
password: {
type: Sequelize.STRING,
allowNull: false
},
last_login: {
type: Sequelize.DATE
},
verified_email: {
type: Sequelize.BOOLEAN,
defaultValue: false,
},
verified_mob: {
type: Sequelize.BOOLEAN,
defaultValue: false,
}
//status: { type: Sequelize.ENUM('active', 'inactive'), defaultValue: 'active' }
});
return User;
}
At any circumstances do not share your token with any of your users. User ID's can easily be extracted by tokens and this can be harmful.
Instead, try this approach;
I assume your user model is something like this at your database
{
name: String,
auth: {
password: String //This will be bcrypted.
email: {
address: String,
verified: String || Boolean,
}
}
}
As you can see, verified field holds a String field or Boolean field.
At the moment you create user, (model.create({...}) sequence) preset the value of verified to sha256 of current time (you can use sha256(moment().format())) and save user.
At mail, send user a link like, yoursite.com/verify?code=[code] and then,
Create a route for user/verify?code=[code] in controller. Get user get the user holds code in verified field and change it to 'true'
You are done.
I have User Schema like this
const UserSchema = new Schema({
username: {
type: String,
unique: true,
required: true,
lowercase: true,
trim: true
},
email: {
type: String,
unique: true,
required: true
},
phoneNumber: {
type: Number,
unique: true,
required: true
},
});
When I post a user with duplicate name, phonenumber or email. I receive the error like this:
This is error message E11000 duplicate key error collection: project.users index: username_1 dup key: { : "john" }
So my question is that how I can know it is a duplicate name or duplicate phonenumber. I want to receive the error like this.
This phone number has been already used
This name has been already used
You can catch the error in your error handler. You can get the path name and value using regex. Simple example:
app.use((err, req, res, next) => {
if (err.code === 11000 || err.code === 11001) {
const pathRegex = err.message.match(/\.\$([a-z]+)/)
const path = pathRegex ? pathRegex[1] : '';
const keyRegex = err.message.match(/key:\s+{\s+:\s\"(.*)(?=\")/)
const key = keyRegex ? keyRegex[1] : '';
return res.status(409)
.json({ status: false, message: `${path} '${key}' already exists`})
}
}
You can also check mongoose-unique-validator that produces a mongoose 'ValidationError' that you can parse more easily.
Alternately you can use a path validation mechanism.
UserSchema.path('username').validate(function(value, done) {
this.model('User').count({ username: value }, function(err, count) {
if (err) {
return done(err);
}
done(!count);
});
}, 'User Name already exists');
UserSchema.path('email').validate(function(value, done) {
this.model('User').count({ email: value }, function(err, count) {
if (err) {
return done(err);
}
done(!count);
});
}, 'Email already exists');
UserSchema.path('phoneNumber').validate(function(value, done) {
this.model('User').count({ phoneNumber: value }, function(err, count) {
if (err) {
return done(err);
}
done(!count);
});
}, 'Phone already exists');
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.
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.