So I have an application developed in NodeJS, which is using the MongoDB for database (with Mongoose). Besides the application, there is an admin panel where administrators can manage all the data being added by regular users.
In the users schema, I have the following prop:
role: {
type: String,
default: "user",
enum: ["user", "admin"]
},
My question is, what's the best/secure way to add one or two admin users?
The most reliable way is still use an ACL - access control list - strategy to manage resource permission. But for the simple apps, I just add to each of user a property named "accessScope" which gets an integer in the range of [0-9] as value, in which 0 is default - guest, 9 is supper admin.
As Dong has pointed out, implementing an Access Control List, is the answer — role based access control, extrapolating from the question.
How simple you would like to keep the design of your RBAC system, depends on the complexity of your app, and the corresponding requirements.
Here is a very simple instance of how it could be done. Given, that a
roleattribute is defined for all of the users, we could define arolesobject atop of our controller file:const roles = { 'normal': { can: [] }, 'admin': { can: ['read'] }, 'superadmin': { can: ['read', 'write'] }, } ... exports.getAdminPage = (req, res) => { const operation = 'read'; // req.user is set post authentication if ( !roles[req.user.role] || roles[req.user.role].can.indexOf(operation) === -1 ) { // early return if the access control check fails return res.status(404).end(); // or an "access denied" page } // rest of the code... ... } ...You can extend the
rolesobject to accommodate conditional access control. Imagine your post on a social network (say, Hashnode :); as the author only you have the ability to edit it, so in this situation, we can implement the ACL in the corresponding Post controller file, like so:const roles = { 'normal': { can: [ 'read', 'write', { name: 'edit', when: (params) => params.reqUserID === params.authorID, } ] }, ... } ... exports.sendDraft = (req, res) => { const operation = 'edit' let userCanObject = ''; roles[req.user.role] && roles[req.user.role].can.forEach( can => { if ( (typeof can === 'string' && can === operation) || (typeof can === 'object' && can.name === operation) ) { userCanObject = op; } } ); Post .findById(req.postID) .select('author') .exec((err, post) => { ... const userCanDoOp = ( typeof userCanObject === 'string' && userCanObject === operation ) || ( typeof userCanObject === 'object' && userCanObject.name === operation && userCanObject.when({ reqUserID: req.user.id, authorID: post.author }) ); // early return if (!userCanDoOp) { return res.status(404).end(); } ... } ... }Of course, as your app scales, the recommended way is to move the RBAC code into its own module. I highly recommend the following article for a complete overview on implementing access control, the correct way™. It is an awesome read!
Hope this helps! :)