I'm trying to implement an authentication flow in Nodejs. I'm using MongoDB as database and there is a problem with 'bcrypt password hashing' and 'mongoose document versioning'.
When I create a new account and login with this account, there is no problem and everything is working. But when I do changes on subdocuments, versionKey "_v" is changing and I can no longer access the account. It throws me the 'Invalid password' error which comes from passport middleware. I don't figure out why it's happening.
Here is the structure:
Mongoose User Model
const bcrypt = require("bcrypt");
const userSchema = new mongoose.Schema(
{
name: { type: String, required: true },
surname: { type: String, required: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
username: { type: String },
bio: { type: String },
title: { type: String },
area: { type: String },
image: {
type: String,
default:
"https://icon-library.com/images/no-profile-pic-icon/no-profile-pic-icon-24.jpg",
},
experiences: [
{ type: mongoose.Schema.Types.ObjectId, ref: "Experience" },
],
friends: [{ type: mongoose.Schema.Types.ObjectId, ref: "User" }],
friendRequests: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
],
},
{ timestamp: true }
);
/**
* Enyrcyp user password before saving DB
*/
userSchema.pre("save", async function (next) {
try {
// const user = this;
// if (!user.isModified("password")) return next();
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
} catch (error) {
console.log("Bcryp hash error: ", error);
next(error);
}
});
/**
* Checks entered password and hashed password in DB
* returns boolean
* @param {String} enteredPassword
*/
userSchema.methods.isValidPassword = async function (enteredPassword) {
try {
return await bcrypt.compare(enteredPassword, this.password);
} catch (error) {
console.log("Bcrypt password check error: ", error);
next(error);
}
};
const User = mongoose.model("User", userSchema);
module.exports = User;
Passport middleware to handling user login process
passport.use(
new LocalStrategy(
{
usernameField: "email",
},
async (email, password, done) => {
try {
const foundUser = await db.User.findOne({ email });
if (!foundUser) throw new ApiError(400, "Invalid email ");
const isPasswordsMatched = await foundUser.isValidPassword(
password
);
if (!isPasswordsMatched)
throw new ApiError(400, "Invalid password");
//Send user if everything is ok
done(null, foundUser);
} catch (error) {
console.log("Passport local strategy error: ", error);
done(error, false);
}
}
)
);
I discovered that if I change the logic of hashing password it works. I deleted the mongoose pre save hook which was adding hash to password right before saving database.
Here is the working structure:
/**
* Enyrcyp user password before saving DB
*/
userSchema.methods.hashPassword = async function () {
try {
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
} catch (error) {
console.log("Bcryp hash error: ", error);
next(error);
}
};
So with this structure I'm just using bcrypt once when the user is signed up. Probably document version changes was effecting the bcrypt hash. So it works now.