Skip to content

Quick Start

Please have express and mongoose installed in advance.

Installation

$ npm install express-mongoose-acl
$ yarn add express-mongoose-acl

Quick Start

Global Permissions

Global Permissions are fundamental elements of role-based access control (RBAC) to the backend API endpoints.

// create Mongoose models beforehand
import macl from 'express-mongoose-acl';

macl.set('globalPermissions', function (req) {
  const user = req.user;

  if (!user) return { isGuest: true };

  return {
    isGuest: false,
    isAdmin: user.roles.includes('admin'),
    isManager: user.roles.includes('manager'),
  };
});

It will set the global permission object to Express request object; request._permissions. To change the permission field name, update the global option permissionField:

macl.set('permissionField', 'mypermissions');

Model Router

To create pre-defined Express routes binding to a Mongoose model, simply create a model router:

const userRouter = macl.createRouter('User', { baseUrl: 'users' });

The first argument must match a Mongoose model name created beforehand.

Route Guard

Route Guard applies role-based security and restricts access to the backend API endpoints based on the global permissions. The available routes are Create, Read, Update, Delete, and List (CRUDL); it only allows API requests with valid checks and excludes the omitted routes. There is more than one way to validate the access:

  • boolean: true | false
  • string: valid if the key returns true in the global permissions
  • array: valid if any of the keys returns true in the global permissions
  • function: valid if the function returns true
userRouter.routeGuard({
  list: true,
  read: ['isAdmin', 'isManager'],
  update: 'isAdmin',
  create: function (globalPermissions) {
    // `this` refers to Express request object
    if (globalPermissions.isAdmin) return true;
    return false;
  },
  delete: false,
});

Base Query

Base Query applies document-level security to control access to individual documents in a collection. It decorates Mongoose Query object to define the permission guardrails based on the global permissions.

userRouter.baseQuery({
  list: function (permissions: Permissions) {
    return true;
  },
  read: function (permissions: Permissions) {
    if (permissions.isAdmin) return {};
    else return { $or: [{ _id: this.user._id }, { roles: 'user' }] };
  },
  update: function (permissions: Permissions) {
    if (permissions.isAdmin) return {};
    else return { _id: this.user._id };
  },
  delete: function (permissions: Permissions) {
    return permissions.isAdmin;
  },
});

For example, in the case of non-admin updating the user of ID 123456, it will generate a query as below behind the scenes:

const query = { $and: [{ _id: this.user._id }, { _id: '123456' }] };
const result = await mongoose.model('User').findOne(query);

Permission Schema

Permission schema defines the fine-grained resource control mapping based on the global and optional document permissions. It applies field-level security to control access to individual fields within a document while Base Query works in document-level security. If no field-level security rule is defined for a field, the field is protected by all actions, list, read, update and create.

userRouter.permissionSchema({
  name: { list: true, read: true, update: 'edit.name', create: true },
  roles: {
    list: ['isAdmin', 'isManager'],
    read: 'isAdmin',
    update: function (permissions: Permissions, docPermissions) {
      // `this` refers to Express request object
      if (docPermissions['edit.roles']) return true;
      return false;
    },
    create: 'isAdmin',
  },
});
  • global permissions are available in all actions.
  • document permissions are also available in update and create actions; for example, edit.name is a document permission generated by the router option docPermissions.

Document Permissions

Document permissions play a key role for field-level security and available in applicable middleware hooks. You can also find the document permissions in the frontend application and apply business logic in UI based on the permissions generated for the user.

userRouter.docPermissions(function (docOrObject, permissions: Permissions) {
  const isMe = String(docOrObject._id) === String(this.user._id);

  return {
    'edit.name': permissions.isAdmin || isMe,
    'edit.roles': permissions.isAdmin,
  };
});