Skip to content

Instantly share code, notes, and snippets.

@jeffwhelpley
Last active December 20, 2015 02:58
Show Gist options
  • 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).

@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