Bcrypt password fails when i try to compare? - node.js

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

Related

How do I write truly custom error messages and codes for Mongoose validation?

I'm trying to follow the MVC architectural pattern and do all of my validation in my Mongoose model, rather than my controller.
I'm wondering how I can set error codes and truly custom error messages in my model (I.E. without the part that mongoose adds to the beginning of the message.)
At the moment my error message for the name field is: "message": "User validation failed: email: Please enter a valid email address", where it should be "Please enter a valid email address".
The response code from the server was 200 until I changed it in my errorHandlerMiddleware file, which is not ideal as it should be a 400 not the general 500.
So, somebody please help me to set the status code in my model and also make a custom error message.
Many thanks in advance!
const mongoose = require("mongoose");
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
const validator = require("validator");
const Schema = mongoose.Schema;
const UserSchema = new Schema(
{
name: {
type: String,
required: [true, "Please add a name"],
minLength: [3, "Name must be at least 3 characters"],
},
email: {
type: String,
required: [true, "Please add an email address"],
unique: [true, "It looks like you already have an account!"],
validate: {
validator: (value) => {
if (!validator.isEmail(value)) {
throw new Error("Please enter a valid email address");
}
},
},
},
password: {
type: String,
required: [true, "Please add a password"],
},
tokens: [
{
token: {
type: String,
required: true,
},
},
],
},
{ timestamps: true }
);
UserSchema.methods.toJSON = function () {
const user = this;
const userObject = user.toObject();
delete userObject.password;
delete userObject.tokens;
return userObject;
};
UserSchema.methods.generateAuthToken = async function () {
const user = this;
const token = jwt.sign({ _id: user._id.toString() }, process.env.JWT_SECRET, {
expiresIn: "7 days",
});
user.tokens = user.tokens.concat({ token });
await user.save();
return token;
};
UserSchema.statics.findByCredentials = async (email, password) => {
const user = await User.findOne({ email });
if (!user) {
statusCode(401);
throw new Error("Unable to login");
}
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) {
statusCode(401);
throw new Error("Unable to login");
}
return user;
};
UserSchema.pre("save", function (next) {
if (this.password.length < 6) {
throw new Error("Password must be at least 6 characters");
}
if (!this.isModified("password")) {
return next();
}
this.password = bcrypt.hashSync(this.password, 10);
return next();
});
module.exports = User = mongoose.model("User", UserSchema);
i need a real custom error code and message from mongoose
I decided to catch the errors in the try/catch block on the controller, as so:
try {
await user.save();
} catch (err) {
// Error handling for duplicate email address
if (err.code === 11000) {
return res.status(400).send("It looks like you already have an account.");
}
// Error handling for misc validation errors
if (err.name === "ValidationError") {
res.status(400);
return res.send(Object.values(err.errors)[0].message);
}
}

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 show Errors from Mongoose?

I have a user I can save in MongoDB, when I enter correct data, the save works.
But when I enter wrong data, I can't catch the errors to be seen for the user. All I can see is this on the code editor:
...UnhandledPromiseRejectionWarning: ValidationError: User validation
failed: username: username is not there!...
This error "kills" the server, and so I'm not rendering home-guest template.
The question is how I can catch the errors and show them to the user?
Schema:
const mongoose = require("mongoose")
const userSchema = new mongoose.Schema({
username: {
type: String,
required: [true, "username is not there!"],
minlength: 3,
maxlength: 20,
},
email: {
type: String,
required: true,
},
password: {
type: String,
required: true,
minlength: 6,
maxlength: 20,
},
})
module.exports = mongoose.model("User", userSchema)
Controller:
const mongoose = require("mongoose")
const userModel = require("../models/userModel")
exports.signUp = async (req, res) => {
const { username, email, password } = req.body
try {
const user = await new userModel({
username,
email,
password,
})
user.save()
} catch (error) {
res.render("home-guest", { error })
}
}
You just need to add an await to the save operation, since that's also async:
const mongoose = require("mongoose")
const userModel = require("../models/userModel")
exports.signUp = async (req, res) => {
const { username, email, password } = req.body
try {
const user = await new userModel({
username,
email,
password,
})
// Wait for the save to complete, also allowing you to catch errors
await user.save()
} catch (error) {
res.render("home-guest", { error })
}
}
EDIT: And note that you do not need an async in front of new userModel(). new cannot return a promise, it is always synchronous.

How to make password validation in NodeJS with Mongoose

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");
}
});

mongoose Model.create function returns undefined

The above query returns a 200 when I try to create a User, but whenever I log into MongoDB there is no collections created. Can anyone help ?
//user model
const userSchema = mongoose.Schema({
name: {
type : String,
required : true,
trim : true
},
email: {
type: String,
required: true,
unique: true,
lowercase: true,
validate: value => {
if(!validator.isEmail(value)){
throw new Error({error : 'Invalid email address'})
}
}
},
password: {
type: String,
required: true,
minLength: 5
},
// a user can have multiple jobs
jobs : [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Job'
}],
tokens: [{
token: {
type: String,
required: true
}
}]
})
const User = mongoose.model('User', userSchema)
module.exports = User
// user functions written
createUser(name, email, password){
return User.create({name: name, email: email, password : password}, (err, docs) => {
if(err){
throw err.message;
}
});
}
//routes.js
// user create
router.post('/users', async(req, res) => {
try{
const {name, email, password } = req.body
const user = userManager.createUser(name, email, password); [1]
res.status(200).json(user)
}
catch(error) {
res.status(400).send({error : error.message})
}
})
The line[1] returns undefined. Why ?
note : all module requirements are fulfilled
After you create the schema you need to create a Model FROM that schema.
Example from MDN:
// Define schema
var Schema = mongoose.Schema;
var SomeModelSchema = new Schema({
a_string: String,
a_date: Date
});
// Compile model from schema
var SomeModel = mongoose.model('SomeModel', SomeModelSchema );
Now after you create the model you can use SomeModel.create
EDIT:
line[1] will always return undefined because you are using callbacks and only way to get value out of callback is either push another callback(I would really discourage that). But best way is to use Promises now mongoose by default supports `Promises. So, basically for promises it will be,
// user functions written
async function createUser(name, email, password){
try {
return await User.create({ name: name, email: email, password: password });
} catch (err) {
throw err.message;
}
}
In the router adda await:
const user = await userManager.createUser(name, email, password);
The problem is you call an asynchronous function synchronously. It returned undefined because the function hasn't been resolved yet.
A solution could be to use promises or async/await.
Example:
async createUser(name, email, password) {
const createdUser = await User.create({name,email,password});
return creaatedUser;
}
Something I ran into was you need to pass in an empty object if your not setting any fields - i.e.
Good: Model.create({})
Bad: Model.create()

Resources