Grant user roles in MongoDB via nodejs - node.js

I'm trying to write a code in Node.JS that grant roles to users in MongoDB.
I know a way via the CLI:
db.grantRolesToUser( "<username>", [ <roles> ], { <writeConcern> } )
How can i do it through Node.JS?
Thanks

I don't know that it's the only way, but the only thing I can see in the docs is to grant a role when you add a user.
var MongoClient = require('mongodb').MongoClient,
test = require('assert');
MongoClient.connect('mongodb://localhost:27017/test', function(err, db) {
// Use the admin database for the operation
var adminDb = db.admin();
// Add the new user to the admin database
adminDb.addUser('admin11', 'admin11', {roles : ['blah']}, function(err, result) {
// Authenticate using the newly added user
adminDb.authenticate('admin11', 'admin11', function(err, result) {
test.ok(result);
adminDb.removeUser('admin11', function(err, result) {
test.ok(result);
db.close();
});
});
});
});

Yes, it's confusing, the mongo driver only seems to implement the addUser and removeUser functions. Nevertheless, you can use the 'command' function of the mongo driver to access the functions available in the mongo shell. This works for me on mongo 3.4:
...
const db = client.db('admin').admin();
const theRoles = [{role:'readWrite', db: 'someDB'}]
await db.command({grantRolesToUser: 'theUsername', roles: theRoles});
...
The documentation of the command function is rather opaque, and I had to use trial and error to find the proper syntax.

Related

Node Express Mongodb connection syntax and error handling

