Playlist explaining the codebase : Here
To launch the application, simply run: docker-compose up
This is a simple demo of an application that utilized role based authentication mechanism to ensure correct access of data and services. In addition to RBS, we're also implementing a cahce layer to demonstrate how database operations can get a significant performance boost with the right thickness of abstraction.
- To automatically test the roles and functioning of the RBS mechanism, we've setup e2e tests. These check and ensure that each role behaves the way they are expected to. Watch all the tests passing.
and see the coverage report here : simple-rbs-coverage.surge.sh
Note that since cache is disabled during tests, the cache service has poor coverage. If you are interesting in manually testing the roles, follow the readme from here. Using this postman collection can ease up the testing process.
I'm currently working on testing the cache-enabled-performance, and it's taking a hit because there's a lot of serialization and de-serialization going over a single thread. This gives rise to an overhead that reduces the benefits of adding the cache layer.
However for large responses, cache gives significant performance improvements.
To quickly start the application, run the docker-compose file, it would setup mongodb, redis and the rbs application.
docker-compose up
To start the process manually, ensure that you have a live mongodb server, redis server, then run
yarn install / npm install
yarn start:dev / npm run start:dev
The project uses mongodb as a data storage layer and relies on redis for the cache layer. I strongly recommend running the a redis container to ease the entire process.
To launch a basic container with open ports, run - docker run -d --name some-redis -p 6379:6379 redis
. Do the same with monngodb instance, and make sure to change the configs in config/default.yml
to ensure that the connection strings are still valid.
@todo
Since the application has database and cache implement, it is vital that to demo it, there is sufficient data on the system. Refer to whatever method you are using for mongodb, and import the
db.dump.json
file. I'll recommend the Azure extension on VSCode which allows linking local databases too. Everything stays in VSCode.
To make it as close to practical life, the roles of the users are stored in the database itself, and even users who have not logged in are given a role. Don't worry, that doesn't need to be done manually, the role resolver automatically gives the visitor role to anyone who does not have one.
The password for all users is 12345, and everything is stored as plaintext in the database.
Once you log in, a cookie is saved on the browser, which can be used to identify the user and role. To change the role, simply logout and login with a different credential.
controller.grant(ROLE.VISITOR).readAny('vehicle').createOwn('profile')
- Notice that the visitors do not have the permission to read the user profile, so trying to load the user profiles must throw an error.
- Visitors are allowed to create new profile / signup into system
- Once visitors create a profile, they can log into the system using the
auth/login
route handler.
When you log into system, the logs state the currently operating user alongwith their role. Also, it can be seen that this data was loaded from cache. I will share a detailed writeup about how the cache is implemented in this system (that was a separate task, but I've included both of them as it was an interesting problem to solve)
info: [email protected]
info: cache.hit.userservice.findonebyemailandpassword::[email protected]
The roles of the users are set as follows, which means that a logged in user
/** roles of user in system */
controller
.grant(ROLE.USER)
.extend(ROLE.VISITOR) /** everything that visitor has **/
.readOwn('profile')
.deleteOwn('profile')
- can read their own profile
- can delete their own profile
which means that they can not list all the users in the system. let's see what happens when we try to list all users.
an interesting thing to note here is that users can only see their own profiles, let's test that.
an edge case is that what if the user tries to view someone else's profile by their email? Let's try that too. So in this example, when trying to view the details of [email protected]
, the system throws an exception.
An interesting thing to note is that when I was developing this prototype, it was a mess trying to send these error codes. Too much redundant code, and try-catch-hell. So I wrote this npm package to sort things out. It allows me to throw an error from anywhere (i do this in the controller) and it automatically catches it and sends the response to the user.
Let us try to delete someone else's profile, and check if the system allows us.
So we know that we cannot delete some other account by our account. Lets check if we can delete our own account.
Alright, we could delete our own profile just like we wanted.
The roles of moderators is to be able to view all user profiles and ensure that no profile is injecting spam into the system.
controller.grant(ROLE.MOD).extend(ROLE.USER).readAny('profile')
Now since the moderatos has rights to read any profile, let's check if they can get list of all users.
Looks like the moderator is successfully able to list all user details. Just a check to see if the roles of user are already included in the moderator role, let's see if the moderator can view details of their own profile.
Works. As a final check, let's see if the moderator can delete other profiles or not. As per the rules, they should not be able to.
So we're assured that the moderator is not over-powered to delete any user. Our users are safe. Almost.
The roles of the admin are defined as
controller.grant(ROLE.ADMIN).extend(ROLE.MOD).deleteAny('profile')
This means that they should be able to perform all operations. Let's check how the API behaves.
The admin can see list of all users. Let's check the delete feature.
The admin was able to successfully delete the user account as allowed in the role definitions.
This demonstrates the working of a simple role bases system.
this is a section of logs generated while writing the above docs. Notice how some operations are being cached by the cache layers and database operations are being bypassed.
info: connection.mongodb.successful
info: connection.redis.success
info: [email protected]
info: operating as role: user
info: operating as role: user
info: [email protected]
info: operating as role: user
info: operating as role: user
info: [email protected]
info: operating as role: user
info: [email protected]
info: operating as role: user
info: operating as role: user
info: [email protected]
info: operating as role: user
info: operating as role: user
info: cache.hit.userservice.findonebyemail::[email protected]
info: [email protected]
info: operating as role: user
info: operating as role: user
info: All roles: visitor,user,moderator,admin
info: All resources: vehicle,profile,$extend,backup
info: Listening in port http://localhost:3000
info: connection.mongodb.successful
info: connection.redis.success
info: [email protected]
info: operating as role: user
info: cache.miss.userservice.findonebyemail::[email protected]
info: db.active
info: Attempting to set cache for userservice.findonebyemail::[email protected]
info: cache.set.userservice.findonebyemail::[email protected]
info: [email protected]
info: [email protected]
info: cookie.set.false
info: cache.miss.userservice.findonebyemailandpassword::[email protected]
info: db.active
info: Attempting to set cache for userservice.findonebyemailandpassword::[email protected]
info: cache.set.userservice.findonebyemailandpassword::[email protected]
info: [email protected]
info: operating as role: moderator
info: [email protected]
info: operating as role: moderator
info: cache.hit.userservice.findonebyemail::[email protected]
info: [email protected]
info: operating as role: moderator
info: cache.hit.userservice.findonebyemail::[email protected]
info: [email protected]
info: operating as role: moderator
info: [email protected]
info: operating as role: moderator
info: cache.hit.userservice.findonebyemail::[email protected]
info: [email protected]
info: operating as role: moderator
info: cache.hit.userservice.findonebyemail::[email protected]
info: [email protected]
info: operating as role: moderator
info: cache.hit.userservice.findonebyemail::[email protected]
info: [email protected]
info: operating as role: moderator
info: [email protected]
info: operating as role: moderator
info: cache.miss.userservice.findonebyemail::[email protected]
info: db.active
info: Attempting to set cache for userservice.findonebyemail::[email protected]
info: cache.set.userservice.findonebyemail::[email protected]