How to make password validation in NodeJS with Mongoose - node.js

I have registration form with username, mail, password and password2. I want to verify passwords that they actually match. I verify practically everything in Mongoose Scheme but I cannot find any useful information in documentation how to grab password2 without actually saving it to database. (I have function to crypt password which runs only before saving)
const userSchema = new mongoose.Schema({
username: {
type: String,
unique: true,
required: true,
trim: true,
validate(value) {
if (!validator.isAlphanumeric(value , 'pl-PL')) {
throw new Error('Name cannot contain special characters.')
}
}
},
email: {
type: String,
unique: true,
required: true,
trim: true,
lowercase: true,
validate(value) {
if (!validator.isEmail(value)) {
throw new Error('Email is invalid')
}
}
},
password: {
type: String,
required: true,
validate(value) {
console.log(value)
if(value !== this.password2) {
throw new Error("Passwords don't match. Try again.")
}
if(value.length < 8) {
throw new Error("Passwords is too short. At least 8 characters.")
}
}
},
tokens: [{
token: {
type: String,
required: true
}
}]
})

You don't need to make password2 a part of userSchema. The better way is to make a compare password function like this:
UserSchema.methods.comparePassword = function(plaintext, callback) {
return callback(null, Bcrypt.compareSync(plaintext, this.password));
};
also you can make a use of Schema.pre:
UserSchema.pre("save", function(next) {
if(!this.isModified("password")) {
return next();
}
this.password = Bcrypt.hashSync(this.password, 10);
next();
});
After this, you need to call the compare function from user controller. Something like this (depending on your logic):
var user = await UserModel.findOne({ username: request.body.username }).exec();
if(!user) {
return response.status(400).send({ message: "The username does not exist" });
}
user.comparePassword(request.body.password, (error, match) => {
if(!match) {
return response.status(400).send({ message: "The password is invalid" });
}
});
For details you can read this excellent article.

You can check password and password2 in your register route, and if they are same you can continue to register.
A sample register route would be like this:
router.post("/register", async (req, res) => {
try {
const { username, email, password, password2 } = req.body;
if (password !== password2) return res.status(400).send("Passwords dont match");
let user = await User.findOne({ email });
//or
//let user = await User.findOne({ username });
if (user) return res.status(400).send("User already registered.");
user = new User({ username, email, password });
user = await user.save();
//todo: at this point you may generate a token, and send to the client in response header or body
res.send(user);
} catch (err) {
console.log(err);
res.status(500).send("Server error");
}
});

Related

empty value when using statics - node Js

I am trying to store a user in MongoDB but when I send a test request from postman the req.body successfully logged to the console but MongoDB throw an error of empty values I put a function to test if I got any value in the statistic function but I got nothing back
my schema :
{
profilePicture: {
type: String,
required: true,
},
username: {
type: String,
required: true,
},
email: {
type: String,
required: true,
},
password: {
type: String,
required: true,
},
},
{ timestamps: true }
);
my static function :
UserSchema.statics.signup = async function (
profilePicture,
username,
email,
password
) {
const exist = await this.findOne({ username });
if (exist) {
throw Error("username exist");
}
if (!password || !profilePicture || !username || !email) {
throw Error("missing");
}
// password are empty so hash don't work fix it first
// const salt = await bcrypt.genSalt(10);
// const hash = await bcrypt.hash(password, salt);
const user = await this.create({
username,
password,
profilePicture,
email,
});
return user;
};
const signupUser = async (req, res) => {
const { profilePicture, username, email, password } = req.body;
console.log(req.body);
try {
const user = await User.signup({
profilePicture,
username,
email,
password,
});
const token = createToken(user._id);
res.status(400).json({ username, token });
} catch (error) {
res.status(200).json({ error: error.message });
}
};
when I send a request from postman I got the req.body working but it shows that the fields are empty this function run
if (!password || !profilePicture || !username || !email) {
throw Error("missing");
}
i use body-parser and send data from postman with the same key name
Your static function takes in four parameters, but you are calling it with a single object.
Try changing the User.signup call to:
const user = await User.signup(
profilePicture,
username,
email,
password,
);
(without the { and }).

How to exclude password from user object sent in response in express.js?

I am returning a response object with the user and jwt token whenever a user logs in. But I want to exclude the password field when the user is sent in the response.
Controller:
const loginUser = asyncHandler(async (req, res) => {
const { email, password } = req.body;
// Check for user email
const user = await User.findOne({ email });
if (user && (await bcrypt.compare(password, user.password))) {
res.json({
user: user,
token: generateToken(user._id),
});
} else {
res.status(400);
throw new Error("Invalid credentials");
}
});
if I exclude the password when finding the user like this:
const user = await User.findById(decoded.id).select("-password");
Then bcrypt's compare method will not work as it needs user.password.
Please tell me how I can exclude password from user object in JSON response?
You can set undefined to the user before returning the user.
const loginUser = asyncHandler(async (req, res) => {
const { email, password } = req.body;
// Check for user email
const user = await User.findOne({ email });
if (user && (await bcrypt.compare(password, user.password))) {
user.password = undefined;
res.json({
user: user,
token: generateToken(user._id),
});
} else {
res.status(400);
throw new Error('Invalid credentials');
}
});
Or you can use the toJSON method in the user schema to exclude password:
const userSchema = new mongoose.Schema(
{
email: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
},
},
{
toJSON: {
transform(doc, ret) {
delete ret.password;
},
},
},
);

