Lesson Description
The "Admin & Editor Permissions" 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:
resources, actions, and optional conditions for each permission added to the builder. He instructs students on how to define permissions for different roles within the application, starting with admin permissions where all actions are allowed for both documents and projects. Kyle then proceeds to define editor permissions, explaining the restrictions and conditions for reading and updating projects and documents based on the user's department. He demonstrates how to set up permissions based on specific project access for editors, ensuring they only have access to projects within their department.
Transcript from the "Admin & Editor Permissions" Lesson
[00:00:00]
>> Kyle Cook: So let's create our permission builder. Just like that, we're going to be creating this as a class. You could do this with a function if you want, but I find using a class is relatively easy for this particular thing, and it's kind of like almost a builder pattern in sorts. Now the first thing we want to do inside of here is define that permission store. I'm going to call this permissions, and this little pound symbol in front of it just means it's a private variable inside of TypeScript or JavaScript.
[00:00:23]
It's okay if it's private or public, it doesn't matter. This just means it's not accessible outside of this particular class itself. Next, what we want to do is we want to define a default variable for this and a type. We know that this is going to be our store for all of our permissions, and we know we already need to have a document and we need to have a project type, which by default have no permissions at all defined in them.
[00:00:43]
So currently our permission builder just has a list of permissions that's completely blank because we haven't added any permissions yet. So the next thing we need to do is to add that allow function that allows us to add these new permissions into it. And this allow function is this exact function right here. So we take in a resource, we take in an action, and we take in an optional list of conditions that we want to apply to this particular thing.
[00:01:04]
So we know this allow function is going to take in a resource, and we're going to want to make this generic, so we're going to come in here with res, and again that's going to extend the key of resources, just like that. So now we can specify that this is a type of res, which is again a key of this type, so it's either going to be the string project or the string document, which if we hover over it, it doesn't quite show us, but that is what it is.
[00:01:28]
Next thing that we're going to need to do is specify the action that we want to perform, and since we have this nice little helper type, it comes directly from here. So we can say permission of our response, that's going to give us all the different types, and we specifically care about the action portion of it. We can do the exact same thing with our condition, which we know is an optional object, and that's going to take our permission, response, and we're going to get the condition type from here.
[00:01:53]
So this type right here allows us to pass in a resource as a string, it allows us to pass in one of the available actions for it, and then it allows us to define any conditions we want that are required for this particular combination of resource and action. Now the really nice thing is defining the actual definition of this function is essentially one line of code. All we need to do is just add that to our permissions.
[00:02:13]
So we know our resource is either document or project, so we can use that to get a key from our object up here. Then I just want to push in the brand new permission. So the brand new permission is going to take in our action and our condition. That's all that we need to do to get this particular section set up and it looks like I have a bit of an error. Let me make sure I've gotten any type showing up.
[00:02:34]
Let me just reference this real quick, see what's going on. Is it resins that are resource? Oh, yep, there you go. Thank you. I just can't type. There we go. So we passed in the resource and there we go. There are no more errors. And then I'm just going to return this, that way we can chain this together. As you can see over here, we're just chaining our allow calls together. You don't have to do that, it just makes it a little bit easier to work with.
[00:02:55]
And that right there actually gives us everything we need to do to define the permissions we're going to be using. It doesn't do anything for setting up our policy engine to use those permissions and check them, but now we can actually define the permissions we want to use. So to give us a break from this complex TypeScript, let's go ahead and actually create a function which we're going to call get user permissions.
[00:03:17]
And this get user permissions function is going to take in our user objects and again we just want to pick out the properties we want, so we're going to pick out the property of department and roll, because those are kind of the things I can think of off the top of my head that we're probably going to use right away. Then inside of here we can get a permission builder, so we'll say builder is equal to new permission builder that's going to set us up with an object that's completely blank with no permissions in it, and now we can go ahead and allow whatever permissions we want.
[00:03:45]
For example, I could say builder.allow, and you can see I get autocomplete for either document or project. Let's say I want to do document. I now get autocomplete for only the actions that the document has. If I were to remove the delete action from my document, you can see immediately that gets removed from my list up here. That's why we did all this complex TypeScript so that we get perfect type safety when we actually use this code, because that's the more important side of things.
[00:04:08]
So let's just come in here, I'll just do a random create, and there we go, we have now essentially allowed anyone in our application that we call this function to be able to create any document inside of our application. If we wanted to filter this down, we could pass in some conditions, and it's just an object with all the keys being optional. For example, if we wanted the status to be draft, we can now say that they can create anything that has a draft status on it.
[00:04:30]
This one doesn't make as much sense, but maybe we change this to a read, now they can read anything with a draft status. Now what we want to do is just go ahead and define all the permissions for our various different roles, and I'm going to be using a switch statement for this so I can get type safety on if I used every single role or not. So we'll come in here with a switch. We're going to do that on the role.
[00:04:54]
Just like that. Switch over that role. And now we can come in here with a case for each of our roles. Let's just start with our admin role first, and to make this really easy, I'm going to create a helper function so that this function doesn't get too large. We'll just say add admin permissions, there we go, and we'll pass it in our admin user just like that and actually we don't even need to pass the admin user in because we know the admin has permission to do everything.
[00:05:19]
We'll just pass in the builder since we want to use that. Let's create that function, add admin permissions. Pass it in our builder, which is of that permission builder type. And now inside of here we can define all those permissions. So we can just say builder.allow and we know that they are allowed to read documents. We also allow them to do everything else associated with documents, so let's just paste that down, change this to create.
[00:05:48]
Paste it down, update, do the exact same thing with delete. And then let's copy that down again because they have permission for everything related to projects as well, so we'll update all of these to projects as well. There we go. So now they have create, read, and update and delete permission for all documents and projects inside of our entire application, which is what our admins can already do.
[00:06:09]
Next, let's go ahead and maybe define some of the other permissions in our application as well. Maybe we should work on the editor permissions next because those ones are relatively straightforward as well. Let's come in here with our editor, and we'll say add editor permissions, this one I will need to pass in my builder just like before, but I'll also need to pass in my user because the editor is restricted on certain things.
[00:06:31]
For example, they can only edit projects that are within their exact department and so on. So let's make sure we pass that in. And I'm also going to come in here with our default case as well, so we'll come in here with a default and we'll say we want to throw a new error. And the error is just going to say unhandled role, pass it in our role, and if we say satisfy is never here, this will just give me an error until I handle all the different roles inside of our application.
[00:07:02]
Now let's copy this function down, give us our add editor permissions. There we go, and we know that this takes in our user object itself and the things that I care about from my user object here is going to be just the department. So we'll just pick out the department because that's the only thing that we care about. So now let's go ahead and define all the things that they can just blanketly do in our application.
[00:07:20]
So for example, the very first thing that we know that they can do is they can read any project that is the same as their department. So we can say condition for the department here is going to be the user.department. So as long as the department of the project equals their department, they have access. We also know that they have access anytime the department is equal to null, so if the department is null, they can also read that particular project, but of course they don't have blanket read or create or update or delete access for projects.
[00:07:47]
So there's all of their read access for projects entirely done. Now when it comes to document access, this is a little bit more restrictive because they only have access to documents that are directly inside of the project that they're a part of. So we're going to create a helper function to narrow that down to just the projects they have access to. So we'll create a function we're just going to call it get department projects and it's going to take in a department and this department is just a string just like that and then we can call that function anywhere we want.
[00:08:22]
Let's just say we call it here const get department projects, pass it in the user.department. And this is going to get all the projects that this user has access to, and we should await this because this will be an asynchronous function. Now inside of here, we essentially need to write a function to get all the projects for us. Luckily, we already have a get all projects function. So we can call our get all projects function and pass it in the database query we want for the user.
[00:08:49]
So we'll come in here with a simple OR query, and this one, we're going to need to make sure we check that the project.department, oops, project table.department is equal to the department we passed in or is the department null itself. So project table.department. There we go. So this little check right here is just checking, do they have access to the project table with the departments that the same as theirs, or is the project table department null?
[00:09:13]
This is essentially the same thing that we wrote up here. It's the one place in the system where we will have to duplicate our permissions, but since it's all in the same file, I think it's okay for this particular use case. Now it does look like we have a little bit of an error with our get all projects. That's because I put this optional property first for some reason. That's fine, we'll just leave it as it is and I'll just come in here and I'll pass in an ordered of false, so we don't have to worry about that.
[00:09:36]
There we go. Realistically, I should probably swap those, but it'd be a little bit too much work to do. So there we go. Let's make sure that this is an async function. And now we have access to only the projects they are able to view, so now we can make sure we can limit their ability to update, delete, and so on based on that particular thing. So let's come into here and I want to loop through each one of these projects.
[00:09:59]
And I want to change my permissions based on that, so I can say builder.allow. I want to allow my document permissions. In our case, this is going to be a read permission for our document, and I want to do it where the project ID in my document is equal to the ID of this particular project. So now we're not just depending on our permissions checking our project first before we check our document. We're now baking into our document what actual documents they have access to based on their projects.
[00:10:27]
We can do the exact same thing for update as well, so we can come in here, change this to update, and now they have access only to update and read those specific projects. Also, we know that they can't update locked projects, so we can come in here and say is locked false as well, so we can restrict what actually projects they are able to update as well. Now let me just do a double check to make sure I've got everything as it should be.
[00:10:48]
Of course these document ones should be removed, so they have permissions for projects here and then they have permissions for documents here, and again it's restricting which documents they have access to based on the actual projects themselves.
Learn Straight from the Experts Who Shape the Modern Web
- 250+In-depth Courses
- Industry Leading Experts
- 24Learning Paths
- Live Interactive Workshops