Express.js/Mongoose user roles and permissions

前端 未结 5 764
青春惊慌失措
青春惊慌失措 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: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)
    }
    

提交回复
热议问题