├── .gitignore ├── LICENSE ├── README.md ├── index.js ├── package.json └── test ├── data └── index.js └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | npm-debug.log 15 | node_modules 16 | .idea 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Karl Düüna 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [easy-session](https://github.com/DeadAlready/node-easy-session) is a session extension and middleware for express and 2 | connect with the aim of providing few convenience functions and security features. 3 | 4 | # Installation 5 | 6 | $ npm install easy-session 7 | 8 | # NB! Breaking changes in v2 9 | 10 | * Callbacks are no longer supported. Promises are used instead 11 | 12 | # Usage 13 | 14 | To use easy-session simply require the module and run the .main function with express or connect. 15 | This will return the middleware to bind to the stack. It can easily be done with two lines: 16 | 17 | const express = require('express'); 18 | const session = require('express-session'); 19 | const cookieParser = require('cookie-parser'); 20 | const easySession = require('easy-session'); // Require the module : line 1 21 | 22 | let app = express(); 23 | 24 | app.use(cookieParser()); 25 | app.use(session({ 26 | secret: 'keyboard cat', 27 | resave: false, 28 | saveUninitialized: true 29 | })); 30 | app.use(easySession.main(session)); // Bind the module : line 2 31 | 32 | Or in Express v3 33 | 34 | const express = require('express'); 35 | const easySession = require('easy-session'); // Require the module : line 1 36 | 37 | let app = express(); 38 | 39 | app.use(express.cookieParser()); 40 | app.use(express.session({secret: 'keyboard cat'}); 41 | app.use(easySession.main(express)); // Bind the module : line 2 42 | 43 | # Options 44 | 45 | The middleware supports the following configuration object that can be sent as a second argument to the easySession.main 46 | function 47 | 48 | { 49 | ipCheck: {boolean} // Defines if IP must be consistent during the session - defaults to true 50 | uaCheck: {boolean} // Defines if UA must be consistent during the session - defaults to true 51 | freshTimeout: {time in ms} // Time since last request still under the maxFreshTimeout 52 | // when the session is considered fresh - defaults to 5 minutes 53 | maxFreshTimeout: {time in ms} // Time after which the session is considered stale 54 | // no matter the activity 55 | rbac: {object or function} // optional, refer to easy-rbac 56 | } 57 | 58 | The IP and UA (User Agent) checks are a method against session hijacking - if the IP or User Agent changes mid-session then 59 | it is a good chance that the session has been hijacked. In which case it should be invalidated. 60 | 61 | # Functions 62 | 63 | easy-session adds the following extra functions to the Session object. 64 | 65 | ## login([role='authenticated'],[extend]) 66 | 67 | Function for logging the user in. 68 | Regenerates the session and adds _loggedInAt to the session object. 69 | Depending on the configuration also adds _ip and _ua for continuity checks. 70 | `Session.setRole` is called with the specified role 71 | If an extend object is added then the key:value pairs are added to the session. 72 | 73 | req.session.login() 74 | .then(() => //here we have a logged in session); 75 | 76 | req.session.login({userId: 10}) 77 | .then(() => { 78 | //here we have a logged in session 79 | console.log(req.session.userId); // Will print 10; 80 | }); 81 | 82 | req.session.login('admin', {userId: 10}) 83 | .then(() => { 84 | //here we have a logged in session 85 | console.log(req.session.userId); // Will print 10; 86 | }); 87 | 88 | 89 | ## logout() 90 | 91 | Function for logging out the user. 92 | Is just a proxy for session regeneration. 93 | 94 | req.session.logout() 95 | .then(() => // Here we have a logged out session); 96 | 97 | ## isGuest() 98 | 99 | Function for checking if the user is a guest. 100 | Returns true if logged out, false if logged in. 101 | 102 | if(!req.session.isGuest()) { 103 | // Logged in user 104 | } 105 | 106 | ## isLoggedIn([role]) 107 | 108 | Function for checking if the user is logged in. 109 | Will return the opposite of isGuest(); 110 | 111 | if(req.session.isLoggedIn()) { 112 | // Logged in user 113 | } 114 | 115 | If a role is specified then it will also run `Session.hasRole` with the given role 116 | 117 | ## isFresh() 118 | 119 | Function for checking if the logged in session is fresh or stale. 120 | Returns true if fresh, false if stale. 121 | 122 | if(req.session.isFresh()) { 123 | // User has logged in recently - session is fresh 124 | } 125 | 126 | ## setRole(role) 127 | 128 | Function for setting a role in the session. 129 | 130 | req.session.setRole('admin'); 131 | 132 | ## getRole() 133 | 134 | Function for returning a role from the session. Will return 'guest' if no role specified. 135 | 136 | const role = req.session.getRole(); 137 | 138 | ## hasRole(role, [reverse]) 139 | 140 | Function for validating if the user has the specified role. Accepts string and array input 141 | 142 | if(req.session.hasRole('admin')) { 143 | // User is admin 144 | } 145 | 146 | if(req.session.hasRole(['admin', 'user'])) { 147 | // User is admin or user 148 | } 149 | 150 | Also accepts second parameter which reverses the check. 151 | 152 | if(req.session.hasRole('admin', true)) { 153 | // User is not admin 154 | } 155 | 156 | if(req.session.hasRole(['admin', 'user'], true)) { 157 | // User is neither admin or user 158 | } 159 | 160 | ## hasNotRole(role) 161 | 162 | Function for validating if a user does not have a specified role. Is equal to hasRole(role, true). 163 | 164 | if(req.session.hasNotRole('admin')) { 165 | // User is not admin 166 | } 167 | 168 | if(req.session.hasNotRole(['admin', 'user'])) { 169 | // User is neither admin or user 170 | } 171 | 172 | ## can(operation, [params]) 173 | 174 | *This function is available only if `rbac` configuration property was set.* 175 | 176 | This function uses the `getRole` function of the session object to invoke `easy-rbac`. Will return promise. 177 | 178 | app.get('/post/save', function (req, res, next) { 179 | req.session.can('post:save', {userId: 1, ownerId: 1}) 180 | .then(accessGranted => { 181 | if(accessGranted) { 182 | res.send('yes'); 183 | return; 184 | } 185 | res.sendStatus(403); 186 | }) 187 | .catch(next); 188 | }); 189 | 190 | # Middleware 191 | 192 | Easy-session provides some middleware for easier session checking 193 | 194 | ## isLoggedIn([errorCallback]) 195 | 196 | Easy-session also provides an isLoggedIn to check if the user is logged in and handle it accordingly. 197 | Usage: 198 | 199 | app.get('/restricted', easySession.isLoggedIn(), function (req, res, next) { 200 | // If the user reaches this then they are logged in. 201 | // Otherwise they get 401 202 | }); 203 | 204 | You can also set it before all validation routes 205 | 206 | app.all('*', easySession.isLoggedIn()); 207 | 208 | app.get('/restricted', function (req, res, next) { 209 | // If the user reaches this then they are logged in. 210 | // Otherwise they get 401 211 | }); 212 | 213 | You can pass a custom error handler as well 214 | 215 | app.all('*', easySession.isLoggedIn(function (req, res, next) { 216 | // A user that is logged out will reach this. 217 | // Can handle unauthorized here. 218 | })); 219 | 220 | ## isFresh([errorCallback]) 221 | 222 | Returns a middleware to check if the user is logged in and the session is fresh. 223 | 224 | app.get('/restricted', easySession.isFresh(), function (req, res, next) { 225 | // If the user reaches this then they are logged in and session is fresh 226 | // Otherwise they get 401 227 | }); 228 | 229 | ## checkRole(role, [errorCallback]) 230 | 231 | Returns a middleware to check if the user has a given role. 232 | 233 | app.get('/restricted', easySession.checkRole('admin'), function (req, res, next) { 234 | // If the user reaches this then they the 'admin' role 235 | // Otherwise they get 401 236 | }); 237 | 238 | ### NB! As of 0.2 it does no longer check if user is logged in 239 | 240 | In order to check for both you should use two middlewares together 241 | 242 | app.get('/restricted', 243 | easySession.isLoggedIn() 244 | easySession.checkRole('admin'), 245 | function (req, res, next) { 246 | // If the user reaches this then they they are logged in and have the 'admin' role 247 | // Otherwise they get 401 248 | }); 249 | 250 | ##can(operation, [params(req, res) => Promise], [errorCallback(req, res, error)]) 251 | 252 | The easy-session-rbac also exposes a middleware factory `can`. 253 | 254 | If `params` is a function then it will be invoked and once the promise it returns resolves it will call the rbac with the result as params. 255 | 256 | If `errorCallback` is provided then it will be invoked if check fails, otherwise `res.sendStatus(403)` is invoked. 257 | 258 | // With no params 259 | app.get('/middleware/post/delete', esRbac.can('post:delete'), function (req, res, next) { 260 | res.send('yes'); 261 | }); 262 | 263 | // With params function 264 | app.get( 265 | '/middleware/post/save/:id', 266 | esRbac.can('post:save', async (req, res) => ({userId: 1, ownerId: +req.params.id})), 267 | function (req, res, next) { 268 | res.send('yes'); 269 | } 270 | ); 271 | 272 | // With errorCallback 273 | app.get( 274 | '/middleware/post/delete', 275 | esRbac.can('post:delete', {}, function (req, res, err) { 276 | console.log(err); 277 | res.send('You are not authorized'); 278 | }), 279 | function (req, res, next) { 280 | res.send('yes'); 281 | } 282 | ); 283 | 284 | ## Changelog 285 | 286 | * v2.0.2 - account for usage of req.session.destroy() 287 | 288 | ## License 289 | 290 | The MIT License (MIT) 291 | Copyright (c) 2014 Karl Düüna 292 | 293 | Permission is hereby granted, free of charge, to any person obtaining a copy of 294 | this software and associated documentation files (the "Software"), to deal in 295 | the Software without restriction, including without limitation the rights to 296 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 297 | the Software, and to permit persons to whom the Software is furnished to do so, 298 | subject to the following conditions: 299 | 300 | The above copyright notice and this permission notice shall be included in all 301 | copies or substantial portions of the Software. 302 | 303 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 304 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 305 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 306 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 307 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 308 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 309 | SOFTWARE. -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const RBAC = require('easy-rbac'); 4 | 5 | module.exports.main = function easySessionMain(connect, opts) { 6 | 7 | if(!connect) { 8 | throw new TypeError('expected connect or express or express-session object as first argument'); 9 | } 10 | let Session = connect.Session || connect.session.Session; 11 | 12 | // Get options 13 | opts = opts || {}; 14 | if(typeof opts !== 'object') { 15 | throw new TypeError('expected an options object as second argument'); 16 | } 17 | 18 | 19 | const ipCheck = opts.ipCheck === undefined ? true : !!opts.ipCheck; 20 | const uaCheck = opts.uaCheck === undefined ? true : !!opts.uaCheck; 21 | const freshTimeout = opts.freshTimeout || (5 * 60 * 1000); 22 | const maxFreshTimeout = opts.maxFreshTimeout || (10 * 60 * 1000); 23 | let rbac; 24 | if(opts.rbac) { 25 | rbac = new RBAC(opts.rbac); 26 | } 27 | 28 | // Extend the Session object 29 | 30 | /** 31 | * Function for logging the user in. 32 | * Regenerates the session and adds _loggedInAt to the session object. 33 | * Depending on the configuration also adds _ip and _ua for continuity checks. 34 | * @param role - optional role for the logged in user 35 | * @param extend - optional properties to set on created session 36 | */ 37 | Session.prototype.login = function login(role, extend) { 38 | if(typeof role === 'function') { 39 | throw new TypeError('Callbacks no longer supported as of v2'); 40 | } else if (typeof role === 'object') { 41 | extend = role; 42 | role = 'authenticated'; 43 | } 44 | if(typeof extend === 'function') { 45 | throw new TypeError('Callbacks no longer supported as of v2'); 46 | } else if (extend && typeof extend !== 'object') { 47 | throw new TypeError('Second parameter expected to be an object'); 48 | } 49 | const req = this.req; 50 | return new Promise((resolve, reject) => { 51 | this.regenerate(function (err) { 52 | if(err) { 53 | return reject(err); 54 | } 55 | // Add logged in date 56 | req.session._loggedInAt = Date.now(); 57 | req.session._lastRequestAt = Date.now(); 58 | req.session.setRole(role); 59 | 60 | if(ipCheck) { 61 | req.session._ip = req.ip; 62 | } 63 | if(uaCheck) { 64 | req.session._ua = req.headers['user-agent']; 65 | } 66 | if(extend) { 67 | Object.assign(req.session, extend); 68 | } 69 | req.session.save(); 70 | resolve(); 71 | }); 72 | }); 73 | 74 | 75 | }; 76 | /** 77 | * Function for logging out the user. 78 | * Is just a proxy for session regeneration. 79 | * @param cb 80 | * @returns {*} 81 | */ 82 | Session.prototype.logout = function logout(cb) { 83 | return new Promise((resolve, reject) => { 84 | this.regenerate(err => err ? reject(err) : resolve()); 85 | }); 86 | }; 87 | 88 | const oldResetMaxAge = Session.prototype.resetMaxAge; 89 | 90 | Session.prototype.resetMaxAge = function resetMaxAge() { 91 | this._lastRequestAt = Date.now(); 92 | return oldResetMaxAge.call(this); 93 | }; 94 | 95 | /** 96 | * Function for setting the last request for current moment 97 | * @returns {*} 98 | */ 99 | Session.prototype.setLastRequest = function setLastRequest() { 100 | this._lastRequestAt = Date.now(); 101 | }; 102 | 103 | /** 104 | * Function for checking if the user is a guest. 105 | * Returns true if logged out, false if logged in. 106 | * @returns {boolean} 107 | */ 108 | Session.prototype.isGuest = function isGuest() { 109 | return !this._loggedInAt; // If this is not set then we are not logged in 110 | }; 111 | 112 | /** 113 | * Function for checking if the user is logged in. 114 | * Returns true if logged id, false if logged out. 115 | * 116 | * @param [optional] {string} - If present the user is also checked for the role 117 | * @returns {boolean} 118 | */ 119 | Session.prototype.isLoggedIn = function isLoggedIn(role) { 120 | if(!role) { 121 | return !this.isGuest(); 122 | } 123 | return !this.isGuest() && this.hasRole(role); 124 | }; 125 | 126 | /** 127 | * Function for checking if the logged in session is fresh or stale. 128 | * Returns true if fresh, false if stale. 129 | * @returns {boolean} 130 | */ 131 | Session.prototype.isFresh = function isFresh() { 132 | if(!this._loggedInAt) { 133 | return false; 134 | } 135 | const age = Date.now() - this._loggedInAt; 136 | if(age > (maxFreshTimeout)) { 137 | return false; 138 | } 139 | if(age < freshTimeout || (Date.now() - this._lastRequestAt) < (freshTimeout)) { 140 | return true; 141 | } 142 | return false; 143 | }; 144 | 145 | /** 146 | * Function setting a role on the session 147 | * @returns {boolean} 148 | */ 149 | Session.prototype.setRole = function setRole(role) { 150 | this._role = role; 151 | return this; 152 | }; 153 | 154 | /** 155 | * Function getting a role from the session 156 | * @returns {boolean} 157 | */ 158 | Session.prototype.getRole = function getRole() { 159 | return this._role || 'guest'; 160 | }; 161 | 162 | /** 163 | * Function checking the session role 164 | * 165 | * returns true if given role matches the session role, false otherwise 166 | * @returns {boolean} 167 | */ 168 | Session.prototype.hasRole = function hasRole(role, reverse) { 169 | 170 | if(reverse) { 171 | return this.hasNotRole(role); 172 | } 173 | 174 | const current = this.getRole(); 175 | if(Array.isArray(role)) { 176 | return role.indexOf(current) !== -1; 177 | } 178 | 179 | return current === role; 180 | }; 181 | 182 | /** 183 | * Function checking the session role not to match a set 184 | * 185 | * returns false if given role matches the session role, true otherwise 186 | * @returns {boolean} 187 | */ 188 | Session.prototype.hasNotRole = function hasNotRole(role) { 189 | const current = this.getRole(); 190 | if(Array.isArray(role)) { 191 | return role.indexOf(current) === -1; 192 | } 193 | 194 | return current !== role; 195 | }; 196 | 197 | if(rbac) { 198 | Session.prototype.can = function can(operation, params, cb) { 199 | return rbac.can(this.getRole(), operation, params, cb); 200 | }; 201 | } 202 | 203 | /** 204 | * Middleware for removing cookies from browser cache and 205 | * depending on configuration checking if users IP and UA have changed mid session. 206 | */ 207 | return function sessionMiddleware(req, res, next) { 208 | 209 | // Remove cookies from cache - a security feature 210 | res.header('Cache-Control', 'no-cache="Set-Cookie, Set-Cookie2"'); 211 | 212 | if(!req.session) { // If there is no session then something is wrong 213 | next(new Error('Session object missing')); 214 | return; 215 | } 216 | 217 | function refresh(){ 218 | res.removeListener('finish', refresh); 219 | res.removeListener('close', refresh); 220 | // v2.0.2 - using req.session.destroy will unset req.session and cause an error otherwise 221 | if(req.session) { 222 | req.session.setLastRequest(); 223 | } 224 | } 225 | 226 | res.on('finish', refresh); 227 | res.on('close', refresh); 228 | 229 | if(req.session.isGuest()) { // If not logged in then continue 230 | next(); 231 | return; 232 | } 233 | 234 | if(ipCheck && req.session._ip !== req.ip) { // Check if IP matches 235 | // It would be wise to log more information here to either notify the user or 236 | // to try and prevent further attacks 237 | console.warn('The request IP did not match session IP'); 238 | 239 | // Generate a new unauthenticated session 240 | return req.session.logout() 241 | .then(() => next()) 242 | .catch(next); 243 | } 244 | 245 | if(uaCheck && req.session._ua !== req.headers['user-agent']) { // Check if UA matches 246 | // It would be wise to log more information here to either notify the user or 247 | // to try and prevent further attacks 248 | console.warn('The request User Agent did not match session user agent'); 249 | 250 | // Generate a new unauthenticated session 251 | return req.session.logout() 252 | .then(() => next()) 253 | .catch(next); 254 | } 255 | // Everything checks out so continue 256 | next(); 257 | }; 258 | 259 | }; 260 | 261 | /** 262 | * An express/connect middleware for checking if the user is loggedIn 263 | * @param errorCallback 264 | * @returns {Function} 265 | */ 266 | function isLoggedIn(errorCallback) { 267 | return function (req, res, next) { 268 | if(req.session.isLoggedIn()) { 269 | next(); 270 | return; 271 | } 272 | if(errorCallback) { 273 | errorCallback(req, res, next); 274 | return; 275 | } 276 | 277 | (res.sendStatus || res.send).call(res, 401); 278 | }; 279 | } 280 | module.exports.isLoggedIn = isLoggedIn; 281 | 282 | /** 283 | * An express/connect middleware for checking if the user is loggedIn and session isFresh 284 | * @param errorCallback 285 | * @returns {Function} 286 | */ 287 | function isFresh(errorCallback) { 288 | return function (req, res, next) { 289 | if(req.session.isFresh()) { 290 | next(); 291 | return; 292 | } 293 | if(errorCallback) { 294 | errorCallback(req, res, next); 295 | return; 296 | } 297 | 298 | (res.sendStatus || res.send).call(res, 401); 299 | }; 300 | } 301 | module.exports.isFresh = isFresh; 302 | 303 | /** 304 | * An express/connect middleware for checking if the user is logged in and has 305 | * certain _role value 306 | * 307 | * @param role - role to check for 308 | * @param reverse - secondary parameter for hasRole 309 | * @param errorCallback 310 | * @returns {Function} 311 | */ 312 | module.exports.checkRole = function checkRole(role, reverse, errorCallback) { 313 | if(typeof reverse === 'function') { 314 | errorCallback = reverse; 315 | reverse = false; 316 | } 317 | return function (req, res, next) { // Check role 318 | if(req.session.hasRole(role, reverse)) { 319 | next(); 320 | return; 321 | } 322 | if(errorCallback) { 323 | errorCallback(req, res, next); 324 | return; 325 | } 326 | 327 | (res.sendStatus || res.send).call(res, 401); 328 | }; 329 | }; 330 | 331 | /** 332 | * An express/connect middleware factory for checking if the is allowed for operation 333 | * 334 | * @param operation - operation to check for 335 | * @param params - secondary parameter for can 336 | * @param errorCallback 337 | * @returns {Function} 338 | */ 339 | module.exports.can = function can(operation, params, errorCallback) { 340 | if(typeof operation !== 'string') { 341 | throw new TypeError('Expected first parameter to be string'); 342 | } 343 | return function canAccess(req, res, next) { 344 | let $promise = typeof params === 'function' ? params(req, res) : Promise.resolve(params); 345 | 346 | $promise.then(data => req.session.can(operation, data)) 347 | .then(accessGranted => { 348 | if(!accessGranted) { 349 | return Promise.reject(new Error('forbidden')); 350 | } 351 | next(); 352 | }) 353 | .catch(err => { 354 | if(errorCallback) { 355 | errorCallback(req, res, next); 356 | return; 357 | } 358 | (res.sendStatus || res.send).call(res, 403); 359 | }); 360 | }; 361 | }; 362 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Karl Düüna ", 3 | "name": "easy-session", 4 | "description": "Session management and security simplifier", 5 | "keywords": [ 6 | "session", 7 | "management", 8 | "rbac" 9 | ], 10 | "version": "2.0.3", 11 | "repository": { 12 | "type": "git", 13 | "url": "git@github.com:DeadAlready/node-easy-session" 14 | }, 15 | "main": "index.js", 16 | "dependencies": { 17 | "easy-rbac": "^3.0.0" 18 | }, 19 | "devDependencies": {}, 20 | "optionalDependencies": {}, 21 | "engines": { 22 | "node": ">=8.0.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test/data/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const roles = { 4 | user: { 5 | can: [ 6 | 'account', 7 | 'post:add', 8 | {name: 'post:save', when: async (params) => params.ownerId === params.userId} 9 | ] 10 | }, 11 | manager: { 12 | can: ['post:delete'], 13 | inherits: ['user'] 14 | }, 15 | admin: { 16 | can: ['user:delete'], 17 | inherits: ['manager'] 18 | } 19 | }; 20 | 21 | module.exports.all = { 22 | roles, 23 | }; 24 | 25 | module.exports.roles = roles; -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var data = require('./data'); 3 | 4 | var express = require('express'); 5 | var session = require('express-session'); 6 | var cookieParser = require('cookie-parser'); 7 | 8 | var easySession = require('../index'); 9 | 10 | var app = express(); 11 | app.use(cookieParser('whut')); 12 | 13 | app.use(session({ 14 | secret: 'secret', 15 | resave: true, 16 | saveUninitialized: true 17 | })); 18 | 19 | app.use(easySession.main(session, {rbac: data.roles})); 20 | 21 | app.get('/', function (req, res, next) { 22 | res.send(req.session.getRole()); 23 | }); 24 | 25 | app.get('/login/:role', function (req, res, next) { 26 | req.session.login(req.params.role) 27 | .then(() => res.redirect('/')) 28 | .catch(next); 29 | }); 30 | 31 | app.get('/logout', function (req, res, next) { 32 | req.session.logout() 33 | .then(() => res.redirect('/')) 34 | .catch(next); 35 | }); 36 | 37 | app.get('/post/save', function (req, res, next) { 38 | req.session.can('post:save', {userId: 1, ownerId: 1}) 39 | .then(accessGranted => { 40 | if(accessGranted) { 41 | res.send('yes'); 42 | return; 43 | } 44 | res.sendStatus(403) 45 | }) 46 | .catch(next); 47 | }); 48 | 49 | app.get('/post/:operation', function (req, res, next) { 50 | req.session.can('post:' + req.params.operation) 51 | .then(accessGranted => { 52 | if(accessGranted) { 53 | res.send('yes'); 54 | return; 55 | } 56 | res.sendStatus(403) 57 | }) 58 | .catch(next); 59 | }); 60 | 61 | app.get('/middleware/post', easySession.can('post:add'), function (req, res, next) { 62 | res.send('yes'); 63 | }); 64 | 65 | app.get('/middleware/post/delete', easySession.can('post:delete'), function (req, res, next) { 66 | res.send('yes'); 67 | }); 68 | 69 | app.get('/middleware/post/save/:id', easySession.can('post:save', (req, res) => Promise.resolve({userId: 1, ownerId: +req.params.id})), function (req, res, next) { 70 | res.send('yes'); 71 | }); 72 | 73 | app.listen(3000); --------------------------------------------------------------------------------