I was having an issue in my app that was simply from an oversight on the database name that I provided to my mongodb connection. While looking around I ran across the examples here:
https://wesleytsai.io/2015/08/02/mongodb-connection-pooling-in-nodejs/
So my question is why don't I get any kind of error thrown if I am providing a database that does not exist to the mongodb connection? I just need help interpreting/understanding the syntax below. On this line:
MongoClient.connect(mongoUrl, function(err, database) {
if( err ) throw err;
.
.
.
shouldn't an error be thrown since mongo can't connect if the database doesn't exist?
What I'm trying to do is setup some kind of error handling that can flag the fact the the database doesn't exist and likewise with the collection if that too does not exist ('expenses' should be 'test').
var mongodb = require('mongodb');
var MongoClient = mongodb.MongoClient;
var mongoUrl = 'mongodb://127.0.0.1:27017/expenses';
var db;
exports.connect = function(callback) {
MongoClient.connect(mongoUrl, function(err, database) {
if( err ) throw err;
db = database;
callback();
})
}
In mongodb, if you are connected to the datbase and then you provide a schema to a collection that you defined, then irrespective of the fact that whether that collection is there or not, mongodb creates a collection for you.
For example if you have the following code,
module.exports = mongoose.model('User', schema);
a collection named users is created in the database. So even if you dont have a collection, mongodb will create one for you.
If you want to check whether a database exists then follow like below:
var mongoose = require('mongoose')
, Admin = mongoose.mongo.Admin;
/// create a connection to ypur database
var connection = mongoose.createConnection(
'mongodb://user:password#localhost:port/your_database');
connection.on('open', function() {
// connection successful
new Admin(connection.db).listDatabases(function(err, result) {
console.log('listDatabases successful');
// the database lists are stored in result.databases
var allDatabases = result.databases;
// allDatabases contains the record of all the databases
// crosscheck this list with the database you want
});
});

Why is this query working in the mongo shell but not the node mongo driver?

I'm successfully running this query in the shell:
db.hourlydatas.find({'timeseries':ObjectId('1234')})
Trying to translate it to the mongo driver:
MongoClient.connect(config.db, function(err, db) {
// Use the admin database for the operation
var collection = db.collection('hourlydatas');
collection.find({'timeseries':'1234'}).toArray(function(err, docs) {
// assert.equal(err, null);
console.log("Found the following records");
console.log(docs);
// callback(docs);
});
});
This does not return any documents, I assume because I'm not converting the string to an objectID. Is this possible in the driver?
Try this
var ObjectId = require('mongodb').ObjectID;
var collection = db.collection('hourlydatas');
collection.find({'timeseries':ObjectId('1234')}).toArray(function(err,docs) {...}
It should work, are you sure you are connecting to the same DB? check if both connections are to test or prod... I once wasted a lot of time on this just to find at the end that my mongo-shell was connected to prod while node was connecting to test.

Is it necessary to open MongoDB connection every time I want to work with the DB?

In the example I am working with is this code:
//lets require/import the mongodb native drivers.
var mongodb = require('mongodb');
//We need to work with "MongoClient" interface in order to connect to a mongodb server.
var MongoClient = mongodb.MongoClient;
// Connection URL. This is where your mongodb server is running.
var url = 'mongodb://localhost:27017/my_database_name';
// Use connect method to connect to the Server
MongoClient.connect(url, function (err, db) {
if (err) {
console.log('Unable to connect to the mongoDB server. Error:', err);
} else {
//HURRAY!! We are connected. :)
console.log('Connection established to', url);
// Get the documents collection
var collection = db.collection('users');
//Create some users
var user1 = {name: 'modulus admin', age: 42, roles: ['admin', 'moderator', 'user']};
var user2 = {name: 'modulus user', age: 22, roles: ['user']};
var user3 = {name: 'modulus super admin', age: 92, roles: ['super-admin', 'admin', 'moderator', 'user']};
// Insert some users
collection.insert([user1, user2, user3], function (err, result) {
if (err) {
console.log(err);
} else {
console.log('Inserted %d documents into the "users" collection. The documents inserted with "_id" are:', result.length, result);
}
//Close connection
db.close();
});
}
});
As you may see, he is doing an operation in the connect function. I would like to keep it modular and separate the connection from DB operations.
My suggestion would be to make a singleton on db variable and reuse that one. At least that's what I would do in Java to which I am used to.
However, I am not sure as in the example he hasn't suggested anything like that.
I would recommend against maintaining one connection if you want any kind of scalability.
There are a number of options for connection pooling, etc, but most folks who spend any time at all with Node and MongoDB end up moving to Mongoose at some point.
In addition to adding a nice schema layer, it offers connection abstraction so that you can default to a shared connection by calling mongoose.connect(), or you can create multiple connections or participate in connection pooling by calling mongoose.createConnection(). In both cases, you call it without a callback, and the mongoose machinery will defer subsequent calls to the module until after the connection is established, so that your code doesn't have to care.
Something like your use case might look like so:
// in your app.js or server.js file
var mongoose = require('mongoose');
mongoose.connect(config.db.url); // assuming you have some module that handles config variables
Then in ./models/user.js
const mongoose = require('mongoose'),
Schema = mongoose.Schema;
const UserSchema = new Schema({
name: String,
age: Number,
roles: [String]
});
mongoose.model('User',UserSchema);
finally, in lets say a seed function to create your initial batch of users:
const mongoose = require('mongoose'),
User = mongoose.model('User');
// create some users
var user1 = new User({name: 'modulus admin', age: 42, roles: ['admin', 'moderator', 'user']});
var user2 = new User({name: 'modulus user', age: 22, roles: ['user']});
user1.save(console.log);
user2.save(console.log);
I believe maintaining a single connection is the best as mentioned in another thread:
The primary comitter in node-mongodb-native says
You open do MongoClient.connect once when your app boots up and reuse the db object. It's not a singleton connection pool each .connect creates a new connection pool. So open it once an[d] reuse across all requests.
Say on server start initiate the mongo connection.
Server.js:
...
var db = require('./db');//require db.js
db.openMongoConnection(function(error)
{
if(error)
{
console.log(error);
console.log("cannot make the connection with database");
}
else
{
server.listen(7400);//say ur server listening on 7000 port
}
}
db.js
var db1;
var MongoClient = require('mongodb').MongoClient;
exports.openMongoConnection = function(callback)
{
MongoClient.connect(<YourUrl1>,function(err,dbInstance)
{
if(err)
{
callback(err);
}
else
{
db1 = dbInstance;
callback(null);
}
});
};
exports.getCollection = function(collectionName, callback){
dbInstance.collection(collectionName, function(err, collectionInstance){
if(err)
{
callback(err);
}
else
{
callback(null, collectionInstance)
}
});
}
Then you can call the getCollection to use at anytime by requiring dbInsance

Telling Mongoose which database to use - in a node.js Express application

I have an Node.js Express web app running.
I have two database connections, the system_db is created with "Mongoose.connect", the user_db is created with "Mongoose.createConnection". This seems to make them separate and have different connection pools. Although this could be the crux of the problem.
I have code dealing with with a Mongoose model
in the model file I have:
var authSchema = mongoose.Schema({
teamName: String,
league: String,
players: []
});
var Team = module.exports = mongoose.model('teams',authSchema);
in my main file I have two connections:
one is a system database
connection_uri = "mongodb://localhost:27017/";
system_db = mongoose.connect(connection_uri.concat("sparks"), {
native_parser : true
}, function(err){
if (err) throw err;
});
the other is a user database
user_db = mongoose.createConnection(connection_uri.concat(user_id));
then I have this piece of code in my main file which finds a Team object based off id:
app.param('team_id', function(req, res, next, team_id) {
Team.findOne(team_id, function(err, team) {
if (err) {
return next(err);
}
if (!team) {
throw new Error("no team matched");
}
req.team = team;
next();
});
});
the problem is that the app.param function above is not finding any matches for teams, even though they exist in a collection in the user_db database. this means, that I am thinking my model is pointing to the wrong database somehow? Somehow it must be pointing to this system_db instead of the user_db. Could this be right? How would I fix that?
The below method opens a connection to the system_db and is binded to the mongoose object,.i.e the current mongoose object is modified. The return type is not a Connection.
system_db = mongoose.connect(connection_uri.concat("sparks")...
Now when you again do:
user_db = mongoose.createConnection(connection_uri.concat(user_id));
This creates a new Connection to the user database and returns a connection, but does not modify the mongoose instance. The mongoose instance is still binded to the system_db database connection.
Since Team model is obtained from the same mongoose instance,
var Team = mongoose.model('teams',authSchema);
whatever operation is done on this model effectively occurs in the connection that the mongoose instance holds, and that is of the system_db database.
So you need to obtain the model from the user_db connection :
var user_db = mongoose.createConnection(..);
// retrieve the Team model
var Team= user_db.model('teams'); // not from mongoose instance.
use createConnection wherever you want to obtain a connection.

Mongoose migrate

Anyone got a migrate module that they use to migrate mongodb data with the mongoose plugin?
I am currently using the 'migrate' module and it works great except for the fact that I need to create/destroy my connection in each up/down.
I.E.
// Setup mongoose
var mongoose = require('mongoose')
, Role = require('../models/role')
, User = require('../models/user');
exports.up = function(next) {
// get a brand new connection for this patch.
mongoose.connect('mongodb://localhost/sagedb');
var adminUser = {
username: 'admin',
password: 'admin'
};
User.createUser(adminUser, function(err, user) {
if (err) {
mongoose.disconnect(); // Make sure to close connection
return next(err);
}
mongoose.disconnect(next); // Make sure to close connection
});
};
exports.down = function(next) {
mongoose.connect('mongodb://localhost/sagedb'); // new connection for down
User.getUserByUsername('admin', function(err, user) {
if (err) {
mongoose.disconnect(function() { // make sure to close connection
return next(err);
});
}
if (!user) {
mongoose.disconnect(); // make sure to close connection
return next();
}
User.deleteUser(user, function(err, user) {
console.log('deleted user');
mongoose.disconnect(next); // make sure to close connection
});
});
};
Probably a much better way to do this. Wondering if the only option is to create my own module that starts the connection once and closes it when all patches are complete.
I have seen mongoose-migrate which tracks migration in database collection. Not really specific to mongoose IMHO, I would rather still use the .migrate file but only have to open the connection once.
The reason of the issue is that you have connection "connected" each time, on every ,migration
That is why you have to disconnect.
The same situation if you replace connect with mongoose.createConnection. you will need to close it.
How to solve?
move
var mongoose = require('mongoose')
, Role = require('../models/role')
, User = require('../models/user');
into module like db
var mongoose = require('mongoose')
, Role = require('../models/role')
, User = require('../models/user');
module.exports = mongoose
and just require it
var mongoose = require('./db')
So you will have:
Single connection
All models loaded in one place
Clean code in
migrations
You can also try my migrate-mongoose migration framework which provides the mongoose connection right out of the box.
in your up or down function you can just access your models like this
this('user').findOne({ name: 'Sergey' });
It also persists your migrations to the database instead of the file system.
You also have east migration framework that is very powerful and it also has mongoDB adaptors:
https://github.com/okv/east
Then you will crate migration with command:
east create my_migration_name
And then your migration scripts will look like this:
exports.migrate = function(client, done) {
var db = client.db;
db.........
done();
};
exports.rollback = function(client, done) {
var db = client.db;
db.........
done();
};

Resources