Hash Password does not match while loading from DB

I'm trying to login with my credentials but while matching password after loading from db it returns always False if still password is correct.
Login Route-
router.post('/users/login', async (req, res) => {
// console.log(req.body.email)
// console.log(req.body.password)
try {
const user = await User.findByCredentials(req.body.email, req.body.password)
res.send(user)
} catch (e) {
res.status(400).send(e)
}
})
Schema Pre-save
userSchema.pre('save', async function (next) {
const user = this
if (user.isModified('password') || user.isNew) {
user.password = await bcrypt.hash(user.password, 8)
}
next()
})
Login using credentials (email and password)-
userSchema.statics.findByCredentials = async (email, password) => {
const user = await User.findOne({ email: email })
// console.log(user)
if (!user) {
throw new Error('Unable to login')
}
const hashedPwd = await bcrypt.hash(password, 8);
console.log(hashedPwd)
const isMatch = await bcrypt.compare(password, user.password)
console.log('Password Match', isMatch)
if (!isMatch) {
throw new Error('Unable to login')
}
return user
}
Schema for User-
const User = mongoose.model('User', {
name: {
type: String,
required: true,
trim: true
},
email: {
type: String,
required: true,
trim: true,
lowercase: true
},
age: {
type: Number,
default: 0
},
password: {
type: String,
required: true,
trim: true,
lowercase: true,
minLength: 7
}
})
Password is stored in lowercase that's why it is showing false everytime i'm matching my password.
While storing password in DB I stored it in lowercase letters so everytime while comparing user entered password from db password it results to False.
So, by removing lowercase: true from password of User schema my error got resolved.

How to use bcrypt.compare in sails js schema?

I have a user model like this:
module.exports = {
attributes: {
email: {
type: 'string',
isEmail: true,
unique: true,
required: true
},
password: {
type: 'string',
required: true
}
},
beforeCreate: (value, next) => {
bcrypt.hash(value.password, 10, (err, hash) => {
if (err){
throw new Error(err);
}
value.password = hash;
next();
});
},
};
Now when I want to match the password during login, how do I decrypt the password, if possible I would prefer to perform it in the user model file.
controller/ login.js
module.exports = {
login: async (req, res) => {
try{
const user = await User.findOne({email: req.body.email});
if (!user){
throw new Error('Failed to find User');
}
// here I want to match the password by calling some compare
//function from userModel.js
res.status(201).json({user: user});
}catch(e){
res.status(401).json({message: e.message});
}
},
};
first try to find the user with the given username by user
const find = Users.find(user=>user.username===req.body.username)
if(!find){
res.send('User Not Found')
}
else{
if( await bcrypt.compare(req.body.password,find.password)){
//now your user has been found
}
else{
//Password is Wrong
}
}
You must use bcrypt.compare(a,b)
a = given password by user
b = original password if username exist
hope it solve your problem

Bcrypt password fails when i try to compare?

I am a complete beginner in coding, currently learning NodeJs and i am stuck with this situation for days now.
I am trying to compare the hashed password in my mongodb with the users input through postman.
I am using bcrypt to compare the hashed password with the original string but i am getting false statement.
Any help is much appreciated
This is the mongoose model Schema,
const usersSchema = new mongoose.Schema({
name: {
type: String,
required: true,
trim: true,
},
email: {
type: String,
unique: true,
required: true,
lowercase: true,
validate(value) {
if (!validator.isEmail(value)) throw new Error("Invalid email");
},
},
password: {
type: String,
required: true,
trim: true,
minLength: 7,
validate(value) {
if (value.toLowerCase().includes("password")) {
throw new Error("Password should not consist of string 'password'.");
}
},
},
})
Right here I hash the password before saving to the database;
usersSchema.pre("save", async function (next) {
const user = this;
const saltRounds = 8;
if (user.isModified("password")) {
user.password = await bcrypt.hash(user.password, saltRounds);
}
next();
});
Below is the login route;
router.post("/users/login", async (req, res) => {
try {
const user = await Users.findByCredentials(
req.body.email,
req.body.password
);
res.send(user);
} catch (error) {
res.status(400).send(error);
}
});
Below is where I try to compare the passwords, help me figure out why I am getting false.
usersSchema.statics.findByCredentials = async (email, password) => {
const user = await Users.findOne({ email: email });
if (!user) {
throw new Error("Unable to log in!");
}
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) {
throw new Error("Unable to login");
}
return user;
};
when ever you use bcrypt it works with you password property on your model
In this case you dont want to trim or lower case any properties of password.
So when designing Schema we dont want to over-load password with extra validations.We have to work with minimal validations in order for bcrypt to function properly.I had the same issue i removedlowerCase:true,trim:true from the Schema then it worked.
try to remove extra validations or restriction from password other wise it will interfere with bcrypt compare function.
try using bcryptjs.hashSync()
and bcryptjs.compareSync instead

Resources