Express.js/Mongoose user roles and permissions

前端 未结 5 497
青春惊慌失措
青春惊慌失措 2021-01-31 12:14

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

相关标签:
5条回答
  • 2021-01-31 12:38

    Yes, you can access that through the request argument.

    app.use(function(req,res,next){
         console.log(req.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?

    0 讨论(0)
  • 2021-01-31 12:39

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

    Route with middlware

    app.get('/journal', `**permissions.check('read', 'journal')**`, function (req, res) {
         // do stuff
    };
    
    0 讨论(0)
  • 2021-01-31 12:50

    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

    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.

    0 讨论(0)
  • 2021-01-31 12:55

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

    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 )

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

    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.

    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)
    }
    
    0 讨论(0)
  • 2021-01-31 12:59

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

    0 讨论(0)
提交回复
热议问题