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
Express.js/Mongoose user roles and permissions
提问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 网站
- Reduce code duplicate, better consistence between client/server
- 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 )
- 减少代码重复,提高客户端/服务器之间的一致性
- 额外好处:在客户端的适配器上,我们可以简单地返回 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?
误读了你的问题。分配用户权限并根据权限允许访问数据库可能会更好。我不明白您所说的通过与数据库交互进行验证是什么意思。如果您已经允许他们与数据库交互,而他们没有这样做的适当权限,这不是一个安全问题吗?

