How to protect fields from mass assignment in Mongoose? - node.js

A Mongoose model, Thing, has two fields, only one of which (safe) should be settable through mass assignment:
var db = require('mongoose');
var schema = new db.Schema({
safe: { type: String }, // settable through mass assignment
unsafe: { type: String } // not settable through mass assignment
});
db.model('Thing', schema);
A controller sets up Thing by passing parameters:
exports.create = function(req, res) {
var thing = new Thing(req.body);
// more...
};
An attacker could try to set thing.unsafe by making a JSON POST request in which unsafe is set. This should be prevented.
It would be great if something like the Rails attr_accessible functionality were available for Mongoose. I did find mongoose-mass-assign, but this is nothing like what I'm looking for. For one thing, mongoose-mass-assign apparently requires the use of a new API (two massAssign functions). I want mass assignment protection for any native Mongoose model function to which params hashes are passed, for example, the Thing constructor and the Thing.create function.
How can I get mass assignment protection for Mongoose models? If not available, how do Mongoose users currently protect against this vulnerability?

Facepalm:
var thing = new Thing(req.body);
Slightly saner:
var okFields = {};
okFields.safe = req.body.safe
var thing = new Thing(okFields);
//Also helpful for longer whitelists from underscore: _.pick(req.body, "safe");
//Also feel free to add some, y'know, data validation either here or in mongoose
Just don't do that. Rails has taught you a terrible antipattern. But to answer your question, AFAIK mongoose nor mongodb has no mechanism to enforce anything analogous to rails's attr_accessible or any concept of tainted variables.

Related

Using a Mongoose model defined in another file returns Undefined

It seems pretty clear that defining a model in one file but using it in another is common practice. I don't know why I'm having so much trouble getting it to work. I spent the morning rewriting my simple MongoDB app to follow what I thought was a dead simple example. I'm just structuring it like the top answer with the added convenience that the file where I define the Schema and Model and access the Model are in the same folder (because the project is so small and I'm just learning MongoDB). I think part of the problem as I tried researching solutions is that other examples really complex to follow. The DB connects as expected and I can work in the ManageDB.js file fine, but of course want to keep the project organized, even at my small scale. (The server.js file in the directory above connects before this code executes.)
What other things can I try to troubleshoot this MongoDB application?
// src/ManageDB.js
const mongoose = require('mongoose');
const devTweetRecordsModel = new mongoose.Schema({
time: Date,
text: String,
source: String,
positive: Number,
negative: Number,
});
var tweetdb = mongoose.model('DevTweetDB', devTweetRecordsModel);
module.exports = {
tweetdb: tweetdb
};
// src/TwitterAPI.js
const mongoose = require('mongoose');
var tweetdb = require('../src/ManageDB').tweetdb;
console.log(tweetdb); // Returns undefined
After defining a model-schema in mongoose you should assign it as mongoose.model
as you can see in the documentation.
https://mongoosejs.com/docs/models.html
const schema = new mongoose.Schema({ name: 'string', size: 'string' });
const Tank = mongoose.model('Tank', schema);
When you call mongoose.model() on a schema, Mongoose compiles a model for you.
and then you can use it by requiring it in another file.
const Model= require('./../models/modelName');
I solved it a way I hadn't seen suggested anywhere so I will post here for completeness. The solution was very sensitivity to how I exported it and brought it into the other JS file.
// Model.js
module.exports = mongoose.model('DevTweetDB', devTweetRecordsModel);
// TwitterAPI.js
var tweetdb = require('../models/tweetdb');
console.log(tweetdb);
// Returns the object as expected:
// Model { DevTweetDB }

Why mongoose doesn't update document? [duplicate]

