Search code examples
node.jsmongodbmongoosemongoose-schema

Mongoose Schema.method() is not working, and showing an error message


I am taking password input from the user and encrypting the password using crypto, then saving into the database. This is my code, here I am storing the encrypted password into the encry_password property that comes from the userSchema. But, this is giving me error that "this.securePassword" is not a function.

const mongoose = require("mongoose");
const crypto = require("crypto");
const { v1: uuidv1 } = require("uuid");

const userSchema = new mongoose.Schema({
  name: {
    type: String,
    required: true,
    maxlength: 32,
    trim: true,
  },
  lastname: {
    type: String,
    maxlength: 32,
    trim: true,
  },
  email: {
    type: String,
    trim: true,
    required: true,
    unique: true,
  },
  usrinfo: {
    type: String,
    trim: true,
  },
  encry_password: {
    type: String,
    required: true
  },
  salt: String,
  role: {
    type: Number,
    default: 0,
  },
  purchases: {
    type: Array,
    default: [],
  },
}, { timestamps: true });


userSchema.virtual("password")
  .set((password) => {
    this._password = password;
    this.salt = uuidv1();
    this.encry_password = securePassword(password, uuidv1());
    console.log(this.encry_password);
  })
  .get(() => {
    return this._password;
  });

// const authenticate = function (plainPassword, encry_password) {
//   return securePassword(plainPassword) === encry_password;
//   };

const securePassword = function (plainPassword, salt) {
  if (!plainPassword) return "";
  try {
    return crypto.createHmac("sha256", salt).update(plainPassword).digest("hex");
  } catch (error) {
    return "";
  }
};


module.exports = mongoose.model("User", userSchema);

Route for user signup

exports.signup = (req, res) => {
    console.log(req.body);
    const user = new User(req.body);
    user.save((err, user) => {
        if (err) {
            console.log(err);
            res.status(400).json({
                err: "Note able to save the user in database"
            });
        } else {
            res.json(user);
        }
    });
};


Solution

  • first of all, in this situation you shouldn't use virtual

    Virtuals

    Virtuals are document properties that you can get and set but that do not get persisted to MongoDB. The getters are useful for formatting or combining fields, while setters are useful for de-composing a single value into multiple values for storage.

    but in the scope of virtual, this cannot access to method, you can not access to the method like your manner, it's a example of method usage in mongoose

      const Animal = mongoose.model('Animal', animalSchema);
      const dog = new Animal({ type: 'dog' });
    
      dog.findSimilarTypes((err, dogs) => {
        console.log(dogs); // woof
      });
    

    you can check the method documantation:

    if you want just access to securePassword in your manner you can like this and delete method mongoose complately because this is not the place to use method:

    UserSchema.virtual("password")
      .set((password) => {
        this._password = password;
        this.salt = uuidv1();
        console.log("This is running");
        this.encry_password = securePassword(password, this.salt);
        console.log(encry_password);
      })
      .get(() => {
        return this._password;
      });
    
    const authenticate = function (plainPassword, encry_password) {
      return securePassword(plainPassword) === encry_password;
    };
    
    const securePassword = function (plainPassword, salt) {
      if (!plainPassword) return "";
      try {
        return crypto
          .createHmac("sha256", salt)
          .update(plainPassword)
          .digest("hex");
      } catch (error) {
        return "";
      }
    };
    

    if you want to create authenticate service, change your manner, and don't use virtual for password and use pre save

    before saving information about users in db this tasks will be done check the pre documentation

    userSchema.pre("save", async function (next) {
      try {
        this.password = securePassword (plainPassword, salt);
      } catch (error) {
        console.log(error);
      }
    });
    

    after created a hash password save informations like this :

    const userSchema = new mongoose.Schema({
        .
        .
        .
        password: { //convert encry_password to password
          type: String,
        }
        .
        .
        .
      }, { timestamps: true });
      //every time want to user save this method called
      userSchema.pre('save', function (next) {
        this.salt = uuidv1()
        this.password = securePassword(this.password, this.salt)
        next()
     })
     //for have a clean  routes, you can create a static methods
     userSchema.statics.Create = async (data) => {
        let model = new User(data); 
        let resUser = await model.save(); //save your user
        return resUser;
      };
    
      const securePassword = function (plainPassword, salt) {
        if (!plainPassword) return "";
        try {
          return crypto.createHmac("sha256", salt).update(plainPassword).digest("hex");
        } catch (error) {
          return "";
        }
      };
      
      let User  = mongoose.model("User", userSchema)
      module.exports = {User};
    

    change controller like this :

    let {User} = require("./path of user schema")
    exports.signup = async (req, res) => {
      try {
        console.log(req.body);
        const user = await User.create(req.body); //create a user
        res.json(user);
      } catch (error) {
        console.log(err);
        res.status(400).json({
          err: "Note able to save the user in database",
        });
      }
    };
    

    NOTE : in req.body, name of password field, should be password