Mongoose: unable to update data - javascript

I want to update user data without entering all the fields like for example if I only enter name then only name gets updated other values remain same. But, when I tried doing that my password validation is showing error, and also saying isAdmin is required.
here is my thunder Client Screen:
To check whether I am getting all user data I consollLoged and I am getting every field:
Here is my code:
userModel.js
const mongoose = require("mongoose");
const bcrypt = require("bcryptjs");
const userSchema = mongoose.Schema(
{
name: {
type: String,
required: true,
},
email: {
type: String,
required: true,
unique: true,
match: [
/^(([^<>()[\]\\.,;:\s#"]+(\.[^<>()[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
"Please enter a valid email address",
],
},
password: {
type: String,
required: true,
match: [
/^(?=.*[A-Za-z])(?=.*\d)(?=.*[#$!%*#?&])[A-Za-z\d#$!%*#?&]{8,}$/,
"Password must contain minimum eight characters, atleast one letter, one number & one speccial character ",
],
},
isAdmin: {
type: Boolean,
required: true,
default: false,
},
},
{
timestamps: true,
}
);
userSchema.methods.matchPassword = async function (enteredPassword) {
return await bcrypt.compare(enteredPassword, this.password);
};
// We are encrypting data before saving it
userSchema.pre("save", async function (next) {
if (!this.isModified("password")) {
next();
}
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
});
const User = mongoose.model("User", userSchema);
module.exports = User;
userController.js
// #description: Update user
// #route: PUT /api/users/:id
// #access: Private/Admin
exports.updateUser = async (req, res, next) => {
try {
const user = await User.findById(req.params.id);
if (user) {
console.log(`USER: ${user}`);
user.name = req.body.name || user.name;
user.email = req.body.email || user.email;
user.isAdmin = req.body.isAdmin;
const updatedUser = await user.save();
res.json({
_id: updatedUser._id,
name: updatedUser.name,
email: updatedUser.email,
isAdmin: updatedUser.isAdmin,
});
} else {
const error = new Error("Sorry, user Not Found");
error.status = 404;
next(error);
}
} catch (error) {
next(error);
}
};
UPDATE:
I tried commenting match for password in my userModel.js
password: {
type: String,
required: true,
// match: [
// /^(?=.*[A-Za-z])(?=.*\d)(?=.*[#$!%*#?&])[A-Za-z\d#$!%*#?&]{8,}$/,
// "Password must contain minimum eight characters, at-least one letter, one number & one special character ",
// ],
},
and I am able to update the values.
What can I do so that it works with match as well.

The problem is in pre-save hook:
// We are encrypting data before saving it
userSchema.pre("save", async function (next) {
if (!this.isModified("password")) {
next();
}
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
});
You call next() and the code goes further messing with this.password etc.
Just return after next()
// We are encrypting data before saving it
userSchema.pre("save", async function (next) {
if (!this.isModified("password")) {
next();
return
}
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
});
You can even simplify to:
// We are encrypting data before saving it
userSchema.pre("save", async function (next) {
if (this.isModified("password")) {
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
}
next()
});

Related

bcrypt compare method is returning false even if I used the same string when I hashed it

When I use the same password I hashed and stored in database as a parameter in compare method I get false. Really don't know whats the problem here:
router:
router.post("/users/login", async (req, res) => {
try {
const user = await User.findByCredentials(
req.body.email,
req.body.password
);
const token = await user.generateAuthToken();
console.log("Route token -->", token);
res.send({ user, token });
} catch (e) {
res.status(400).send(errors.e400);
}
});
Method for email and password validation ( calling compare method here):
userSchema.statics.findByCredentials = async (email, password) => {
const user = await User.findOne({ email });
if (!user) throw new Error("User does not exist.");
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) throw new Error("Invalid password.");
return user;
};
password hashing:
userSchema.pre("save", async function () {
try {
this.password = await bcrypt.hash(this.password, 8);
} catch (e) {
console.log(e);
}
});
user schema:
const userSchema = new mongoose.Schema({
username: {
type: String,
trim: true,
required: true,
unique: true,
},
email: {
type: String,
trim: true,
required: true,
unique: true,
validate(value) {
if (!validator.isEmail(value))
throw new Error("Email address is not valid.");
},
},
password: {
type: String,
trim: true,
required: true,
minLength: 7,
},
tokens: [
{
token: {
type: String,
required: true,
},
},
],
});

MongoDB document naming

I have tried so many time different things but unable understand that whenever I am saving data into MongoDB why am I getting the name of that document as below
User.js
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const userSchema = new Schema({
email: {
type: String,
require: true,
unique: true,
lowercase: true,
},
username: {
type: String,
require: true,
unique: true,
maxLength: 7,
},
password: {
type: String,
require: true,
minLength: 8,
},
});
const User = mongoose.model("user", userSchema);
module.exports = User;
When I have saved the data by hitting API through postman and printing the data in console, the data I am receiving is as below:
email {
_id: new ObjectId("627a4ae94b8958e3fe968311"),
email: 'abc#abc.com',
username: 'fzee07',
password: 'test1234',
__v: 0
}
So my question is this that why this object is getting saved and named as "email".
registerUser.js
const User = require("../../models/User");
const registerUser = async (req, res) => {
const { email, username, password } = req.body;
try {
const e_mail = await User.findOne({});
console.log("email", e_mail);
if (e_mail.email === email) {
res.status(409).json({
success: false,
msg: "User already exists",
});
} else {
if (username.length < 8) {
if (password.length >= 8) {
const user = User.create({ email, username, password });
res.status(201).json({
success: true,
data: "User Created",
});
} else {
res.status(401).json({
success: false,
data: "Password muste be minimum 8 characters",
});
}
} else {
res.status(401).json({
success: false,
data: "Username must be less than 8 characters",
});
}
}
} catch (err) {
console.log(err);
}
};
module.exports = registerUser;
I understand the problem now. Sorry about the confusion, I misread the second part of your question. The reason you're getting this in console:
email {
_id: new ObjectId("627a4ae94b8958e3fe968311"),
email: 'abc#abc.com',
username: 'fzee07',
password: 'test1234',
__v: 0
}
Is because you are console logging the string "email", then your user object from the database, which you have confusingly named e_mail.
Change the log message to look like this, and you're good:
console.log("user", e_mail);
In fact, your code needs some severe refactoring. Right now, it's not very readable. Here's an improvement:
const User = require('../../models/User');
const registerUser = async (req, res) => {
const { email, username, password } = req.body;
try {
// Find a user with that email
const user = await User.findOne({ email });
console.log('user', user);
// if it exists, return
if (user) {
res.status(409).json({
success: false,
message: 'User already exists',
});
return;
}
// Validate the username and password
// If validates, create the user
if (username.length < 8 && password.length >= 8) {
const newUser = User.create({ email, username, password });
res.status(201).json({
success: true,
message: 'User created',
data: newUser,
});
return;
}
// If all else fails, return 401
res.status(401).json({
success: false,
message: 'Username must be less than 8 characters & password must be at least 8 characters long',
});
} catch (err) {
res.status(500).json({
success: false,
message: 'Server error',
});
}
};
module.exports = registerUser;
I changed all the places where you had the key of data to have the key of message, as messages were the values.
Once again though, I really recommend doing the validation in Mongoose middleware/hooks instead of right within the route. Will save you headaches as your application grows.

user.save() is not a function when i am trying to save the model

const user = User.findOne({email:req.body.email});
if(!user){
return next(new ErrorHander("User not found",404));
}
const resetToken = user.getResetPasswordToken
await user.save({ validateBeforeSave: false });
I dont know why user.save is not working in mern mongodb i have same peice of code working for other person but not for me
What i did is that i get the user by fireing the query in the line one after having that user i should be able to use user.save() function here but sadly it is giving error
//here is my model schema
const mongoose = require("mongoose");
const validator = require("validator");
const bcrypt = require("bcryptjs");
const crypto = require("crypto")
const jwt = require("jsonwebtoken");
const userSchema = new mongoose.Schema({
name: {
type: String,
required: [true, "please Ente the your name"],
},
email: {
type: String,
required: [true, "Please Enter the email"],
unique: [true],
validate: [validator.isEmail, "Please Enter a valid email"],
},
password: {
type: String,
required: true,
minlength: [8, "Passwaord must be more that 8 characters"],
select: false,
},
avatar: {
public_id: {
type: String,
required: true,
},
url: {
type: String,
required: true,
},
},
role: {
type: String,
default: "user",
},
resetPasswordToken: String,
resetPasswordTokenExpire: Date,
createdAt: {
type: Date,
default: Date.now,
},
});
userSchema.pre("save", async function (next) {
if (!this.isModified("password")) {
next();
}
this.password = await bcrypt.hash(this.password, 10);
});
userSchema.methods.comparePassword = async function (password) {
return await bcrypt.compare(password, this.password);
};
userSchema.methods.getJWTToken = function () {
return jwt.sign({ id: this._id }, process.env.JWT_SECRET, {
expiresIn: process.env.JWT_EXPIRE,
});
};
// compare password
// userSchema.methods.comparePassword = async function(enterPass){
// return await bcyrypt.compare(enterPass,this.password)
// }
// Reset Password
userSchema.methods.getResetPasswordToken = function(){
const resetToken = crypto.randomBytes(20).toString("hex");
this.resetPasswordToken = crypto.createHash("sha256").update(resetToken).digest("hex");
this.resetPasswordTokenExpire = Date.now +15*60*1000;
return resetToken;
}
module.exports = mongoose.model("User", userSchema);
User.findOne will return a Promise. So you need to add await keyword.
const user = await User.findOne({email:req.body.email});
if(!user){
return next(new ErrorHander("User not found",404));
}
const resetToken = user.getResetPasswordToken
await user.save({ validateBeforeSave: false });

Getters and Setters for Virtual fields in Sequelize

I'm trying to hash my password field before storing it in the database. For that reason I've created a virtual field called password and an actual field called hashedPassword. The trouble is that when I try to encrypt the password in beforeCreate hook, it the user.password is undefined. I have tried every thing. I've also defined custom getters and setters for the virtual field. I don't know what I'm doing wrong here. Any help or guidance would be appreciated. Thanks.
import bcrypt from 'bcrypt';
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define(
'User',
{
passwordhash: {
type: DataTypes.STRING,
allowNull: false,
validate: {
notEmpty: true
}
},
password: {
allowNull: false,
type: DataTypes.VIRTUAL,
set(password) {
const valid = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[#$^+=!*()#%&]).{8,30}$/.test(
password
);
if (!valid) {
throw new Error(`Password not valid ${password}`);
}
this.setDataValue('password', password);
},
get() {
return this.getDataValue('password');
}
}
}
},
{
hooks: {
beforeCreate: function hashPassword(user) {
console.log('user is:', user);
return bcrypt
.hash('Abcdefgh1#', 12)
.then(hashed => {
user.passwordhash = hashed;
})
.catch(error => error);
}
}
}
);
User.associate = function(models) {
// associations can be defined here
};
return User;
};
use hooks :
hooks: {
beforeCreate: async (user, options) => {
let salt = await bcrypt.genSalt(10)
let hash = await bcrypt.hash(user.password, salt)
user.password = hash;
}
}
and for updating password fields use this:
User.beforeBulkUpdate(async instance => {
if (instance.attributes.password) {
let salt = await bcrypt.genSalt(10)
let hash = await bcrypt.hash(instance.attributes.password, salt)
instance.attributes.password = hash;
}
})
this works fine for me

Why do I get a 400 error when logging a user using bcrypt?

I'm trying to create login authentication, but I keep getting an 400 error in Postman saying that my syntax is bad when testing things out. I'm pretty sure my entire User model is solid, but for good measure, I've attached the whole thing in case something's off there. Otherwise, I'm really not sure what the problem is or where to go from here.
This is the data that I'm sending that triggers the 400 Bad Request (the request cannot be fulfilled due to bad syntax) and logs the invalid password to the console:
{
"email": "andrew#example.com",
"password": "Red12345!"
}
Here's my entire user model code:
const mongoose = require('mongoose')
const validator = require('validator')
const bcrypt = require('bcryptjs')
const userSchema = new mongoose.Schema({
name: {
type: String,
required: true,
trim: true
},
email: {
type: String,
unique: true,
require: true,
trim: true,
lowercase: true,
validate(value) {
if(!validator.isEmail(value)) {
throw new Error('Email is invalid')
}
}
},
age: {
type: Number,
default: 0,
validate(value) {
if(value < 0) {
throw new Error('Age must be a positive number.')
}
}
},
password: {
type: String,
trim: true,
lowercase: true,
required: true,
minlength: 7,
validate(value) {
if(value.toLowerCase().includes("password")) {
throw new Error("Password can't be 'password'.")
}
}
}
})
userSchema.statics.findByCredentials = async (email, password) => {
const user = await User.findOne({ email })
if (!user) {
throw new Error('User not found')
}
const isMatch = await bcrypt.compare(password, user.password)
if (!isMatch) {
throw new Error('Invalid password')
}
return user
}
//Hash the plain text password before saving
userSchema.pre('save', async function (next) {
const user = this
if(user.isModified('password')) {
user.password = await bcrypt.hash(user.password, 8)
}
next()
})
const User = mongoose.model('User', userSchema)
module.exports = User
And here's the user login router:
router.post('/users/login', async (req, res) => {
try {
const user = await User.findByCredentials(req.body.email, req.body.password)
res.send(user)
} catch (e) {
console.log(e.message)
res.status(400).send()
}
})

Categories

Resources