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 | falsestring
: valid if the key returns true in the global permissionsarray
: valid if any of the keys returns true in the global permissionsfunction
: 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
andcreate
actions; for example,edit.name
is a document permission generated by the router optiondocPermissions
.
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,
};
});