User Roles and Permissions Express.js / Mongoose

I am creating a fairly simple site with Node, Express and Mongoose. The site must have user roles and permissions. My thoughts are that I will check permissions based on user interaction with the database.

Is there a way in the mongoose to determine the type of CRUD operation currently being performed, perhaps by the user?

+7
mongodb mongoose express
source share
5 answers

I have found a solution. It would be great to hear people's opinions about this.

I have a permissions configuration profile that defines each role and their permissions.

Permission Configuration Object

roles.admin = { id: "admin", name: "Admin", description: "", resource : [ { id : 'blog', permissions: ['create', 'read', 'update', 'delete'] }, { id : 'user', permissions: ['create', 'read', 'update', 'delete'] }, { id : 'journal', permissions: ['create', 'read', 'update', 'delete'] }, ] }; roles.editor = { id: "editor", name: "Editor", description: "", resource : [ { id : 'blog', permissions: ['create', 'read', 'update', 'delete'] }, { id : 'user', permissions: ['read'] }, { id : 'journal', permissions: ['create', 'read', 'update'] }, ] }; 

Middleware function

 var roles = require('./config'); var permissions = (function () { var getRoles = function (role) { var rolesArr = []; if (typeof role === 'object' && Array.isArray(role)) { // Returns selected roles for (var i = 0, len = role.length; i < len; i++) { rolesArr.push(roles[role[i]]); }; return rolesArr; } else if (typeof role === 'string' || !role) { // Returns all roles if (!role) { for (var role in roles) { rolesArr.push(roles[role]); }; } // Returns single role rolesArr.push(roles[role]); return rolesArr; } }, check = function (action, resource, loginRequired) { return function(req, res, next) { var isAuth = req.isAuthenticated(); // If user is required to be logged in & isn't if (loginRequired && !isAuth) { return next(new Error("You must be logged in to view this area")); } if (isAuth || !loginRequired) { var authRole = isAuth ? req.user.role : 'user', role = get(authRole), hasPermission = false; (function () { for (var i = 0, len = role[0].resource.length; i < len; i++){ if (role[0].resource[i].id === resource && role[0].resource[i].permissions.indexOf(action) !== -1) { hasPermission = true; return; } }; })(); if (hasPermission) { next(); } else { return next(new Error("You are trying to " + action + " a " + resource + " and do not have the correct permissions.")); } } } } return { get : function (role) { var roles = getRoles(role); return roles; }, check : function (action, resource, loginRequired) { return check(action, resource, loginRequired); } } })(); module.exports = permissions; 

Then I created a middleware function, when the check method is called, it gets the user role from the req object (req.user.role). He then looks at the parameters passed to the middleware and cross-references them in the permissions configuration settings.

Route with middlware

 app.get('/journal', `**permissions.check('read', 'journal')**`, function (req, res) { // do stuff }; 
+6
source share

This is my implementation. The code can be reused for the client and server . I use it for express / angular website

  • Reduce duplicate code, improve consistency between client / server
  • Bonus benefit: on the client adapter, we can simply return true to provide maximum access, to check the reliability of the server (since hackers can easily overcome the restriction on the client side)

in the app /both/both.js

 var accessList = { //note: same name as controller function name assignEditor: 'assignEditor' ,adminPage: 'adminPage' ,editorPage: 'editorPage' ,profilePage: 'profilePage' ,createArticle: 'createArticle' ,updateArticle: 'updateArticle' ,deleteArticle: 'deleteArticle' ,undeleteArticle: 'undeleteArticle' ,banArticle: 'banArticle' ,unbanArticle: 'unbanArticle' ,createComment: 'createComment' ,updateComment: 'updateComment' ,deleteComment: 'deleteComment' ,undeleteComment: 'undeleteComment' ,banComment: 'banComment' ,unbanComment: 'unbanComment' ,updateProfile: 'updateProfile' } exports.accessList = accessList var resourceList = { //Note: same name as req.resource name profile: 'profile' ,article: 'article' ,comment: 'comment' } exports.resourceList = resourceList var roleList = { admin: 'admin' ,editor: 'editor' ,entityCreator: 'entityCreator' ,profileOwner: 'profileOwner' //creator or profile owner ,normal: 'normal' //normal user, signed in ,visitor: 'visitor' //not signed in, not used, open pages are uncontrolled } var permissionList = {} permissionList[accessList.assignEditor] = [roleList.admin] permissionList[accessList.adminPage] = [roleList.admin] permissionList[accessList.editorPage] = [roleList.admin, roleList.editor] permissionList[accessList.profilePage] = [roleList.admin, roleList.editor, roleList.normal] permissionList[accessList.createArticle] = [roleList.admin, roleList.editor, roleList.normal] permissionList[accessList.updateArticle] = [roleList.admin, roleList.editor, roleList.entityCreator] permissionList[accessList.deleteArticle] = [roleList.admin, roleList.editor, roleList.entityCreator] permissionList[accessList.undeleteArticle] = [roleList.admin, roleList.editor, roleList.entityCreator] permissionList[accessList.banArticle] = [roleList.admin, roleList.editor] permissionList[accessList.unbanArticle] = [roleList.admin, roleList.editor] permissionList[accessList.createComment] = [roleList.admin, roleList.editor, roleList.normal] permissionList[accessList.updateComment] = [roleList.admin, roleList.editor, roleList.entityCreator] permissionList[accessList.deleteComment] = [roleList.admin, roleList.editor, roleList.entityCreator] permissionList[accessList.undeleteComment] = [roleList.admin, roleList.editor, roleList.entityCreator] permissionList[accessList.banComment] = [roleList.admin, roleList.editor] permissionList[accessList.unbanComment] = [roleList.admin, roleList.editor] permissionList[accessList.updateProfile] = [roleList.admin, roleList.profileOwner] var getRoles = function(access, resource, isAuthenticated, entity, user) { var roles = [roleList.visitor] if (isAuthenticated) { roles = [roleList.normal] if (user.username === 'admin') roles = [roleList.admin] else if (user.type === 'editor') roles = [roleList.editor] if (resource) { if (resource === resourceList.profile) { //Note: on server _id is a object, client _id is string, which does not have equals method if (entity && entity._id.toString() === user._id.toString()) roles.push(roleList.profileOwner) } else if (resource === resourceList.article) { if (entity && entity.statusMeta.createdBy._id.toString() === user._id.toString()) roles.push(roleList.entityCreator) } else if (resource === resourceList.comment) { if (entity && entity.statusMeta.createdBy._id.toString() === user._id.toString()) roles.push(roleList.entityCreator) } } } return roles } exports.havePermission = function(access, resource, isAuthenticated, entity, user) { var roles = getRoles(access, resource, isAuthenticated, entity, user) //Note: we can implement black list here as well, like IP Ban if (!permissionList[access]) return true for (var i = 0; i < roles.length; i++) { var role = roles[i] if (permissionList[access].indexOf(role) !== -1) return true } return false } 

Then on app / server / helper.js (act like an adapter)

 var both = require(dir.both + '/both.js') exports.accessList = both.accessList exports.resourceList = both.resourceList exports.havePermission = function(access, resource, req) { return both.havePermission(access, resource, req.isAuthenticated(), req[resource], req.user) } //todo: use this function in other places exports.getPermissionError = function(message) { var err = new Error(message || 'you do not have the permission') err.status = 403 return err } exports.getAuthenticationError = function(message) { var err = new Error(message || 'please sign in') err.status = 401 return err } exports.requiresPermission = function(access, resource) { return function(req, res, next) { if (exports.havePermission(access, resource, req)) return next() else { if (!req.isAuthenticated()) return next(exports.getAuthenticationError()) else return next(exports.getPermissionError()) } } } 

on app / client / helper.js also act as an adapter.

 exports.accessList = both.accessList exports.resourceList = both.resourceList exports.havePermission = function(access, resource, userService, entity) { //Note: In debugging, we can grant client helper all access, and test robustness of server return both.havePermission(access, resource, userService.isAuthenticated(), entity, userService.user) } 
+3
source share

I was personally inspired by a ghost. There is perms in my configuration, and permissions.js exports the canThis function, which accepts the currently logged in user. Here is the whole project

Part of my configuration file

 "user_groups": { "admin": { "full_name": "Administrators", "description": "Adminsitators.", "allowedActions": "all" }, "modo": { "full_name": "Moderators", "description": "Moderators.", "allowedActions": ["mod:*", "comment:*", "user:delete browse add banish edit"] }, "user": { "full_name": "User", "description": "User.", "allowedActions": ["mod:browse add star", "comment:browse add", "user:browse"] }, "guest": { "full_name": "Guest", "description": "Guest.", "allowedActions": ["mod:browse", "comment:browse", "user:browse add"] } }, mongoose = require("mongoose") ### This utility function determine whether an user can do this or this using the permissions. eg "mod" "delete" @param userId the id of the user @param object the current object name ("mod", "user"...) @param action to be executed on the object (delete, edit, browse...) @param owner the optional owner id of the object to be "actionned" ### # **Important this is a promise but to make a lighter code I removed it** exports.canThis = (userId, object, action, ownerId, callback) -> User = mongoose.model("User") if typeof ownerId is "function" callback = ownerId ownerId = undefined if userId is "" return process(undefined, object, action, ownerId, callback) User.findById(userId, (err, user) -> if err then return callback err process(user, object, action, ownerId, callback) ) process = (user, object, action, ownerId, callback) -> if user then role = user.role or "user" group = config.user_groups[role or "guest"] if not group then return callback(new Error "No suitable group") # Parses the perms actions = group.allowedActions for objAction in actions when objAction.indexOf object is 0 # We get all the allowed actions for the object and group act = objAction.split(":")[1] obj = objAction.split(":")[0] if act.split(" ").indexOf(action) isnt -1 and obj is object return callback true callback false config = require "../config" 

Usage example:

 exports.edit = (userid, name) -> # Q promise deferred = Q.defer() # default value can = false # We check wheteher it can or not canThis(userid, "user", "edit").then((can)-> if not userid return deferred.reject(error.throwError "", "UNAUTHORIZED") User = mongoose.model "User" User.findOne({username: name}).select("username location website public_email company bio").exec() ).then((user) -> # Can the current user do that? if not user._id.equals(userid) and can is false return deferred.reject(error.throwError "", "UNAUTHORIZED") # Done! deferred.resolve user ).fail((err) -> deferred.reject err ) deferred.promise 

Perhaps what I did is not good, but it works well, as far as I can see.

+1
source share

Yes, you can access this with the request argument.

 app.use(function(req,res,next){ console.log(req.method); }); 

http://nodejs.org/api/http.html#http_message_method

Edit:

Do not pay attention to your question. It would probably be better to assign user rights and allow access to the database based on permissions. I do not understand what you mean by verification through interaction with the database. If you already allow them to interact with the database and they do not have proper permissions to do this, is that not a security issue?

0
source share

Check out the Node permission module. This is a fairly simple concept, I hope that they will allow all CRUD methods to be used.

0
source share

All Articles