├── .babelrc ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── gulpfile.js ├── package.json └── src └── index.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "stage-0", 4 | "es2015" 5 | ], 6 | "plugins": [ 7 | "transform-class-properties" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "parser": "babel-eslint", 4 | "rules": { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | node_modules 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Zlatko Fedor 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | mongoose-hrbac 2 | ============== 3 | 4 | Hierarchical Role Based Access Control for your mongoose models 5 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var mocha = require('gulp-mocha'); 3 | var babel = require('gulp-babel'); 4 | 5 | gulp.task('test', function () { 6 | return gulp.src('./tests/**/*.js') 7 | .pipe(babel()) 8 | .pipe(mocha({ 9 | timeout: 10000 10 | })); 11 | }); 12 | 13 | gulp.task('build', function (callback) { 14 | return gulp.src('./src/**/*.js') 15 | .pipe(babel()) 16 | .pipe(gulp.dest("./dist")); 17 | }); 18 | 19 | gulp.doneCallback = function (err) { 20 | process.exit(err ? 1 : 0); 21 | }; 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mongoose-hrbac", 3 | "version": "4.0.1", 4 | "description": "Hierarchical Role Based Access Control for your mongoose models", 5 | "author": { 6 | "name": "Zlatko Fedor", 7 | "email": "zfedor@gmail.com", 8 | "url": "http://www.cherrysro.com/" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git://github.com/seeden/mongoose-hrbac.git" 13 | }, 14 | "keywords": [ 15 | "mongoose", 16 | "rbac", 17 | "hrbac", 18 | "role" 19 | ], 20 | "scripts": { 21 | "prepublish": "npm run build", 22 | "build": "node ./node_modules/gulp/bin/gulp.js build", 23 | "test": "babel-node ./node_modules/gulp/bin/gulp.js test", 24 | "eslint": "node ./node_modules/eslint/bin/eslint.js ./src || exit 0", 25 | "eslintautofix": "node ./node_modules/eslint/bin/eslint.js --fix --ext .js,.jsx ./src" 26 | }, 27 | "private": false, 28 | "license": "MIT", 29 | "main": "./dist/index.js", 30 | "engines": { 31 | "node": ">= 0.10.0" 32 | }, 33 | "dependencies": { 34 | "lodash": "^4.6.1" 35 | }, 36 | "devDependencies": { 37 | "babel-cli": "^6.6.5", 38 | "babel-core": "^6.6.5", 39 | "babel-eslint": "^5.0.0", 40 | "babel-loader": "^6.2.4", 41 | "babel-plugin-transform-class-properties": "^6.6.0", 42 | "babel-preset-es2015": "^6.6.0", 43 | "babel-preset-stage-0": "^6.5.0", 44 | "babel-preset-stage-1": "^6.5.0", 45 | "eslint": "2.2.0", 46 | "eslint-config-airbnb": "^6.1.0", 47 | "eslint-loader": "^1.3.0", 48 | "eslint-plugin-react": "^4.2.0", 49 | "connect-mongo": "^1.1.0", 50 | "gulp": "^3.9.1", 51 | "gulp-babel": "^6.1.2", 52 | "gulp-mocha": "^2.2.0", 53 | "gulp-util": "^3.0.7", 54 | "mongoose": "^4.4.6", 55 | "should": "^8.2.2", 56 | "supertest": "^1.2.0" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import union from 'lodash/union'; 2 | import indexOf from 'lodash/indexOf'; 3 | import without from 'lodash/without'; 4 | 5 | function getScope(rbac, cb) { 6 | const permissions = this.permissions || []; 7 | 8 | rbac.getScope(this.role, (err, scope) => { 9 | if (err) { 10 | return cb(err); 11 | } 12 | 13 | const newScope = union(permissions, scope); 14 | return cb(null, newScope); 15 | }); 16 | 17 | return this; 18 | } 19 | 20 | /** 21 | * Check if user has assigned a specific permission 22 | * @param {RBAC} rbac Instance of RBAC 23 | * @param {String} action Name of action 24 | * @param {String} resource Name of resource 25 | * @return {Boolean} 26 | */ 27 | function can(rbac, action, resource, cb) { 28 | // check existance of permission 29 | rbac.getPermission(action, resource, (err, permission) => { 30 | if (err) { 31 | return cb(err); 32 | } 33 | 34 | if (!permission) { 35 | return cb(null, false); 36 | } 37 | 38 | // check user additional permissions 39 | if (indexOf(this.permissions, permission.name) !== -1) { 40 | return cb(null, true); 41 | } 42 | 43 | if (!this.role) { 44 | return cb(null, false); 45 | } 46 | 47 | // check permission inside user role 48 | return rbac.can(this.role, action, resource, cb); 49 | }); 50 | 51 | return this; 52 | } 53 | 54 | /** 55 | * Assign additional permissions to the user 56 | * @param {String|Array} permissions Array of permissions or string representing of permission 57 | * @param {Function} cb Callback 58 | */ 59 | function addPermission(rbac, action, resource, cb) { 60 | rbac.getPermission(action, resource, (err, permission) => { 61 | if (err) { 62 | return cb(err); 63 | } 64 | 65 | if (!permission) { 66 | return cb(new Error('Permission not exists')); 67 | } 68 | 69 | if (indexOf(this.permissions, permission.name) !== -1) { 70 | return cb(new Error('Permission is already assigned')); 71 | } 72 | 73 | this.permissions.push(permission.name); 74 | return this.save((err2, user) => { 75 | if (err2) { 76 | return cb(err2); 77 | } 78 | 79 | if (!user) { 80 | return cb(new Error('User is undefined')); 81 | } 82 | 83 | return cb(null, true); 84 | }); 85 | }); 86 | 87 | return this; 88 | } 89 | 90 | function removePermission(permissionName, cb) { 91 | if (indexOf(this.permissions, permissionName) === -1) { 92 | cb(new Error('Permission was not asssigned')); 93 | return this; 94 | } 95 | 96 | this.permissions = without(this.permissions, permissionName); 97 | this.save((err, user) => { 98 | if (err) { 99 | return cb(err); 100 | } 101 | 102 | if (!user) { 103 | return cb(new Error('User is undefined')); 104 | } 105 | 106 | if (indexOf(user.permissions, permissionName) !== -1) { 107 | return cb(new Error('Permission was not removed')); 108 | } 109 | 110 | return cb(null, true); 111 | }); 112 | 113 | return this; 114 | } 115 | 116 | function removePermissionFromCollection(permissionName, cb) { 117 | this.update({ 118 | permissions: permissionName, 119 | }, { 120 | $pull: { 121 | permissions: permissionName, 122 | }, 123 | }, { 124 | multi: true, 125 | }, (err, num) => { 126 | if (err) { 127 | return cb(err); 128 | } 129 | 130 | return cb(null, true); 131 | }); 132 | 133 | return this; 134 | } 135 | 136 | /** 137 | * Check if user has assigned a specific role 138 | * @param {RBAC} rbac Instance of RBAC 139 | * @param {String} name Name of role 140 | * @return {Boolean} [description] 141 | */ 142 | function hasRole(rbac, role, cb) { 143 | if (!this.role) { 144 | cb(null, false); 145 | return this; 146 | } 147 | 148 | // check existance of permission 149 | rbac.hasRole(this.role, role, cb); 150 | return this; 151 | } 152 | 153 | function removeRole(cb) { 154 | if (!this.role) { 155 | cb(null, false); 156 | return this; 157 | } 158 | 159 | this.role = null; 160 | this.save((err, user) => { 161 | if (err) { 162 | return cb(err); 163 | } 164 | 165 | if (!user) { 166 | return cb(new Error('User is undefined')); 167 | } 168 | 169 | return cb(null, user.role === null); 170 | }); 171 | 172 | return this; 173 | } 174 | 175 | function removeRoleFromCollection(roleName, cb) { 176 | this.update({ 177 | role: roleName, 178 | }, { 179 | role: null, 180 | }, { 181 | multi: true, 182 | }, (err, num) => { 183 | if (err) { 184 | return cb(err); 185 | } 186 | 187 | return cb(null, true); 188 | }); 189 | 190 | return this; 191 | } 192 | 193 | function setRole(rbac, role, cb) { 194 | if (this.role === role) { 195 | cb(new Error('User already has assigned this role')); 196 | return this; 197 | } 198 | 199 | // check existance of permission 200 | rbac.getRole(role, (err, role) => { 201 | if (err) { 202 | return cb(err); 203 | } 204 | 205 | if (!role) { 206 | return cb(new Error('Role does not exists')); 207 | } 208 | 209 | this.role = role.name; 210 | return this.save((err2, user) => { 211 | if (err2) { 212 | return cb(err2); 213 | } 214 | 215 | if (!user) { 216 | return cb(new Error('User is undefined')); 217 | } 218 | 219 | return cb(null, user.role === this.role); 220 | }); 221 | }); 222 | 223 | return this; 224 | } 225 | 226 | export default function hrbacPlugin(schema, options = {}) { 227 | schema.add({ 228 | role: { 229 | type: String, 230 | default: options.defaultRole, 231 | }, 232 | permissions: { 233 | type: [String], 234 | default: options.defaultPermissions, 235 | }, 236 | }); 237 | 238 | schema.methods.can = can; 239 | 240 | schema.methods.addPermission = addPermission; 241 | schema.methods.removePermission = removePermission; 242 | 243 | schema.methods.hasRole = hasRole; 244 | schema.methods.removeRole = removeRole; 245 | schema.methods.setRole = setRole; 246 | 247 | schema.methods.getScope = getScope; 248 | 249 | schema.statics.removeRoleFromCollection = removeRoleFromCollection; 250 | schema.statics.removePermissionFromCollection = removePermissionFromCollection; 251 | } 252 | --------------------------------------------------------------------------------