Skip to content

Instantly share code, notes, and snippets.

@jeffwhelpley
Last active December 20, 2015 02:58
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jeffwhelpley/6059715 to your computer and use it in GitHub Desktop.
Save jeffwhelpley/6059715 to your computer and use it in GitHub Desktop.
This is a design spec for a configuration-based Node.js NoSql security access library.

Node.js CRUD Configurator

The past 2 months I have been focused almost exclusively on building a new, exciting product for GetHuman. I am using the MEEAN stack (i.e. MongoDB Express.js ElasticSearch AngularJS Node.js), which basically means doing a boat load of JavaScript development. As someone who has done a lot of JavasScript development can tell you, there are a ton of open source libraries out there that can help speed your development and improve the quality of your code.

In fact, there are so many great libraries out there that I am always surprised when I can't find a good one for something that I think is a common problem. One such problem came up a couple weeks ago and I was inspired to create a solution which I hope to turn into a new open source library.

Problem

When an API is exposed with multiple roles and perhaps even multiple permission levels within each role, it takes a decent amount of code to properly control security access. For example, here are some examples of role based security access code that is implemented within an API:

  • Allow admins to select all fields in a USERS collection, but block other roles from selecting sensitive data like password fields, user address, etc.
  • Only allow a user to query data that they created (ex. their own posts)
  • User with role X can read all data from a collection, but is only allowed to update a subset of those fields.

These are all pretty standard examples of API logic and I think many developers assume this is the stuff that is manually written within API code. The issues with doing that, however, include:

  1. Code Proliferation - As you start to add roles and permissions become more complex, this type of code proliferates and bloats the API
  2. More Bugs - It is easy to make a mistake because you are manually writing out what often ends up being long, complex logic
  3. Difficult to Refactor - It is tough to refactor code that is so specific and dense. Since the code is security related you can end up being afraid to make changes.

Solution

The thing is that while the rules are specific (i.e. role X can only access fields a, b and c in collection Y), they almost always follow a general pattern. My solution is to abstract out the basic patterns for CRUD operations on NoSql document store databases and create a relatively simple configuration file that can be used to enforce all the specific rules. Here is an example of a config file for one collection:

{
create: {
fullAccess: ['admin'],
allowed: {
user: ['name', 'title', 'description']
},
restricted: {
visitor: ['field1', 'field2']
}
},
retrieve: {
onlyMyStuff: ['visitor']
select: {
fullAccess: ['admin'],
restricted: {
user: ['field23', 'blahblahfield']
},
allowed: {
visitor: ['field1', 'field2']
}
default: {
user: '-modifyUserId -modifyUserType',
visitor: '-field1 -field2 -modifyUserId -modifyUserType'
}
},
where: {
allowed: ['_id', 'name', 'field1', 'field2']
},
sort: {
allowed: ['createDate', 'status', 'field2'],
default: '-createDate'
}
},
update: {
fullAccess: ['admin'],
onlyMyStuff: ['visitor']
allowed: {
user: ['name', 'tags', 'country']
},
restricted: {
visitor: ['title', 'field2']
}
}
}

Explaination

My CRUD Configurator code reads in a config file like this and then enforces security access when an API attempts to execute a query, update, create a new document, etc. To be clear, this library does not actually make any back end calls, but rather works as follows:

  1. API receives a client request for a query
  2. Within the request, the client may specify things like fields they would like to select, where conditions, sort fields, etc.
  3. The Crud Configurator will 'filter' the select fields, where conditions, sort fields and any other similar value such that only those fields that the current user has permission to utilize will remain. So, using the example config above, an API client with a role of visitor may request a select field of field5 and field1, but only field1 will be returned.

Other misc. things of note:

  • onlyMyStuff is used to indicate that a user in the role(s) specified may only retrieve and/or update documents that they created.
  • No one role should be under both restricted and allowed. Either permission is restrive or permissive. If both are filled in for a role then the Configurator will simply use the restricted and ignore the allowed.
  • The retrieve.where fields are not broken down by role. This is because I couldn't imagine a practical time when I would restrict the conditional fields one user can utilize vs another.
  • The default values for retrieve.select do have Mongo specific notations, but this can be easily changed to accommodate whatever NoSql backend you may have.
  • I created a generic base model that all other models inherit off in order put all the Mongoose code in one place. the CRUD operations are largely generic, but can be overridden by subclassed models. In short, the Crud Configurator code is independant of any particular NoSql document store and then the base model sits on top of that with all of my Mongo specific code.

Final Point

The last point I will make is that because of essentially two 175 line modules (i.e. the Crud Configurator and my base model class), I was able to eliminate a massive amount of code and simplify my automated tests. Almost all of my individual models and API routing modules are extremely small. The modules that do have more than a dozen lines of code are dealing with some edge case overrides to the generic CRUD logic that I hope to eliminate as I continue to refine the Configurator.

Feedback

I posted this in hopes to get feedback on the direction, whether there is interest in this type of thing, etc. It is going to take me another 2 months to complete my current project with GetHuman, but then I plan trying to open source some form of this library (as long as there is some interest).

@fizerkhan
Copy link

Yes certainly, it will be the better idea to handle the access control. In my project, i currently using Node_acl. It works pretty well.

@jeffwhelpley
Copy link
Author

Thanks for the link and the feedback. Node_acl (and some other libraries I have found) are focused at the resource (i.e. NoSql collection) level and grant access by role to the primary CRUD operations on that resource. The main focus of my Configurator is one level deeper than that on access to individual fields and documents within collections during those CRUD operations. With something like Node_acl you could write manual code within one of the acl callbacks, but I still think that can get too complicated and messy.

What I should probably think about, however, is combining the higher resource-level acl with my field-level acl so you can do both in one place.

@jacopotarantino
Copy link

This sounds like a great idea to me. With a well-written library, I'd probably start including it in most of my projects. I'm dealing with user levels on a project right now and they're such a pain to code out.

@jeffwhelpley
Copy link
Author

@jacopotarantino, I hear you on the pain. That was my motivation for building this. I will do another gist with more detailed code in a couple weeks after I work out a couple issues.

@chieffancypants
Copy link

Couple questions / comments:

  • Would you create a separate CRUD configurator file for each resource (e.g. db colleciton)?
  • Storing the data as JSON (instead of in the DB) will prevent your app's users from making adjustments to the security model. For example, many apps (such as Github) allow end-users to create their own groups and assign permissions.
  • Is there any thought on how such an API could be tied to individual documents/objects? For example, the HR group/role should have access to HR-related documents, but because it could contain sensitive information, the Sales (or any other) group shouldn't be able to view any of those documents. Perhaps this is a potential use for roles in the where configuration?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment