I am kind of missing how the 'pure' query problem is solved.
Say I have 10000 rows, and my authorization gives access to 3 of those rows.
With security in the database, you return 3 rows. From what I can read, the protect pattern returns 10000 rows from the database, but discard 9997 of them afterwards. Doesn't this increase load and memory usage? Shouldn't there be a balance?
Besides those performance considerations, the article starts off with you need a data access layer, but has no idea about controllers and middleware? Seeing stuff like...
if (!(user.isAdmin || document.authorId === user.id)) {
forbidden();
}
makes me almost a bit angry.
Isn't every contemporary authorization system shifting towards ReBAC (based on Google's Zanzibar paper)? The ReBAC paradigm favors the segregation of authorization logic from business logic. It'd even be possible to reimplement ABAC/RBAC-styles if you prefer to do so, but your application layer shouldn't need to care.
// this one is usually done in middleware layer
const user = await resolveUser(req);
if (!user) {
return res.status(401);
}
// this one is usually done in the controller layer
const canReadDocuments = await auth.canReadDocuments(user);
if (!canReadDocuments) {
return res.status(403);
}
// all from here is usually done in the service layer
const canReadAllDocuments = await auth.canReadAllDocuments(user);
if (!canReadAllDocuments) {
return findManyDocuments(user);
}
return findManyDocuments();
How is protecting individual queries (after retrieval!) more scalable?
This is not what I'd intended to communicate with this article. The Kilpi.filter pattern is not the primary point of this article, it is only a minor utility provided by Kilpi for special cases. I do not suggest to fetch all rows and return only authorized rows. The inner query function should still be performant and return only the requested data.
My point was to show how you can co-locate your queries and authorization logic, just as you would with any sensible data access layer. However, this approach keeps the inner function pure and e.g. easily cacheable with the upcoming Next.js "use cache" directive, and also allows easy bypassing of the authorization logic when required by your application.
Say I have 10000 rows, and my authorization gives access to 3 of those rows.
With security in the database, you return 3 rows. From what I can read, the protect pattern returns 10000 rows from the database, but discard 9997 of them afterwards. Doesn't this increase load and memory usage? Shouldn't there be a balance?