In previous versions of Mongoose (for node.js) there was an option to use it without defining a schema
var collection = mongoose.noSchema(db, "User");
But in the current version the "noSchema" function has been removed. My schemas are likely to change often and really don't fit in with a defined schema so is there a new way to use schema-less models in mongoose?
I think this is what are you looking for Mongoose Strict
option: strict
The strict option, (enabled by default), ensures that values added to our model instance that were not specified in our schema do not get saved to the db.
Note: Do not set to false unless you have good reason.
var thingSchema = new Schema({..}, { strict: false });
var Thing = mongoose.model('Thing', thingSchema);
var thing = new Thing({ iAmNotInTheSchema: true });
thing.save() // iAmNotInTheSchema is now saved to the db!!
Actually "Mixed" (Schema.Types.Mixed) mode appears to do exactly that in Mongoose...
it accepts a schema-less, freeform JS object - so whatever you can throw at it. It seems you have to trigger saves on that object manually afterwards, but it seems like a fair tradeoff.
Mixed
An "anything goes" SchemaType, its flexibility comes at a trade-off of
it being harder to maintain. Mixed is available either through
Schema.Types.Mixed or by passing an empty object literal. The
following are equivalent:
var Any = new Schema({ any: {} });
var Any = new Schema({ any: Schema.Types.Mixed });
Since it is a schema-less type, you can change the value to anything
else you like, but Mongoose loses the ability to auto detect and save
those changes. To "tell" Mongoose that the value of a Mixed type has
changed, call the .markModified(path) method of the document passing
the path to the Mixed type you just changed.
person.anything = { x: [3, 4, { y: "changed" }] };
person.markModified('anything');
person.save(); // anything will now get saved
Mongoose Schema Types
Hey Chris, take a look at Mongous. I was having the same issue with mongoose, as my Schemas change extremely frequently right now in development. Mongous allowed me to have the simplicity of Mongoose, while being able to loosely define and change my 'schemas'. I chose to simply build out standard JavaScript objects and store them in the database like so
function User(user){
this.name = user.name
, this.age = user.age
}
app.post('save/user', function(req,res,next){
var u = new User(req.body)
db('mydb.users').save(u)
res.send(200)
// that's it! You've saved a user
});
Far more simple than Mongoose, although I do believe you miss out on some cool middleware stuff like "pre". I didn't need any of that though. Hope this helps!!!
Here is the details description: [https://www.meanstack.site/2020/01/save-data-to-mongodb-without-defining.html][1]
const express = require('express')()
const mongoose = require('mongoose')
const bodyParser = require('body-parser')
const Schema = mongoose.Schema
express.post('/', async (req, res) => {
// strict false will allow you to save document which is coming from the req.body
const testCollectionSchema = new Schema({}, { strict: false })
const TestCollection = mongoose.model('test_collection', testCollectionSchema)
let body = req.body
const testCollectionData = new TestCollection(body)
await testCollectionData.save()
return res.send({
"msg": "Data Saved Successfully"
})
})
[1]: https://www.meanstack.site/2020/01/save-data-to-mongodb-without-defining.html
Note: The { strict: false } parameter will work for both create and update.
Its not possible anymore.
You can use Mongoose with the collections that have schema and the node driver or another mongo module for those schemaless ones.
https://groups.google.com/forum/#!msg/mongoose-orm/Bj9KTjI0NAQ/qSojYmoDwDYJ

Mongoose some field are not inserted [duplicate]

In previous versions of Mongoose (for node.js) there was an option to use it without defining a schema
var collection = mongoose.noSchema(db, "User");
But in the current version the "noSchema" function has been removed. My schemas are likely to change often and really don't fit in with a defined schema so is there a new way to use schema-less models in mongoose?
I think this is what are you looking for Mongoose Strict
option: strict
The strict option, (enabled by default), ensures that values added to our model instance that were not specified in our schema do not get saved to the db.
Note: Do not set to false unless you have good reason.
var thingSchema = new Schema({..}, { strict: false });
var Thing = mongoose.model('Thing', thingSchema);
var thing = new Thing({ iAmNotInTheSchema: true });
thing.save() // iAmNotInTheSchema is now saved to the db!!
Actually "Mixed" (Schema.Types.Mixed) mode appears to do exactly that in Mongoose...
it accepts a schema-less, freeform JS object - so whatever you can throw at it. It seems you have to trigger saves on that object manually afterwards, but it seems like a fair tradeoff.
Mixed
An "anything goes" SchemaType, its flexibility comes at a trade-off of
it being harder to maintain. Mixed is available either through
Schema.Types.Mixed or by passing an empty object literal. The
following are equivalent:
var Any = new Schema({ any: {} });
var Any = new Schema({ any: Schema.Types.Mixed });
Since it is a schema-less type, you can change the value to anything
else you like, but Mongoose loses the ability to auto detect and save
those changes. To "tell" Mongoose that the value of a Mixed type has
changed, call the .markModified(path) method of the document passing
the path to the Mixed type you just changed.
person.anything = { x: [3, 4, { y: "changed" }] };
person.markModified('anything');
person.save(); // anything will now get saved
Mongoose Schema Types
Hey Chris, take a look at Mongous. I was having the same issue with mongoose, as my Schemas change extremely frequently right now in development. Mongous allowed me to have the simplicity of Mongoose, while being able to loosely define and change my 'schemas'. I chose to simply build out standard JavaScript objects and store them in the database like so
function User(user){
this.name = user.name
, this.age = user.age
}
app.post('save/user', function(req,res,next){
var u = new User(req.body)
db('mydb.users').save(u)
res.send(200)
// that's it! You've saved a user
});
Far more simple than Mongoose, although I do believe you miss out on some cool middleware stuff like "pre". I didn't need any of that though. Hope this helps!!!
Here is the details description: [https://www.meanstack.site/2020/01/save-data-to-mongodb-without-defining.html][1]
const express = require('express')()
const mongoose = require('mongoose')
const bodyParser = require('body-parser')
const Schema = mongoose.Schema
express.post('/', async (req, res) => {
// strict false will allow you to save document which is coming from the req.body
const testCollectionSchema = new Schema({}, { strict: false })
const TestCollection = mongoose.model('test_collection', testCollectionSchema)
let body = req.body
const testCollectionData = new TestCollection(body)
await testCollectionData.save()
return res.send({
"msg": "Data Saved Successfully"
})
})
[1]: https://www.meanstack.site/2020/01/save-data-to-mongodb-without-defining.html
Note: The { strict: false } parameter will work for both create and update.
Its not possible anymore.
You can use Mongoose with the collections that have schema and the node driver or another mongo module for those schemaless ones.
https://groups.google.com/forum/#!msg/mongoose-orm/Bj9KTjI0NAQ/qSojYmoDwDYJ

Mongoose find returns empty array (works fine for other collections)

I have been writing a restful api in nodejs fairly succesfully for the most part. There are two collections in the MongoDB that I am accessing that return empty strings and happen to be the only collections that contain capital letters in their names. When I use MongoClient, I am able to access these collections just fine, so I know that it is not an out of date mongodb driver.
one example is when I try to access a collection called bulkBuds
//bulkBuds model
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var BulkBudsSchema = new Schema({
sourceLicense: String,
quantity: Number,
strainName: String,
priceProfile: String
});
mongoose.model('bulkBuds', BulkBudsSchema);
The controller has a bit of excess logic in the query, but a simple find returns an empty string as well.
//bulkBuds controller
var express = require('express'),
router = express.Router(),
mongoose = require('mongoose'),
BulkBuds = mongoose.model('bulkBuds'),
Friends = mongoose.model('Api'),
config = require('../../config/config'),
jwt = require('express-jwt');
module.exports = function (app) {
app.use('/api/bulkBuds/', router);
};
router.get('/:license', jwt({secret: config.secret}), function (req, res, next) {
if(!req.user.friend){
res.status(401);
}
Friends.findById(req.user.id, function(err, friend){
if(err) throw err;
if(!friend) res.send("friend does not exist");
if(req.user.username != friend.username) res.send("invalid user");
console.log(req.params.license);
console.log(BulkBuds.find({}));
BulkBuds.find({'storeLicense': req.params.license, 'availableForSale': true},
"sourceLicense quantity strainName priceProfile", function (err, bulkBuds) {
if (err) return next(err);
console.log(bulkBuds);
res.send(bulkBuds);
});
})
});
Any suggestions would be greatly appreciated, thanks.
Very difficult to answer without being able to test against your database. But I would try a few things.
refactor {'storeLicense': req.params.license, 'availableForSale': true} to create the object outside of the query, and then console log that object prior to passing it to the query. That will ensure everything is as you expect.
Remove "sourceLicense quantity strainName priceProfile" as the second argument to BulkBuds.find, and replace with an empty object. I usually pass an object as the second param with the following syntax {_id:1,quantity:0} to modify the projection. Your syntax may work, but just in case I would try running the query without to see if that yields any results.
Confirm quantity in your db is indeed a Number and not a String. I know mongoose won't let you insert records that don't validate, not sure about querying. Most likely not the issue, but doesn't hurt to verify.
After creating the Bulkbirds schema try this:
mongoose.model('bulkBuds', BulkBudsSchema, 'bulkBuds');
Another long shot, but perhaps it has something to do with mongoose pluralizing the collection names. Using the above syntax will ensure it's querying the bulkBuds collection.
Once again, difficult to pinpoint without being able to test, but hopefully those ideas help.

Mongoose model testing require models

I have a problem testing my mongoose models
I have a structure like
app
models
Address
User
Organization
test
Both models User and Organization need to know the model Address. My models are structured like:
module.exports = function (mongoose, config) {
var organizationSchema = new mongoose.Schema({
name : {
type : String
},
addresses : {
type : [mongoose.model('Address')]
}
});
var Organization = mongoose.model('Organization', organizationSchema);
return Organization;
};
In my normal app i require Address before requiring User and Organization and everything is fine. I now wrote tests for User and Organization. In order to have the Address model registered i call require('../models/Address.js') This works fine if i run one test. But if i run all tests in a batch i get an error because i tried to register Address twice.
OverwriteModelError: Cannot overwrite Address model once compiled.
How do i solve this problem?
The problem is that you cant set mongoose model twice. The easiest way to solve your problem is to take advantage of node.js require function.
Node.js caches all calls to require to prevent your model from initializing twice. But you wrapping your models with functions. Unwrapping them will solve your problem:
var mongoose = require('mongoose');
var config = require('./config');
var organizationSchema = new mongoose.Schema({
name : {
type : String
},
addresses : {
type : [mongoose.model('Address')]
}
});
module.exports = mongoose.model('Organization', organizationSchema);
Alternative solution is to make sure that each model initialized only once. For example, you can initialize all you modules before running your tests:
Address = require('../models/Address.js');
User = require('../models/User.js');
Organization = require('../models/Organization.js');
// run your tests using Address, User and Organization
Or you can add try catch statement to your models to handle this special case:
module.exports = function (mongoose, config) {
var organizationSchema = new mongoose.Schema({
name : {
type : String
},
addresses : {
type : [mongoose.model('Address')]
}
});
try {
mongoose.model('Organization', organizationSchema);
} catch (error) {}
return mongoose.model('Organization');
};
Update: In our project we have /models/index.js file to handle everything. First, it calls mongoose.connect to establish connection. Then it requires every model in models directory and creates a dictionary of it. So, when we need some model (e.g. user) we requires it by calling require('/models').user.
Best solution (IMO):
try {
mongoose.model('config')
} catch (_) {
mongoose.model('config', schema)
}
This question already has an answer, but for a unique way to accomplish this check out https://github.com/fbeshears/register_models. This sample project uses a register_models.js that includes all models from an array of file names. It works really well and you end up with all your models pretty much wherever you need them. Keep in mind node.js's cache will cache objects in your project while it's running.
I use try/catch to tackle this problem and it works ok. But i think this not the best way of doing this.
try{
var blog = mongoose.model('blog', Article);
} catch (error) {}
I fixed this by patching r.js and making sure that all my modules used localRequire calls.
Checkout the thread here
https://github.com/jrburke/requirejs/issues/726

Resources