Search code examples
node.jsmongodbmongoosemongodb-querymongoose-schema

Prevent _id field from being supplied in a MongoDB query


I'm using Mongoose in NodeJS to control a MongoDB database.

I'm creating an API and for obvious security reasons, I want to prevent the auto generated document _id field from getting replaced by a manually generated one in the API request.

Schema:

{ name: String }

Creating a document:

const record = {
    _id: '5e35517cc894c90327a34baf'
    name: 'bob'
}

const insertRecords = async () => {
    await Quiz.create(record);
};

insertRecords();

Results in the following document:

{
   _id: '5e35517cc894c90327a34baf'
   name: 'bob'
}

As can be seen, the _id supplied in the query, as long as it's a valid ObjectID, would replace the _id that was supposed to be auto generated by mongo.

Is there a way to check if this _id field is in the query so that I can reject the API request? The .create method triggers the pre save middleware hook which would always have the _id of the final document so I cannot depend on it to know whether the _id was in the query or it's the auto generated one.

The only option I found is to disable the _id field altogether but this does not make sense.


Solution

  • Solution #1 - Use .create() method with an explicit object.

    It's actually easier than you think. This is self-explanatory - we only define what we want to allow. Mongoose will ignore anything that's not in the object.

    const record = {
        _id: '5e35517cc894c90327a34baf'
        name: 'bob'
    }
    
    const insertRecords = async () => {
        await Quiz.create({
          name: record.name // only allow names.
        });
    };
    
    insertRecords();
    

    Solution #2 - Define a function to clear unwanted objects.

    You can define a helper function to clear out unwanted fields.

    const filterObj = (obj, ...allowedFields) => {
      const newObject = {};
    
      // If the current field is one of the allowed fields, keep them in the new object.
      Object.keys(obj).forEach((el) => {
        if (allowedFields.includes(el)) {
          newObject[el] = obj[el];
        }
      });
    
      return newObject;
    };
    

    How to use:

    const filteredRecord = filterObj(record, 'name'); // arbitrary list of allowed fields. In this case, we'll only allow 'name'.
    await Quiz.create(filteredRecord);