#Suggestion on how to structure providers depending on role

9 messages · Page 1 of 1 (latest)

hexed grotto
#

Hello Nestjs architecture community. I need your suggestion in terms on how best to structure a Nestjs app when it has to serve different actors with different permissions.

My API serves 3 actors: admin, partners, and final users.
As a consequence, at the moment, I planned different sets of controllers with different guards in different folders:
/admin
/partners
/

Does it make sense? Is there a better way?

Second question. As such actors perform (are allowed to perform) different actions on the same entity say "Person", I thought about creating separate providers per actors
adminPersonsService
partnerPersonsService
PersonsService

does it make sense? Is there a better way?

Thanks a lot!

severe narwhal
#

Guards are an implementation detail to the controller. The name of the controller should just be the controller and the location of the controller should be in the module with the feature it guides traffic for simply in a /controller folder. No more, no less. Don't put controllers in folders for roles or permission sets. That will end up making you crazy.

Your services should take in or know about the current session it is working on. Either you have via your authentication guard the user attached to the request object or you have some other method to know what session is being handled in your services. As you then know what session is being handled, you should also know what permissions the user has and as such, can make the right decisions in your services as to what to do and not do. Also, depending on what the controller can "pass" will depend on what the service should be doing. DO NOT make up different services for your permission sets/ roles. If you pass through a partner and a user to the same service, the service should be able to tell the difference and the decisions are made accordingly. If you, at that point, need methods specialized for each role type, then make them there or you can break them out to their own services. However, you break them out, not because of the role, but because the methods are completely different. What you should avoid is copying and pasting the same code, in order to have the same or similar method for each role.

If the differences in work are large or vast, what you might end up needing to do is to break the three roles either into their own apps. That depends, again, on how vast and different the work and processes will be.

hexed grotto
#

Thanks Scott for the amazing explanation. I have a concrete example that perhaps could help elaborate the topic a bit further

#

Here you can find the current folder structure.
I have extracted the controllers outside their specific modules and I have controllers for admin (app used by internal employees with admin permissions and "dangerous" functionalities) and for partners (same FE app, but limited actions). As you can see the respective modules do not have controllers anymore, but only services.

From what you say above, it would be better for me to move the controllers back to their modules, but I guess still keep one file for admin (e.g. AdminPlansControlller) and one for partner (PartnerPlansController).

At the same time, as actions available for Admin and actions available for Partners are different, I should break the services in two (e.g. instead of ScholarsService > AdminScholarsService and PartnerScholarsService) also considering the different functionalities. An example is as follows:

  • Admin users can fetch all Scholars in the system
  • Partner users can only fetch Scholars they created

Wdyt?

severe narwhal
#

How are you making permission decisions currently?

hexed grotto
#

I use guards and decorators

severe narwhal
#

Unfortunately, RBAC authorization via decorators is quite limited because it is one dimensional. You can only say, if this person is user type X, allow this access to this path of response. Once you need to have more intricate permissions, it starts to fail you miserably or you end up making special controllers and services just to route to what a user can do, which is where you are now. That will get more and more painful as your system grows. It's ok for very simple systems and that is about it.

If you know you'll need more authorization capabilities, what you need to do is abstract authorization away from your base code. By that I mean, your authorization system should analyze what is coming in as a request, compare it to what the users is allowed to do, and at that point, either allow it or not. After that decision, the path to making the request happen will be the same for everyone. A "global guard". Can you imagine how much more simpler that would be? The CASL implentation can help you with this. But, it is mighty hard to grasp. But, once you do, it is very powerful. 🙂