Express.js / Mangos y permisos de usuario de Mongoose

Estoy creando un sitio bastante simple con Node, Express y Mongoose. El sitio debe tener roles y permisos de usuario. Mi opinión es que validaré los permisos en función de la interacción del usuario con la base de datos.

En Mangosta, ¿hay una manera de determinar el tipo de operación de CRUD que está llevando a cabo actualmente un usuario?

He encontrado una solución. Sería genial escuchar las opiniones de la gente sobre esto.

Tengo un objeto de configuración de permisos que define cada rol y sus permisos.

Objeto de configuración de permisos

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

Función de middleware

 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; 

Luego creé una función de middleware, cuando se llama al método check , obtiene el rol de los usuarios del objeto req (req.user.role). A continuación, examina los parámetros pasados ​​al middleware y los compara con los del objeto de configuración de permisos.

Ruta con middlware

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

Esta es mi implementación. El código es reutilizable para cliente y servidor . Lo uso para mi sitio web expreso / angular

  1. Reducir el código duplicado, mejor consistencia entre cliente / servidor
  2. Beneficio adicional: en el adaptador del cliente, simplemente podemos devolver el valor verdadero para otorgar el acceso máximo para probar la robustez del servidor (ya que los piratas informáticos y los clientes pueden superar fácilmente la restricción del lado del cliente)

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

Luego en app / server / helper.js (actuar como adaptador)

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

en app / client / helper.js, también actúa como adaptador.

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

Personalmente me inspiré en el fantasma. En mi configuración están los permisos, y permissions.js exportan una función canThis que toma el usuario registrado actual. Aquí está todo el proyecto.

Parte de mi archivo de configuración

 "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" 

Ejemplo de uso:

 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 

Tal vez lo que he hecho no sea bueno, pero funciona bien por lo que puedo ver.

Sí, puedes acceder a eso a través del argumento de request .

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

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

Editar:

Malinterpretado tu pregunta. Probablemente sería mejor asignar permisos de usuario y permitir el acceso a la base de datos en función de los permisos. No entiendo a qué se refiere con validar mediante la interacción con la base de datos. Si ya les está permitiendo interactuar con la base de datos y no tienen los permisos adecuados para hacerlo, ¿no es eso un problema de seguridad?

Verifique el permiso del módulo Nodo para esa materia. Es un concepto bastante simple, espero que también permitan todos los métodos CRUD.