-
Notifications
You must be signed in to change notification settings - Fork 21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How to design user-facing and admin JSON endpoints? #9
Comments
I believe Option 1 is the best choice as the product details for the customer will be different from that of the Admin. I would go with Option 1. It will be simple to understand and implement too also to test. |
As we are using Haskell, we can always extract away the common functionality into functions at the time of refactoring / code review. |
What about cases where even on the admin side, different user-roles are to On Wed, Oct 5, 2016 at 7:19 PM, Sudhir Kumar [email protected]
|
/products I guess the route I would take if have different end points also, if the details are going to differ then how will we handle it? how will we parse the JSON? how will we be sure that things are the way they should be? I understand that in RoR this is easy... because its all dynamic. I believe I will go with different end points depending on the role. In the future I will move to GraphQL and Code Generation to avoid writing these things by hand. |
@sudhirvkumar Option 1 is the easy default. The real question here is whether we can do better with help from Haskell's type-system 😀 |
@saurabhnanda yeah that's true :D I had an idea of using jwt token and extracting the role from that and using that in types and also mentioning that in the API endpoint definition in Servant and then define a type which will return the appropriate type depending on the role! API definition will depend on the role extracted from the jwt token ;) http://haskell-servant.readthedocs.io/en/stable/tutorial/ApiType.html#request-headers So yes, I believe Haskell Type System can help! may be we can ask somebody in the servant repo? We need to write custom combinator(s)?
RoleType being
What do you think? |
I found this example... yet to understand it though https://github.com/arianvp/servant-jwt-example also there is an experimental method in Servant https://github.com/haskell-servant/servant/blob/master/servant/src/Servant/API/Experimental/Auth.hs So I believe we should be able to extract the Role along the request. https://github.com/haskell-servant/servant/blob/master/doc/tutorial/Authentication.lhs
|
So, Servant has some support for different authentication strategies, via that experimental api. I'm hoping to dig into that Wednesday. Authorization is a whole different problem. After authentication, i know who you are, but i'm still not sure what you're allowed to do. I really like one endpoint per role. I can look at your roles, and then go look at the endpoints and see what you can do. This works well for a relatively small number of roles. Sometimes it's helpful to put all of the authorization information in one place, a text file, a database, haskell source, whatever. Eventually, someone will have access to something they shouldn't. It's nice to be able to split out, is it a policy problem or is it a bug in the code? A contrived example, a customer can delete another customer's bookings. Clearly bad. If there's one central policy, you can tell right away if the policy is just wrong, "auth:delete-any-booking" vs "auth:delete-own-booking" or if there's a bug somewhere in the code. In java spring, you can sprinkle authorization around arbitrarily, per endpoint, per method. It can be a handy shortcut when you have a new role that's just like X plus a couple of methods. Unfortunately, that makes it pretty rough to figure out what the rules are supposed to be for a given role. One endpoint per role is great, it's clean and easy to figure out what a specific role can do. If you wind up have a hundred different roles, it kind of sucks. |
@jfoutz just to be clear, what do you mean when you say "role"? Is "admin" or "finance manager" a role? Or, is "view-any-booking" or "view-any-booking" a role? For me, a role is a tenant-defined collection of permissions. A permission is a fine grained ability to do a specific action in the system, eg can-edit-invoice, can-generate-reports. So, a different endpoint per role can work when you have 1-3 roles, but can't if you allow a tenant to define his/her own roles. Any thoughts on how to approach the solution for arbitrarily defined roles? |
I'd think of "admin" or "finance-manager" as a role. "view-any-booking" would be a permission, something they're allowed to do. I think we agree, but i might include a "vacation-labs-admin" that could do stuff across all tenants or end users. For arbitrarily defined roles, I'll go back to the standard suggestion of one table of permissions, one table of user defined roles, and a junction table to map them. each api call would have to check authorization. I think that query can be built into the type so it just happens on every call without developer interaction. Without that central source of truth things would get very complicated. You can build a nice UI around the permissions. maybe provide a few pre defined bundles of permissions for common roles. Also cache the results so the webservices aren't constantly querying. I'll keep thinking about it, maybe as i get further along i'll have some better ideas. I do think this is a pretty standard approach that works for lots of people though. |
@jfoutz that's pretty much how roles and permissions have been modelled in the starter DB schema we have in the repo. Except perhaps not having permissions in a separate table. I'm envisioning the permission list to come from the Haskell source. Adding a row in the permissions table cannot have any effect till some code is updated to react to that permission, right? However, the larger question is not how to design the schema, but how to leverage the type system to force every developer to at least think about authorisation at appropriate "levels" in the app: at the JSON level, DB access level, etc. |
Yeah, the permission (as i see it) would be some code to run, if the code doesn't exist... it couldn't run. I'll need to think more about the second part of the comment. I'm not sure how that would flow down to actual decisions in code. Since it's dynamic, it would have to start with a DB query. I'm thinking something like a reader monad that carries around a list of permissions. I'll probably have better ideas as i get further along. |
theoretically, just like we can use different content type JSON, HTML... we can vary the output depending on the Role too.. for example... if we can extract Role from the token (jwt) and use pattern matching on the Role then we know how to handle each of those Roles. data Role = Anonymous | Admin | Customer | Manager if its anonymous... we can throw an error as required... if its Admin, we can handle it accordingly! |
@sudhirvkumar Yes! absoulutely. But if the roles are custom created at runtime things get tricky. Maybe something like
or maybe
So stock roles are straightforward, they can just be hardcoded. I haven't quite figured out how to verify a |
I don't think we should keep the permissions dynamic if we need compile time guarantees |
Note: This is probably closely related to #10
Most domain models in our shopping card application will need two kinds of behaviours/representations/actions:
Apart from privileged actions, which are relatively easier to handle, privileged views seem be tougher to implement in a type-safe manner. For example, the following fields should never be sent down to a UI for end-customers:
Now, there can be two ways in which this can be designed.
Option 1: Separate API endpoints for end-customers and store owners:
/products
vs/admin/products
This allows us a very easy way to respond with different JSONs for the end-customer vs the store owner. However, what if we want a more fine-grained privilege system? For example:
Option 2: A single API endpoint which responds with different JSONs based on user permissions/roles
This allows us to implement a more fine-grained privilege system. However, if it's not type-safe, it is easy to get this wrong.
Is there any other options? Is Option 2 possible to implement in a type-safe manner?
The text was updated successfully, but these errors were encountered: