accesscontrol
accesscontrol copied to clipboard
getGrants of specific role
I have use case similar to https://github.com/onury/accesscontrol/issues/31
I don't want expose all roles and action to client , just roles and actions that granted
In the following
ac.grant('user').read('profile', ['*', '!id'])
.grant('admin').extend('user');
{
"admin": {
"$extend": ["user"]
},
"user": {
"profile": {
"read:any": ["*", "!id"]
}
}
}
If the client's role is user
, the admin role is exposed too
This is not specific to AccessControl.js. You should never expose grants / policies to client side. (Even if you only expose the policy of the required role and no other role; it's still bad practice.)
You should handle the request and grant/deny access on server side. And if granted; filter data if needed (using permission#filter()
if using this lib) and return the response. Client side should have no knowledge of your policies.
@onury I know that I must grant/deny access on server side As I told before I have use case similar to https://github.com/onury/accesscontrol/issues/31
I want to hide/show sidebar menu items based on grants, So I want to get all grants of logged in user and create new instance of AccessControl using the grants
@onury Reply please
Currently there is no built-in way to get compiled grants with resource-attributes for a role, all at once. I'm marking this as a feature request.
For now you can use this function:
const actions = ['create', 'read', 'update', 'delete'];
const possessions = ['Any', 'Own'];
function getCompiledGrantsOf(role) {
const compGrants = {};
const can = ac.can(role);
let permission;
ac.getResources().forEach(resource => {
actions.forEach(act => {
possessions.forEach(pos => {
permission = can[act + pos](resource);
if (permission.granted) {
compGrants[resource] = compGrants[resource] || {};
compGrants[resource][`${act}:${pos.toLowerCase()}`] = permission.attributes.concat();
}
});
});
});
return compGrants;
}
You can also:
-
(Recommended) Implement SSR and render menu items on server side after AC handles it (e.g. SSR with React or Next.js has built-in support for SSR)
-
Or create a new resource for such things (menu-items), setup your AC to grant it. Then fetch this from your endpoint as filtered data after AC handles it.
// Let's say menu is a resource with attributes:
// home, profile, videos, accounts
ac.grant('guest').read('menu', ['home'])
.grant('user').read('menu', ['home', 'profile', 'videos'])
.grant('admin').read('menu', ['*']);
// checking and filtering:
const menuItems = ['home', 'profile', 'videos', 'accounts'];
const permission = ac.can(role).read('menu');
if (permission.granted) {
const attrs = permission.attributes;
// instead of permission#filter we'll use Array#filter
if (attrs.length === 1 && attrs[0] === '*') return menuItems.concat();
return menuItems.filter(m => attrs.indexOf(m) >= 0)
}
EDIT: filtering part above feels awkward. I'll update this later.
- Or pre-calculate the permissions required for these menu-items. A simple example would be:
// Let's say menu items are:
// Home, My Profile, Edit Profile, My Videos, All Videos
// on server-side
function getGrantedMenuItemsOf(role) {
const menuItems = ['Home'];
if (!role) return menuItems;
const can = ac.can(role);
if (can.readOwn('profile').granted) menuItems.push('My Profile');
if (can.updateOwn('profile').granted) menuItems.push('Edit Profile');
if (can.readAny('video').granted) {
menuItems.push('My Videos');
menuItems.push('All Videos');
} else if (can.readOwn('video').granted) {
menuItems.push('My Videos');
}
return menuItems;
}
// Bind this to an API endpoint and fetch from client-side. e.g.:
getGrantedMenuItemsOf(req.user.role);
Note: These are all quick thoughts. Since I have almost no knowledge of your application, possibly there might be better ways. This is a security concept so, you should carefully consider/improve/test, if you decide to implement.
I don’t agree that you should not expose permissions. Your clients or users knows what they can do and what they can’t do anyway (because you documented this somewhere on the website).
For example, on medium anonymous users can’t leave applause and it’s clear for everybody (because you just can’t do this on ui).
So, what’s the difference whether you share permissions as JSON object or as human readable text or as UX?
If you don’t share permissions, your clients will need to duplicate or even hardcode permission logic. Later it will be very hard to change.
I consider it to be safe to share permissions with client for currently logged in user. Some time ago I wrote an article about this:
https://medium.com/dailyjs/casl-and-cancan-permissions-sharing-between-ui-and-api-5f1fa8b4bec
A good restful design requires to return 403 Forbidden status response for actions which are not allowed. So, anyway it’s quite easy to understand or get a list of permissions for a particular user credentials.
Side note: I think the issue with permission sharing in this library exists because it relies on user roles and not on what user can do (e.g., create post, read post, leave comment). If you could share user actions instead of roles, it would not expose any internals of underlying permission logic