Lesson Description
The "Creating the can Function" Lesson is part of the full, Permission Systems that Scale course featured in this preview video. Here's what you'd learn in this lesson:
Kyle introduces the concept of building a function that can handle permissions, explaining the process step by step. He discusses the importance of correctly implementing the can function within the build function to check for access permissions. Kyle demonstrates how to handle optional data conditions within the permissions, ensuring that the function accurately determines if a user has the required access rights.
Transcript from the "Creating the can Function" Lesson
[00:00:00]
>> Kyle Cook: So this is all of our editor permissions and our admin permissions done. We have not finished our permissions yet for our author or our viewer. I'll just come in here and add those in just like that. We'll get to those in a little bit. What I want to focus on next is going to be actually writing out the function that can function. So if we look back over here, you notice we have this can function that be returned to us.
[00:00:20]
We need to implement that particular piece of code next. So that's going to be going back into our builder to deal with some of the more complex TypeScript stuff, but luckily this one's not too bad. So we're going to create a build function. This build function we can essentially call at the very end. So here I can just say return builder.build. Just like that, that's going to build up all of my different permissions for us.
[00:00:44]
And then inside this build function we can take all of our permissions here and we can essentially return a brand new object that contains things like the can function to check if someone has access to it or not. So inside this build function, I essentially want to turn a brand new object and that brand new object should have this function called can directly on it. Also, I'm going to make sure I get my permissions here by just saying this.permissions.
[00:01:06]
The reason I'm doing this is because of the way closures inside a JavaScript work. I want to make sure that I only get whatever the current permissions are at the time that I run build. If I don't do this, it could access the overall permissions of the class, which may change between when I call build and not. So I like to do this to make sure I only get the current permissions that they have access to.
[00:01:25]
Then directly inside of here, we can define our can function, which is going to be another generic similar to what we have for allow up here. So our can function, we know it's going to take in a resource which is of that type of resource, because again if we look at how this function is called, we pass it in the string of our resource, we passed it in our action, and then we pass it in the actual data for our resource as well.
[00:01:47]
So we got our resource right here. We have our action. Our action comes from our resource actions, just like that, and then finally we have our data. Now this data is technically going to be an optional property, and the reason for that is because I could call this can function on my permissions, and I could say that I want to check if they have access to read any document in general. This doesn't say they have permission to read all the documents, this just says they have some type of read permission to read at least one document.
[00:02:19]
That's what this line of code is doing right here. This is very common for like create. You may want to say, oh, can the user create at least one document? If so, let them go to the new document page. So we want to make data optional to be passed in, so essentially what that does when it checks our permissions, for example, the edit permissions here is it'll just ignore all the stuff that comes afterwards, so as long as they have at least one allow with that read permission, it doesn't matter what the conditions are, this will return true for us, and this again is great for like showing our hiding buttons because for a create document, we don't know what the document's going to look like when they click on the button to actually go to the document form itself.
[00:02:57]
So this is going to be optional, and this is going to take in all that data that we care about checking for our conditions. So this is going to be our resource and this right here is going to be our condition. There we go. And the reason we can't reuse this permission helper like we were before is because if we look at our permission helper, condition is a partial object. It means we can pass in as much or as little of this condition object as we want.
[00:03:21]
But when we pass in our data, we need to have every single field available to check against our permissions, because if they check a permission for status, we need to pass in the status. If they check the is locked property, we need to pass that in as well. So anytime we pass our data in, it must either be null, as in there's no data, or we must pass in a full piece of data with all the possible conditions that we want to check.
[00:03:41]
Now once that's done, we can actually write out what this function looks like and essentially all we need to do is just loop through our current set of permissions and see if at least one of them matches what we have. So the very first thing we can do is we can take our permissions. We want to get them for the specific resource we're checking, whether it's document or project, and we want to check if some of them, which is a function that says as long as we return at least one true result from here, it'll return true.
[00:04:06]
So as long as some of these permissions match, then we're going to be fine. So the first thing to check is the action. This is the easiest thing to check. If our action is not equal to the action that we're actually checking, well then we can just return false. Just like that. Next, I want to check, do we have valid data? So when I say valid data. And this is going to be checking this data against all of the different conditions for our permission.
[00:04:31]
So first of all, if our permission.condition is equal to null, that means that we didn't define any conditions. This is a very good example of our admin permissions. You can see none of these have conditions applied, because the admin can do anything to read a document, no matter what the document itself looks like. So, as long as we have no conditions at all, we're going to return true. Or if our data itself is equal to null, again, that's just checking, can they read something, no matter what it is, it's just can they read at least one thing, even if, you know, we don't know what the conditions and such are, can they just read something on our page.
[00:05:03]
Finally, if we have data and we have the permission conditions, we want to check each one of those conditions to make sure they're true, because obviously if they're false, we want to return false. So to loop through those, we can do a simple Object.entries for each one of our conditions. This will loop through our entire object itself, and we want to loop through every single one of them to make sure they're true.
[00:05:23]
So every is the opposite of some. It must return true for every single value inside of here, and all we're going to do inside this function is get our data. Actually our data is going to be a key value pair. Value, there we go. Make sure I get my parentheses on this set up properly. Here we go, and now what we can do is we can take our data, that's the object we're checking against, and we want to compare, is it equal to the condition that we have.
[00:05:54]
So we can pass in the key and we can check to see if that is equal to our value. Now you will notice we get a little bit of a TypeScript issue here. Also up here, action should not be in a string. There we go. But you notice we get a TypeScript error here. That's because TypeScript is kind of dumb when it comes to Object.entries. It takes this key, which we know should be a type of the key of the thing we passed in, and changes it to a string.
[00:06:20]
So we essentially need to tell this TypeScript to not be dumb, and we just tell it that is a key of the perm, oops, type of perm.condition, there we go. Essentially, I'm just fixing what TypeScript should already do on its own. It knows we're passing in the permission condition, so it knows the key must be one of those keys. I'm just forcing TypeScript to know that data. So now we have this valid data.
[00:06:40]
It'll return true if we have no conditions, it'll return true if we have no data to check against, and it'll return true if the data and conditions match each other perfectly. Now in our case, that's the last thing we need to check, so we can just return this valid data right here and this will return true or false depending on if they have access to this information or not. So now we have this can function which we can use inside of our code.
[00:07:01]
We have the allow function to define our different permissions, and we so far have our permissions defined for our admin as well as for our editor. Now we want to make sure we clean up the rest of this code because it looks like we have a little bit of stuff going on inside of here. I should just put a break in here for now. There we go, that fixes that. And of course, if our user is equal to null, we're just going to return builder.build with no permissions at all.
[00:07:23]
If we don't define permissions, it'll always return false cause there are no permissions in our array to check at all. Now another thing that I want to do just to make our code a little bit more optimized is to add in some caching to this. So we're using React in this code, so we're going to use React's built-in cache. What we can do is we can call the cache function that comes from React, and we can pass it in whatever function we want.
[00:07:45]
So we have our get user permission. I'm going to call this our internal version, so we'll change this to internal just like this, and essentially what this does is it'll cache this particular function based on the data that we have being passed into it. Now we're passing our user information. I'm actually going to remove that completely and just get our user inside of here. So we'll say get current user.
[00:08:05]
Because you'll notice every time we call our function to check things, we're always getting our user first and then passing it in, so I'm kind of just skipping the middleman here and just getting the user directly in this function. So now this function has no parameters, which means that this cache function, it'll run on every single request in React. So when I make a request to my server, it doesn't matter how many times I call my get permissions function, it's going to always use the exact same cache version for that entire request.
[00:08:31]
The next time I make a request, it's going to break or create a brand new get permissions function. It'll call it, use the exact same one for that entire request, and then again, a next request after that will be a brand new one. So it's each request will only actually call this function once, which is nice because I don't want to call a get current user 100 times if I'm doing 100 different permission checks on each request.
[00:08:56]
Now this we just want to export as a function because this will give us the cached version, which we'll call get user permissions. Make sure I spell that properly. Yes, question. Might be my lack of knowledge on React, but how does it know to not like use the cache for one user for another user, uh, if we're not passing it in as an input. Yeah, so the way it works is that it's cached based on every request, and the way the users are stored in our data or in our system is a cookie inside your session.
[00:09:25]
So every time you make a request to the server, whether it's to get a page or to get some data, it'll send up that cookie, and when we call get current user, it'll give us the user for that particular cookie, but this cache is only valid for that one single request to the server. So the next time you make a request to the server, you could be logged in as a brand new user, so it'll use that new user cookie to get the current user.
[00:09:47]
Does that make sense? I think so. Yeah, essentially the way this cache function works is it's super short-lived. I mean if your request takes 100 milliseconds, the cache only lives for 100 milliseconds. So it should never have any conflicts cause there's no way you can make a request and change your user mid-request cause you have the same cookie information the whole request.
Learn Straight from the Experts Who Shape the Modern Web
- 250+In-depth Courses
- Industry Leading Experts
- 24Learning Paths
- Live Interactive Workshops