I'm trying to limit the fields a user can post when inserting an object in mongodb. I know ho i can enforce fields to be filled but I can't seem to find how to people from inserting fields that I don't want.
This is the code I have now for inserting an item.
app.post("/obj", function (req, res) {
var newObj = req.body;
//TODO filter fields I don't want ?
if (!(newObj .id || newObj .type)) {
handleError(res, "Invalid input", "Must provide a id and type.", 400);
return;
}
db.collection(OBJ_COLLECTION).insertOne(newObj, function(err, doc) {
if (err) {
handleError(res, err.message, "Failed to create new object.");
} else {
res.status(201).json(doc.ops[0]);
}
});
});
There's likely JS native ways to do this, but I tend to use Lodash as my toolbox for most projects, and in that case what I normally do is setup a whitelist of allowed fields, and then extract only those from the posted values like so:
const _ = require('lodash');
app.post("/obj", function (req, res) {
var newObj = _.pick(req.body, ['id', 'type','allowedField1','allowedField2']);
This is pretty straightforward, and I usually also define the whitelist somewhere else for reuse (e.g. on the model or the like).
As a side note, I avoid using 'id' as a field that someone can post to for new objects, unless I really need to, to avoid confusion with the autogenerated _id field.
Also, you should really look into mongoose rather than using the straight mongodb driver, if you want to have more model-based control of your documents. Among other things, it will strip any fields off the object if they're not defined in the schema. I still use the _.pick() method when there are things that are defined in the schema, but I don't want people to change in a particular controller method.
Related
TL;DR: Is there a safe way to dynamically define a mongoose discriminator at runtime?
I have an app with a MongoDB collection where users have some control over the underlying schema.
I could add one or two fixed, required fields and just use mongoose.Mixed for the remainder that users can change, but I'd like to make use of Mongoose's validation and discriminators if I can.
So, what I've got is a second collection Grid where the users can define the shape they'd like their data to take, and in my main model Record, I've added a function to dynamically generate a discriminator from the definition in the second collection.
The code for my Record model looks like this:
const mongoose = require("mongoose")
const recordSchema = new mongoose.Schema({
fields: {
type: Array,
required: true
}
}, {
discriminatorKey: "grid"
})
const Record = mongoose.model("Record", recordSchema)
module.exports = grid => {
// Generate a mongoose-compatible schema from the grid's field definitions
const schema = grid.fields.map(field => {
if(field.type === "string") return { [field.name]: String }
if(field.type === "number") return { [field.name]: Number }
if(field.type === "checkbox") return { [field.name]: Boolean }
return { [field.name]: mongoose.Mixed }
})
return Record.discriminator(grid._id, new mongoose.Schema(schema))
}
This is inside an Express app, and I use the model in my middleware handlers something like this:
async (req, res) => {
const grid = await Grid.findById(req.params.id)
const Record = await GenerateRecordModel(grid)
const records = await Record.find({})
res.json({
...grid,
records
})
}
This works great on the first request, but after that I get an error Discriminator with name “ ” already exists.
I guess this is because only one discriminator with its name per model can exist.
I could give every discriminator a unique name whenever the function is called:
return Record.discriminator(uuidv4(), new mongoose.Schema(schema), grid._id)
But I imagine that this isn't a good idea because discriminators seem to persist beyond the lifetime of the request, so am I laying the groundwork for a memory leak?
I can see two ways forward:
COMPLICATED? Define all discriminators when the app boots up, rather than just when a HTTP request comes in, and write piles of extra logic to handle the user creating, updating or deleting the definitions over in the Grid collection.
SIMPLER? Abandon using discriminators, just use mongoose.Mixed so anything goes as far as mongoose is concerned, and write any validation myself.
Any ideas?
In Mongoose, I can use a query populate to populate additional fields after a query. I can also populate multiple paths, such as
Person.find({})
.populate('books movie', 'title pages director')
.exec()
However, this would generate a lookup on book gathering the fields for title, pages and director - and also a lookup on movie gathering the fields for title, pages and director as well. What I want is to get title and pages from books only, and director from movie. I could do something like this:
Person.find({})
.populate('books', 'title pages')
.populate('movie', 'director')
.exec()
which gives me the expected result and queries.
But is there any way to have the behavior of the second snippet using a similar "single line" syntax like the first snippet? The reason for that, is that I want to programmatically determine the arguments for the populate function and feed it in. I cannot do that for multiple populate calls.
After looking into the sourcecode of mongoose, I solved this with:
var populateQuery = [{path:'books', select:'title pages'}, {path:'movie', select:'director'}];
Person.find({})
.populate(populateQuery)
.execPopulate()
you can also do something like below:
{path:'user',select:['key1','key2']}
You achieve that by simply passing object or array of objects to populate() method.
const query = [
{
path:'books',
select:'title pages'
},
{
path:'movie',
select:'director'
}
];
const result = await Person.find().populate(query).lean();
Consider that lean() method is optional, it just returns raw json rather than mongoose object and makes code execution a little bit faster! Don't forget to make your function (callback) async!
This is how it's done based on the Mongoose JS documentation http://mongoosejs.com/docs/populate.html
Let's say you have a BookCollection schema which contains users and books
In order to perform a query and get all the BookCollections with its related users and books you would do this
models.BookCollection
.find({})
.populate('user')
.populate('books')
.lean()
.exec(function (err, bookcollection) {
if (err) return console.error(err);
try {
mongoose.connection.close();
res.render('viewbookcollection', { content: bookcollection});
} catch (e) {
console.log("errror getting bookcollection"+e);
}
//Your Schema must include path
let createdData =Person.create(dataYouWant)
await createdData.populate([{path:'books', select:'title pages'},{path:'movie', select:'director'}])
New to MongoDB and express interactions. I'm trying to query the database based on URL parameters, essentially I want to grab URL parameters then add them to an object if they exist to use when querying the database. I set up my express path and understand that the find() method accepts an object, when I hard code the key-value pair like below I get the correct data but when I set an object called filter with the same key-value pairs as the hard-coded example and uncomment filter and comment state and type then pass it to the find method I get an empty array. Whats the reason for this and what's the best way to achieve this?
app.get('/query', function (req, res) {
var filter = {
state: "florida",
type: "red"
}
db.collection('tickets').find({
//filter
state:"florida",
type:"red"
})
.toArray(function (err, documents) {
// Here is where we decide what to do with the query results
if (err)
throw err
res.send(documents)
})
});
The find method accepts a query object as the first parameter, the query object is your filter object yourself. therefore, you should do it like this:
db.collection('tickets').find(filter)
.toArray(function (err, documents) {
// Here is where we decide what to do with the query results
if (err)
throw err
res.send(documents)
})
When using Mongoose (with bluebird in my case, but using callbacks to illustrate), the following codes all return a document from the collection:
model.findOne({}, function(err, document) {
//returns a document
})
model.findOne(null, function(err, document) {
//returns a document
})
model.findOne([], function(err, document) {
//returns a document
})
I would like to know if and how I can disable this kind of behaviour, as it is becoming a liability to my code where I infer queries from data a user feeds into the system. Especially the null query returning a valid document worries me.
As of right now I check the input for being an non-empty, non-array, non-null object, but it's becoming a bit cumbersome at scale.
What would be the best way to exclude this behaviour?
Not sure if it is the best way to go about it, but right now I've settled on using a pre-hook on the model itself which checks for the _conditions property of the 'this' object (which I inferred from printing seems to hold the query object) to not be empty.
Inserting a self-defined object in the next functionality causes the Promise to reject in which the query was originally called from.
( _ is the underscore package)
//model.js
//model is a mongoose.Schema type in the following code
model.pre('findOne', function(next) {
var self = this
if (_.isEmpty(self._conditions)) {
next(mainErrors.malformedRequest)
} else {
next()
}
})
I have a node js function:
function func() {
USER.find({},function(err, users){
user = users[0];
console.log(user); // {"name":"mike", "age":15, "job":"engineer"}
user.name = "bob"; //{"name":"bob", "age":15, "job":"engineer"}
delete user.name;
console.log(user); // {"name":"mike", "age":15, "job":"engineer"} name still there??
});
}
Here USER is a mongoose data model and find is to query the mongodb. The callback provide an array of user if not err. The user data model looks like
{"name":"mike", "age":15, "job":"engineer"}.
So the callback is invoked and passed in users, I get the first user and trying to delete the "name" from user. The wired part is I can access the value correctly and modify the value. But if I 'delete user.name', this element is not deleted from json object user. Why is that?
As others have said, this is due to mongoose not giving you a plain object, but something enriched with things like save and modifiedPaths.
If you don't plan to save the user object later, you can also ask for lean document (plain js object, no mongoose stuff):
User.findOne({})
.lean()
.exec(function(err, user) {
delete user.name; // works
});
Alternatively, if you just want to fetch the user and pay it forward without some properties, you can also useselect, and maybe even combine it with lean:
User.findOne({})
.lean()
.select('email firstname')
.exec(function(err, user) {
console.log(user.name); // undefined
});
Not the best workaround, but... have you tried setting to undefined?
user.name = undefined;