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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/24286835/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 17:30:15  来源:igfitidea点击:

Express.js/Mongoose user roles and permissions

node.jsmongodbexpressmongoose

提问by tkiddle

I am creating a fairly simple site with Node, Express and Mongoose. The site needs to have have user roles and permissions. My thoughts are that i'll validate permissions based on user interaction with the data base.

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

In mongoose is there a way to determine the type of CRUD operation currently being carried out possibly by a user?

在猫鼬中,有没有办法确定用户当前可能执行的 CRUD 操作的类型?

回答by tkiddle

I've found a solution. It would be great to hear peoples opinions on this.

我找到了解决办法。很高兴听到人们对此的看法。

I have a permissions config object which defines each role and their permissions.

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

Permissions config 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 checkmethod gets called it gets the users role from the reqobject (req.user.role). It then looks at the params passed to the middleware and cross references them with those in the permissions config object.

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

Route with middlware

使用中间件路由

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

回答by OMGPOP

This is my implementation. The code is reusable for client and server. I use it for my express/angular website

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

  1. Reduce code duplicate, better consistence between client/server
  2. Bonus benefit: on client's adapter, we can simply return true to grant max access to test the robustness of server (since hackers and easily overcome client side restrict )
  1. 减少代码重复,提高客户端/服务器之间的一致性
  2. 额外好处:在客户端的适配器上,我们可以简单地返回 true 以授予最大访问权限以测试服务器的健壮性(因为黑客很容易克服客户端限制)

in app/both/both.js

在 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

}

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

然后在 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())
        }
    }
}

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

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

回答by Vinz243

I personnally took inspiration from ghost. In my config there is the perms, and permissions.jsexport a canThisfunction that take the current logged user. Here is the whole project

我个人从ghost那里获得了灵感。在我的配置中有 perms,并permissions.js导出一个canThis采用当前登录用户的函数。这是整个项目

Part of my config 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. 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"

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've done isn't good, but it works well as far as I can see.

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

回答by Tommz

Check the Node module permissionfor that matter. It's pretty simple concept, I hope they'll allow all CRUD methods too.

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

回答by Michael

Yes, you can access that through the requestargument.

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

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

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

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

Edit:

编辑:

Misread your question. It would probably just be better to assign user permissions and allow access to the database based upon the permissions. I don't understand what you mean by validate by means of interaction with the database. If you are already allowing them to interact with the database and they don't have the proper permissions to do so, isn't that a security issue?

误读了你的问题。分配用户权限并根据权限允许访问数据库可能会更好。我不明白您所说的通过与数据库交互进行验证是什么意思。如果您已经允许他们与数据库交互,而他们没有这样做的适当权限,这不是一个安全问题吗?