Express.js / Mongoose用户角色和权限

时间:2014-06-18 13:27:06

标签: node.js mongodb express mongoose

我正在使用Node,Express和Mongoose创建一个相当简单的站点。该站点需要具有用户角色和权限。我的想法是,我将根据用户与数据库的交互来验证权限。

在mongoose中是否有办法确定当前可能由用户执行的CRUD操作类型?

5 个答案:

答案 0 :(得分:10)

我找到了解决方案。听到人们对此的看法会很棒。

我有一个权限配置对象,用于定义每个角色及其权限。

权限配置对象

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']
        },

    ]
};

中间件功能

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;

然后我创建了一个中间件函数,当检查方法被调用时,它从 req 对象(req.user.role)获取用户角色。然后查看传递给中间件的参数,并将它们与权限配置对象中的参数进行交叉引用。

使用middlware路由

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

答案 1 :(得分:4)

这是我的实施。 代码可以重用于客户端和服务器。我将它用于我的express / angular网站

  1. 减少代码重复,提高客户端/服务器之间的一致性
  2. 奖金利益:在客户端的适配器上,我们可以简单地返回true以授予最大访问权限以测试服务器的健壮性(因为黑客并且很容易克服客户端限制)
  3. 在app / both / both.js中

    var accessList = {
        //note: same name as controller's 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
    
    }
    

    然后在app / server / helper.js上(充当适配器)

    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())
            }
        }
    }
    
    在app / client / helper.js上,也充当适配器。

    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)
    }
    

答案 2 :(得分:1)

我个人从幽灵中获取灵感。在我的配置中有perms,permissions.js导出一个canThis函数,它接受当前登录的用户。 Here is the whole project

我的配置文件的一部分

"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. e. g. "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"

用法示例:

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

也许我所做的并不好,但就我所见,它运作良好。

答案 3 :(得分:0)

是的,您可以通过request参数访问它。

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

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

修改

误读了你的问题。分配用户权限并允许基于权限访问数据库可能更好。通过与数据库的交互验证,我不明白你的意思。如果您已经允许它们与数据库进行交互,并且它们没有相应的权限,那么这不是一个安全问题吗?

答案 4 :(得分:0)

检查节点模块permission。这是一个非常简单的概念,我希望他们也允许所有的CRUD方法。