Kurukshetran
12/3/2016 - 9:47 PM

Express.js role-based permissions middleware

Express.js role-based permissions middleware

// middleware for doing role-based permissions
export default function permit(...allowed) {
  let isAllowed = role => _.indexOf(allowed, role) > -1;
  
  // return a middleware
  return (req, res, next) => {
    if (req.user && isAllowed(req.user.role))
      next(); // role is allowed, so continue on the next middleware
    else {
      response.status(403).json({message: "Forbidden"}); // user is forbidden
    }
  }
}
// dummy middleware for db (set's request.db)
export default function loadDb(req, res, next) {
  
  // dummy db
  request.db = {
    users: {
      findByApiKey: token => new Promise((resolve, reject) => {
        switch {
          case (token == '1234') {
            resolve({role: 'account-owner', id: 1234});
            break;
          case (token == '5678') {
            resolve({role: 'account-member', id: 5678});
            break;
          default:
            reject();
        }
      })
    }
  };
  
  next();
}
// middleware for authentication
export default function authorize(req, res, next) {
  let apiToken = req.headers['x-api-token'];
  
  req.db
     .users
     .findByApiKey(apiToken)
     .then(user => req.user = user) // set user on-success
     .finally(next); // always continue to next middleware
}
// the main app file
import express from "express";
import loadDb from "./loadDb"; // dummy middleware to load db (sets request.db)
import authorize from "./authorization"; // middleware for doing authorization
import permit from "./permission"; // middleware for checking if user's role is permitted to make request

let app = express(),
    api = express.Router();

// first middleware will setup db connection
app.use(loadDb);

// check authorization for each request
// will set `request.user`
app.use(authorize);

// setup permission middleware,
// check `request.user.role` and decide if ok to continue 
app.use("/api/private", permit("admin"));
app.use(["/api/foo", "/api/bar"], permit("account-owner", "account-member"));

// setup requests handlers
api.get("/private/whatever", (req, res) => response.json({whatever: true}));
api.get("/foo", (req, res) => response.json({currentUser: req.user}));
api.get("/bar", (req, res) => response.json({currentUser: req.user}));

// setup permissions based on HTTP Method

// account creation is public
api.post("/account", (req, res) => req.json({message: "created"}));

// account update & delete (PATCH & DELETE) are only available to account owner
api.patch("/account", permit('account-owner'), (req, res) => req.json({message: "updated"}));
api.delete("/account", permit('account-owner'), (req, res) => req.json({message: "deleted"}));

// viewing account "GET" available to account owner and account member
api.get("/account", permit('account-member', 'account-owner'),  (req, res) => req.json({currentUser: request.user}));

// mount api router
app.use("/api", api);

// start 'er up
app.listen(process.env.PORT || 3000);