Handling bits for rights in Javascript
Yesterday I wrote about handling permissions and rights with a simple model. I will try to implement the basics today in javascript. We first need to define the rights, then check if a user right collection matches one right in order to access a resource.
Defining rights
Let’s assume we define 4 rights to handle a file: Read, Download, Write and Administrate. They can be encoded each for one bit of a byte as I mentioned yesterday.
const READ = 1 // 00000001
const DOWNLOAD = 2 // 00000010
const WRITE = 4 // 00000100
const ADMIN = 8 // 00001000
Ok that’s it. For consistency, we can create an array and set the expected value at each position of the array.
const BIT_IN_BYTE = 8
const BIT_RIGHT = new Array(BIT_IN_BYTES).fill(1).map((_, i) => 2**i)
const RIGHTS = {
READ: BIT_RIGHT[0],
DOWNLOAD: BIT_RIGHT[1]
WRITE: BIT_RIGHT[2]
ADMIN: BIT_RIGHT[3]
}
// or we can customize the writing
const [READ, DOWNLOAD, WRITE, ADMIN, _UNUSED] = BIT_RIGHT
That’s maybe too much and the first solution can be fine.
Checking user rights
A user has the rights 00000011
. We want to check if they can write and read. Okay. It’s time to use bitwise operators. We can see the one that is interesting: &
. 00001000 & 00001001
returns 00001000
, so ADMINISTRATE mixed with a right value returns ADMINISTRATE or nothing. It sounds like a good predicate. Let’s implement this small one:
const userRight = READ + DOWNLOAD
const hasRight = (userRight, right) => (userRight & right) === right
if(hasRight(userRight, READ)) {
console.log('can read') // log "can read"
}
if (hasRight(userRight, ADMIN)) {
console.log('can admin') // nope
}
and… it just works! Easy peasy. We now have a way to define rights and check if a user right value matches. Short, simple and quick. Nice.
As I am programming I will be sure I will mix both parameters: userRight and the right itself. Both are numbers, so typescript won’t help us… easily.
Typescript to the rescue
Sure we can split the number type into two distinct types with branded types
type Right = number & { __brand: "RIGHT" }
type UserRight = number & { __brand: "USER_RIGHT" }
const READ = 1 as Right
const DOWNLOAD = 2 as Right
const Alice = READ + DOWNLOAD as UserRight
const hasRight = (userRight: UserRight, right: Right) => (userRight & right) === right
hasRight(Alice, READ)
hasRight(READ, Alice) // Error: Argument of type 'Right' is not assignable to parameter of type 'UserRight'
Okay, but we have to typecast all values to Right and UserRight. It is not so convenient. There should be better ways in Typescript. I simply don’t know out of the box. I’m feeling sleepy and I need to get this post done OKAY.
JS modeling or kind of curry
What if we can “express” it directly in javascript. Let’s dream of a convenient usage (or API or Interface as you want to call it):
User(Alice).can(READ)
Neat! But we hate classes because OOP sucks adds a bit of overhead. Javascript function are scoped though!
const User = (userRights) => {
return {
can: (right) => (userRights & right) === right
}
}
User(Alice).can(READ) // true
User(Alice).can(ADMIN) // false
There should be a better name for “User” to convey “right checker” but you get the idea.
We add a bit of an overhead with this solution: two functions are called and an object is returned from the first. We get however more safety!
Another cool thing is we can extend User
with more capabilities:
const User = (userRights) => {
return {
can: (right) => (userRights & right) === right,
// put other useful function using userRight
canAll: () => userRights === READ + DOWNLOAD + WRITE + ADMIN
// we can compute the sum of all rights, but again here's the idea
}
}
Another way to write it from the right perspective
const Right = (right) => {
return {
allowedTo: (userRights) => (userRights & right) === right,
}
}
Right(READ).allowedTo(Alice) // true
Right(ADMIN).allowedTo(Alice) // false
Okay we now have two safer way to handle rights. We can dig into two directions: make it more type safe with typescript or improve the javascript API/interface of the logic.
Now I am feeling more sleepy but the goal is achieved: handling bits for rights in javascript.