-
-
Notifications
You must be signed in to change notification settings - Fork 276
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
Feature request: Promises in ability conditions #160
Comments
What is the usecase for this? Not sure whether this is a good idea because it will be hard to understand/optimize amount of queries generated by authorization library |
Let's say you have users and devices, and users have access to one or more devices. The relationship between users and their devices is stored in a table in a database. You have some data about those devices stored in Redis and want to protect this function:
It would work as follows (pseudo code):
|
The main issue here is that So, the starting point for PoC is to detect async conditions and resolve them, later check conditions as usually. There are 3 downsides with this approach however:
That’s why I tried to postpone implementation of something like this. |
I would REALLY like to see this idea dropped. For reference, I built https://github.com/JaneJeon/objection-authorize which is built on top of a competing access control library, https://github.com/tensult/role-acl. The developer of And that broke everything needlessly. And that's actually the main reason why I'm looking to migrate my library to using casl instead. I feel that you SHOULDN'T have async conditions to "query the database". If you need something async, you should put it OUTSIDE the actual ability condition checks - provide all of the information necessary to make a decision to the ability builder in forms of context objects. You don't need async conditions, so the benefits are nil to begin with, and if you do "add" this "feature", it will break everyone's code. BAD IDEA. @stainly I hope to dear god that you don't consider this. It's far more changes than you'd think for basically no benefit. |
@JaneJeon thanks for sharing your opinion! Currently I don’t plan to change anything as I need to think about this more closely. Very likely that logic can be inverted and left to be pure and sync on Ability side. There were some thoughts about adding functionality not changing. So, be sure, there will be always logic in Casl to check conditions synchronously. |
You're absolutely right. I'd use the factory pattern, where the context is provided beforehand: async function defineAbilitiesFor(user, device) {
// also @RedShift1 you're just begging for an injection attack if you're using template strings for this
const deviceCount = await db.query(`SELECT COUNT(*) AS cnt FROM userDevices WHERE deviceId = ${device}`)
return AbilityBuilder.define((can, cannot) => {
if (deviceCount) can('read', 'SomekeyRequest')
})
} This solves RedShift's problem without having to add async ability conditions. I see no reason to even consider changing the way this library works when there's a working solution like this. Please feel free to close this issue as it's solved. @stalniy |
Probably what @RedShift1 wanted is a posibility to lazily define conditions for rules. Why is useful? because it may be not optimal to query database for device count on each request (without caching) when you want to check permissions on action and resource which has nothing to do with user devices. Solution: I see 2 solutions for this issue:
Anyway there should be a logic which allows to associate some rules with side effects. But the 1st solution I like more as it does not introduce anything additional to the library. |
Something like this should work for a while: function basicRules() {
const { rules, can } = AbilityBuilder.extract()
can('read', 'Post')
return rules
}
async function devicesRules() {
const count = await getDeviceCount()
const { rules, can } = AbilityBuilder.extract()
if (count > 2) {
can('read', 'Device')
}
return rules // or basicRules().concat(rules)
}
const rulesProvider = {
basic: basicRules,
devices: devicesRules
}
function provideAbility(rulesType) {
const defineRules = rulesProvider[rulesType]
return async (req, res, next) => {
const rules = await defineRules()
req.ability = new Ability(rules)
}
}
const app = express()
app.get('/posts', provideAbility('basic'), findPosts)
app.get('/devices', provideAbility('devices'), findDevices) |
The way I see it, you are basically caching, only with ability classes, if you choose to define the "get device count and check if count is greater than $number" functionality within the ability. Think about it - device counts can change over time, so if you really want an accurate logic, then you would need to check/create this ability class every time you get a device request. In the end, you're just moving the problem to within the ability class which is just feature bloat imo. If you want to handle "I don't need to get the device count for every request", then your approach above works perfectly - define different abilities for every route, or if that sounds verbose, have a "central" ability factory that changes what it outputs (so that it doesn't need to call |
Don't close as the recommendation should be documented in docs |
Will be documented as part of #204 |
Abilities allow using functions in the conditions of abilities, such as:
can('update', 'Post', { userId: function(id) { return id === 5 } });
Would it be possible to extend this API to also support promises? This would be handy for cases like querying databases, which return a promise.
Test example:
Right now this evaluates to
false
because it doesn't recognize promises.The text was updated successfully, but these errors were encountered: