Lesson Description

The "Project Service" 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 creating, updating, and deleting project services, explaining the need for permission checks and demonstrating how to implement them in the actions. He walks through the process of moving permission checks from the pages to the service layer, ensuring centralized authorization handling. Kyle discusses the benefits of the service layer, emphasizing improved single responsibility, enhanced security, and simplified testing due to centralized authorization logic.

Preview

Transcript from the "Project Service" Lesson

[00:00:00]
>> Kyle Cook: Now like I said, the big change here that you're going to notice, this is inside the project section, so let's actually move on to working on the project service next. Just minimize this down, go into our services, and we'll go directly into the project section. And essentially, I want to do a lot of the same stuff that I had before. There we go, but we're going to be doing this for projects, so we're going to have a create project service right here.

[00:00:24]
We're going to have an update project service. We're going to have our delete project service and then we'll work on the queries in a little bit. So let's actually go ahead and look at the actions for this particular section. Go to the actions for our projects. And inside of our create action you can see all we're taking along is the data, so let's make sure that we get that data that we want. Go into our create here and we'll just paste that down.

[00:00:49]
There we go, and let's import everything we need. Perfect. So now we can do all the same permission checks. The nice thing is all the permissions are the same because creating, updating, and deleting projects is only accessible by an admin. So if the user's role is not equal to admin, we're going to make sure that we throw an authorization error. Also, we're making sure that the user is authenticated, and now we can just copy whatever our create project code looked like, which is this, copy that over, and we'll paste that directly into where we're doing our create here.

[00:01:19]
And if we do a little import with that data. Looks like we have a little bit of an error going on. Let me just make sure, see what's going on here. I'm using the document schema here. This is just from the copy paste. This should be our project schema. There we go. That cleaned up all of our different errors. If we get rid of this comma here, we now have this create project service done, and instead of our action, we can again replace all this authorization code we did here.

[00:01:40]
Paste in our service and just pass it along our data as is, and that'll handle all of our different transformations and everything that we need for us. So once we have that done, we can go back into our service, we can go into the data access layer and again remove the permissions from here. We're making it so that our data access layer is essentially a single responsibility. All it does is access data or mutate data.

[00:02:00]
So let's do the exact same thing for update. We'll remove those permissions and same thing here, we will remove those permissions as well. Now back into our service, we can essentially do the exact same thing that we did for create, but we can do it for update as well. So I'll just paste all the code into here. This is going to take in our project ID and it's going to take in our project form values just like that.

[00:02:21]
And if we go over to our action, we can see exactly what the update function itself looks like. Here we go. You can see that inside of here, all we're calling this with is our project ID and our data, so that's relatively straightforward. Let's come into here and replace that with update project. There we go, and we'll just paste that exact same data in there so you can see we're checking our permissions, we're doing all of our different data validation, and then we're calling out directly to that data access layer.

[00:02:51]
We of course do the exact same thing with our delete function as well. So let's go down to our delete. This should take in a project ID. And the only thing we need to do right here is delete project and pass it in the project ID. Again, super straightforward. Now what we can do inside of our actions is clean these up by removing all the different permission checking that we had inside of here and just call out directly to the service and pass it along our data as a raw object and make sure we do the same thing here, looks like we are.

[00:03:20]
And then finally, in our delete section, we can do the exact same thing by making sure we call out to our service and everything else should just work for us. This one didn't really have any permissions inside of it, so it's relatively simple to update. And we can get rid of all these old imports that we no longer need because all of that user information is handled for us inside of the service layer instead of in the data access layer or instead of inside of our actions.

[00:03:40]
Let's again come up here, get rid of all the different imports that we no longer need. There we go, and now we can move on to the actual queries themselves. So let me just minimize this down. I'll delete what we have here because that's all from the document section, and instead what I want to do is go into my project queries, and this is where I think the real magic of this service system comes in.

[00:03:59]
So if you take a look at this code here, we essentially have two different functions and we have this giant where clause because as we know, users have different access to projects. For example, we know that a user is only able to access projects based on their department. So this is making sure we have an if check, or essentially an or check for our database query to make sure that the project department and everything matches up based on the permissions we talked about before.

[00:04:19]
But we know admins don't have restrictions, so we're not adding any extra database queries on there. But again, this logic is handled in our data access layer. So if you wanted to be able to get all the projects but not query it down based on your user information, currently our system can't do that. So we want to bring this into that service layer and let our data access layer be as pure as possible.

[00:04:38]
So we're essentially going to again take all of this code, bring it into our service layer, and we're just going to rename these functions to add service at the end of them so we know they come from our service layer. There we go. And again, these are just essentially wrappers that handle our permissions for us. So already we kind of have our permission right here. If the user is not logged in, they can't access anything.

[00:04:59]
Otherwise we want to get all of the different projects based on this giant user where query. So I'm going to import all the different stuff. Essentially it's not important you understand what the code in this where query does because it's written in Drizzle. If you're not familiar with Drizzle, it may not make sense. But essentially this is just writing a custom database query that limits what projects users have access to based on our current permission rules.

[00:05:19]
So for example, here it's checking to make sure the project table is equal to or the project department is either equal to their department or it's equal to null, which is our current restriction, and if they're an admin, we're not adding additional where clauses onto this particular check. Now in this get all project service, we want to make sure we call out to the original version, so we can say return project, I'm sorry, get all projects, get that from our data access layer, and we know we need to pass in that ordered property which is coming in from our parameters right here, because again, this is pretty much a wrapper, but we want to make sure that we still use that where clause as well, because right now it's not being used anywhere.

[00:05:57]
So we want to pass this where clause along as another parameter to this particular function. So we can just come in here, say we're going to pass along our user where clause with our user information, which we have up here. And if we go into this get all projects, we can make sure we can take along a new parameter that handles that for us. So we'll just call this our where clause. Inside of Drizzle, there's this type called SQL, which can either be SQL or undefined, that can be the two types for this, and that essentially allows us to pass along custom SQL for like different checks that we want to make.

[00:06:27]
So now we can use this where clause directly right here, instead of using the hard coded one in the system, and now you'll notice we can take all the permissions from inside of here, completely remove them from the data access layer, and now if we want to filter what objects or what projects a user has access to or filter down projects in any way, we can just pass along that where clause directly.

[00:06:45]
Now let's clean up a couple of these imports, just so we don't have any errors inside of our code. There we go, and we can move on to the final function we need to implement, which is get project by ID. So here we have our service version, all it needs to do is just return get project by ID and we need to pass it along the actual ID that we're trying to reference, and let me make sure that I import this.

[00:07:11]
If I can type properly, that is, there we go. So that is calling that and of course we need to add our permissions into this particular section. So first we want to make sure that our user is authenticated, and then we also want to make sure that the user specifically has access to this exact project. So if you remember that check that we pretty much copy pasted everywhere inside of our application, we'll just go and pull it up again.

[00:07:31]
This check right here, we essentially want to move this one check that we have everywhere into our service as a centralized check. So we'll just copy that code into our service right here. We'll actually put it down here below our project because we need to get our project to make sure they have access to it, so we'll say like that, we're going to be getting our project via ID and then what we're going to do is we're going to check to make sure everything's fine.

[00:07:53]
First of all, if our project is null, we'll just return null, because if we try to get a project that doesn't exist, that's fine. Otherwise what we're going to do is we're going to check, does the user have access to access this actual project? That's what this permission check right here is doing, and if they don't have access to this project, we're going to return null. Otherwise, at the very end of our code, we're going to return our project as is.

[00:08:14]
So now we essentially have taken this copy paste check that we've put everywhere in our code, and we've put it in one place inside of our service layer. So if we want to get a project for a user, we call this function, it'll authorize that they have access to this project, and if they don't, it'll just return null as if that project didn't exist at all. And this is where I think the real power comes in, because now if we go back to this page, we have this permission check, but we no longer actually need this permission check.

[00:08:39]
I can completely remove this permission check because if we call out to the service version of our function, that automatically handles that permission check for us. So we now no longer even have any permission checks in our page because again, the service is handling all of that for us. So essentially what we can do is we can just search for data access layer projects throughout our entire application and we can replace all of the different layers that we're using this with our service version instead, and you'll see we're able to remove tons of permissions from our application.

[00:09:06]
So here, let's remove that data access layer version and make sure we call out to the service version, and again it will now only return projects the user has access to. It was already doing that before, but now it's in that service layer already for us. Let's move on to here, do the exact same thing, make sure that this is the service layer specifically. Here this is getting a project by ID so we can make sure we call the service layer, and since it only returns a project if they have access, if they don't, it'll return null, which is already handled.

[00:09:37]
Again, this entire permission check right here, we no longer need that at all inside of our code, and we can completely get rid of all of that. Oops, there we go. And again, we're able to take that long complicated permission check and extract it out into one single location. Looks like we have an import we no longer need, so let's get rid of that, do the exact same thing here, so we'll make sure we get our project by ID just like that, and again, it's automatically handling our permissions so we can get rid of this whole permission check here.

[00:10:06]
Now we do still need this permission check. So we'll just say if user is equal to null or the role, there we go. We still need this permission check because this is checking does the user have access to edit the document? Let's make sure we actually get our user. There we go. Make sure we await that. Perfect. So now we have that permission check just for editing. This is essentially saying only show this page if we have access to edit this, but we're able to get rid of that giant project check inside the section.

[00:10:37]
We can do the same exact thing here. Again, get rid of that giant check that we no longer need for our entire permission, so we can just shrink all that down. And now we're just checking right here. Let's make sure we come in here. The user is equal to null. Perfect, we're redirecting them in that particular case, and again, this is just checking, do they have access to create a brand new document?

[00:10:56]
And then finally we can go on to this very last page here. This is the edit project page. So we can come in here with this service, and we do still need to keep this check in place because this is the check that's saying, do they have access to edit? Do they essentially have access to view this page. And then the only other place we have data access layer is inside of our services layer, which is perfectly fine.

[00:11:15]
So essentially if we do a quick search here, the only files that have our data access layer besides our auth stuff that we're not particularly touching is inside of this services layer, and I believe, yeah, all of these are specifically related to users, which is auth, which again, we're not touching authentication at all. Dustin, is there a way to differentiate between not found and not authorized, or are there any best practices around that?

[00:11:40]
Yes, so if you wanted inside your service layer, I'm not doing this, but if you wanted to, for example, here, where we're returning null, you could throw an error or do something else that essentially says like we could essentially replace this with throw new authorization error, and now you essentially are saying, okay, it's not just not found, we specifically are unauthorized to do this. It's entirely up to you.

[00:12:01]
I am only returning null here because it's much simpler for our particular code. If you have to handle errors, you have extra error handling code, and that error handling code I find is not really relevant to the permission system, so I'm just returning null, and many sites actually do this. If you try to access a document or something you don't have access to, instead of saying, hey, this document exists, but you don't have access, it'll just say not found, which is almost another layer of security because now you can't just guess IDs and find which ones exist, it'll specifically just say not found no matter what.

[00:12:29]
It's kind of like when you try to authenticate and you enter an email password, oftentimes it'll just say incorrect email and password instead of saying, you got the right email, but you got the wrong password because that is a potential security vulnerability. But again, entirely up to you, whatever you want to do. Now let's just make sure we get rid of this extra import, and with that done, we pretty much have created a full service layer.

[00:12:50]
Now the big benefit of doing this is you hopefully notice that we took a lot of our permission checks that were in our pages and we moved them into our service. For example, this code was everywhere in our application, and now if we search for it, it only exists in one single file, so we're able to massively centralize this particular aspect of our permission system and that's kind of the idea of this services layer.

[00:13:11]
Now the big benefit of this layer will come as we start to implement more complicated permission systems, but it's already starting to give us a lot of benefit inside of our code. So let's scroll down because we have implemented all that. We also refactored all of our page components because you can see here, it's now returning that null for us if it's not found or unauthorized, so we don't need those extra layers of permission checks.

[00:13:31]
We've already gone through and implemented this, so let's talk about what the benefits of this system are and maybe some of the drawbacks of this system. First of all, the big benefit is we are able to get a little bit better of single responsibility. For example, our pages now specifically only care about routing, layout, and calling our services. There's a few instances where it still calls out and checks permissions, but for the most part it's relatively straightforward.

[00:13:52]
Our actions only handle errors and redirects. They don't ever do any data access, or not data access, data manipulation and checking that. They don't do any permission handling, it's just really straightforward. The big benefit is our data access layer because now that just purely does database operations, no permissions or anything, which is great because now if you want to access data and not scope it to a user permission, you have a way to actually do that.

[00:14:16]
Our old system could not do that. And finally, we added a new services layer, which is really just based on our business logic and authorization that handles essentially all the complicated code for your application. This also makes it a little bit easier to test because now our authorization is kind of more centralized in one single place instead of all over, and our data access layer again just deals with data access.

[00:14:37]
Also, something that most permission systems get wrong is we finally have security by default, essentially. As long as you call the service layer, it doesn't matter what you do, you will only ever get authorized data back. Whether you get data they have access to, you get null if they don't have it, it's always going to only give you data they have access to, which is one of the most important things in any type of security system.

[00:14:55]
So hopefully if you've been following along up to this point, your code should look relatively similar to mine, but if you want to download the exact code I have, you can check out the branch 3-add service layer. That's going to be the exact code you see in the system.

Learn Straight from the Experts Who Shape the Modern Web

  • 250+
    In-depth Courses
  • Industry Leading Experts
  • 24
    Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now