Lesson Description
The "Using CASL-Based 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:
Kyle introduces updating permissions using the CASL library, explaining the need to adjust action order and using the `subject` helper function to handle object types functionally. He discusses CASL's class-based nature as a challenge in React/Next.js environments and demonstrates cloning objects as a workaround for compatibility.
Transcript from the "Using CASL-Based Permissions" Lesson
[00:00:00]
>> Kyle Cook: So we can kind of go through our services layer first to just kind of update all of our different permissions inside of here. We'll skip over any of the advanced stuff for now. We'll come back to that in a bit. I just want to get the basic permissions in place. So, the nice thing about getting our basic permissions in place is it's relatively straightforward. First of all, we swap the order of everything, so we pass along our action first, and then since we're using CASL, CASL was really originally built around object-oriented programming.
[00:00:25]
So it'll try to infer the type of our document based on the class that it is, but we don't have any classes in our code, we're using more of a functional style of programming. So in order to get around that, that's what this subject function does. The subject function from CASL allows you to essentially pass in the actual type of this thing. That's why we needed to define inside CASL, both a string and an object version.
[00:00:50]
That's so that this subject function knows that when we pass in our string, which is document, it knows to associate this object with that string right there. Now, another problem with CASL, and the fact that it's very class-based, is that when you call subject and you pass it in an object, it's actually going to take that object, mutate it, and add some specific parameters on it that make the code think that it's a class, which makes it work great with CASL, but if you're using Next.js or React, you know that classes do not work very well when you pass between server and client components.
[00:01:21]
It'll completely break your application if you try to do that. And since this mutates our document and forces it to work like a class, we essentially just need to wrap this in a new object and spread it out. So instead of passing in this object as is and letting CASL mutate it, we're going to be essentially cloning this object and then CASL will change it, but it won't matter because when we use this document later, we won't have to run into those issues.
[00:01:43]
This is probably my biggest annoyance with the CASL library is just the way it was built doesn't work very well when you're working with like React and Next.js. So we just have this small thing for working around this particular problem. Yes, I'd assume in cases like that's not a deep clone, right? So you, no, it's not a deep clone, but the class thing that it adds to it is only added to the top level of the object.
[00:02:06]
So it won't matter if it's a deep clone or not. The only reason I'm cloning it is because CASL essentially adds a field that makes the code think that you're working with a class, and then when you pass that data from a server to a client component in React, React will fail and say we cannot work with classes being passed that way between server and client. So, this is essentially just making it so that it doesn't mutate the object.
[00:02:27]
So when we want to use it in other places, it'll work fine. Now, technically, this particular function, it doesn't matter because this code never makes it to the client because we're just using this to create our database file here. But in our UI you'll see much more why this problem occurs. And I'll even, when we get to that point, I'll show what happens when you do and don't wrap this particular thing, but it shouldn't matter that it's a deep clone or not.
[00:02:51]
Any other questions about that whole subject thing? Cool. So we'll move on and finish out with the rest of the stuff we have in here. Like I said, we'll come back to these permitted fields and database query stuff cause it's a little bit more advanced. So, pretty much the exact same thing. I'm actually, to make it a little bit easier, we'll just copy this line of code, paste it down, and we'll just change this to update.
[00:03:16]
And this right here for our document, I believe is just called document. There we go. Let's copy that over for our delete as well. Delete there. And that is done, and then we can move on to our reads as well. So here we just have a read. Read, pass in our document. This one is going to be doing a read as well. This one is going to be slightly different though. You notice we're not passing along an individual document.
[00:03:47]
So instead of having to wrap all this in a subject and everything, we can just pass it along our document as a string, and this is going to work just like our system when we passed along no document and just the string. This will just check, is there at least one instance where the user can read a document? If so, return true. Now finally, we have this last one down here, which again, I'll copy over my code to prevent too much retyping the same thing.
[00:04:12]
And there we go, we've essentially converted this file to using CASL for everything except for our more advanced features, which we'll get to in a little bit. Let's move on to our next, which is going to be our services layer here. We want to do essentially the exact same thing, except for instead of documents, we're going to be doing this for our projects instead. And of course, I copied the wrong line of code.
[00:04:37]
Let me make sure I copy this line, here we go. And this is going to be our project. Project and let's import that subject. There we go. And now let's just make sure we use this pretty much everywhere inside of our code. So we come up to here. Also, I just noticed an issue. This should be using our Drizzle where query, so this should say permissions.toDrizzleWhere. Of course, we don't have the actual autocomplete, but it should be our toDrizzleWhere.
[00:05:01]
I forgot to add that in when we were writing our code, but we'll come back to fix this cause we'll be converting to CASL anyway. So let's come over here for our read. Again, this one just uses the project as is. There we go. Move on to our delete for the exact same thing. Our update and finally copying down one last time, this is going to be for our create. And it takes in our new project. There we go.
[00:05:36]
So that has updated our services minus, like I said, our systems, our things such as our toDrizzleWhere and so on. We haven't updated those yet, but we will come back to those. Now, to find all the places that we want to update, I'm just going to do a simple search for everywhere that we have a permission, I guess is probably the easiest. There we go. And we're just going to go through these essentially one by one, updating every single one of them to use the new CASL way, and we'll just get a nice little copy so we have something to start with.
[00:06:08]
So here we have permission that's checking. This one will just swap create. And project and that should solve the problem. There we go. That solved all of that. We'll move on to the very next one. This is going to be in our project view page for all of our documents. So again, we just kind of need to swap the order of this. So I'll take our permission code that we're going to be using. And we'll paste this into here.
[00:06:34]
And this is going to be taking in our project, and we want to make sure we get that subject function imported, and this one right here is for update, so we'll make sure it says update. We'll copy this over again here for our create. There we go, and this one doesn't even have a document associated with it cause we're creating something brand new. There we go. And then finally, this is again another create, so let's paste that down.
[00:07:06]
Documents and create. Perfect. That's all the permissions inside this file handled. Moving on to our next file, this is going to be the actual document view page. And again, we pretty much are just swapping the order of everything and wrapping it inside of that subject if needed. So this is for editing a document. So we're going to pass it in our document information. This one is for deleting a document.
[00:07:31]
So to make it simple, we'll copy over the document one. Paste that in. And we're going to be deleting documents. Down here, we have a permissions for field-level permissions, and again, just like with our system, we essentially just need to reverse the order of certain things. So let's paste this in. We want to be doing a read on a document and we specifically want to check a field so we can pass in the field as the last property.
[00:07:59]
In our case, this is the created at field. We can do the exact same thing down here for the updated at field. So this is read. And this is our updated at field. There we go. That cleans up all the different permission related code we have in this file. Moving on to our next file here, we can just again copy this down. This is going to be for our edit document, so it's updating a document based on that.
[00:08:24]
And then here we again have more permission checks specifically for fields, so we can come into here. We are checking if we can update the document and we want to get its locked field. And we do the exact same thing here, but this is for our status field. And one thing that you may have noticed as I'm typing out my code is we actually have much less type safety and autocomplete than we had in our original version.
[00:08:47]
For example, here I could type anything. It doesn't have to be a valid field, and I will not get any TypeScript errors. If I type in a word that's actually real. For example, you can see there's no TypeScript errors at all being shown up in my code, which is a bit of a problem. Also, inside of our permissions, if we go to where we define our permissions and we try to go and create a permission here we go.
[00:09:12]
If I were to just put in something that doesn't exist. You can see, again, no TypeScript errors at all. So we are losing type safety by moving over to CASL because they don't have that type safety built in for these particular things, most likely because it's very difficult to write, especially in a generic way that can work across every single person's project. So we are losing a little bit of type safety in that regard.
[00:09:32]
Let's go back to finishing out what the rest of our permissions look like. This one we have already done, so we can close that down and we can move on to this particular file. This is again just a create file, so we can change this to create and make sure we import our subject. And this is a new document page, so of course, we don't have a new document for our subject yet, so we can remove the subject wrapper.
[00:09:58]
There we go and get rid of that import that we don't need. Perfect. Finish out the last couple of pages. Actually looks like we have some permissions down here still. This one again, we don't need the subject wrapper cause we don't have a document yet. All we need to do is just check the isLocked property. And then do the exact same thing right here, but for the status property. There we go. Now it looks like I just got an extra parenthesis there, that cleaned that up.
[00:10:30]
Let's go ahead and check our permissions inside of here. And once we get these permissions cleaned up, we then should be able to test to see if this is working, because unfortunately, we can't really test this until we actually get our permissions fully written out because we have too many errors. So this one is going to be our project edit page. So we're seeing if we can edit that project. And then we have a delete permission down here.
[00:10:55]
So we just again, wrap that in delete. And we want to check our project to see if we can delete it. There we go. I'll copy that cause I think most of the code we have left is project pages. Let's go down to this next page. This is our edit project page. That one's actually already done, so we can minimize that. Here we go. This is our create project page, so we can just swap the order of these. Simple as that because we don't have the actual project object yet, we're just going to be using the string instead.
[00:11:20]
Finally, our document section, we already went through that one and I think we went through our services as well. So we have actually finalized everything. The only thing that we haven't done is this pick permitted field section yet. For now, I'm going to just comment this out, so at least we can run our code. I'm just going to say const restrictedData is equal to our data. So at least we can run our code here, and I'm going to do that up above as well.
[00:11:47]
Again, we'll just comment that out. We will come back to fix this in a little bit, and all the way at the bottom where we have our Drizzle where query. I'm just going to comment that out and we'll just paste an undefined. That'll give them access to everything. For now, that's going to be good enough. We'll come back, like I said, and fix this. I just want to do this so we can test our code to see if it's running.
[00:12:11]
So again, here. Just going to replace that with undefined and we'll come back and fix this. We'll just add a little to do here, and we'll do the exact same thing here. And in the other locations just so we remember to come back and fix these. There we go, that should have removed all the errors inside of our code because we have everything converted over to CASL, so we should at least be able to check our permissions minus those database queries.
[00:12:31]
So let's go ahead and look at our application, refresh our page, and you can see we have access to too many projects and documents because those database queries aren't working, but everything for individual things should be working. The new document button is not appearing, which is great for our viewer. If they were to try to view something that they have access to, it works fine. If they were to try to view something they don't have access to, it should redirect them back to this page or a 404 actually in our case, so you can see if that is working like we expect.
[00:12:55]
If we go over to someone that has a little more permissions like an author, we can see that they should have this new document button, but it's not appearing, so that may be something we want to look into real quick, and they should be able to edit documents that they have access to, which that looks like it's working. Let's try changing the title, save the changes, that is working as well. Not 100% sure why this new document button is not appearing, but we can check for that.
[00:13:24]
What we can do, go over to our page where we're viewing our individual document, our individual project, so that'd be right here, and I just want to check essentially our button that should be appearing for creating a new document. Let me see here. Of course, it's because I set create project, just a typo in my case, this should say create document instead. This one says create document. OK, so it's just that one place I had a small typo, and there's that new document button.
[00:13:48]
Test something with just some random data, and that does look like it's working. So at least we have our permissions working minus the advanced features of automatic Drizzle queries and the pick permitted fields. So essentially, the last thing we have to do for our CASL implementation is create those things inside of our system. So let me get my code on the side here to this section I'm going to be covering, cause unfortunately, this is where we get a little bit more complicated code.
Learn Straight from the Experts Who Shape the Modern Web
- 250+In-depth Courses
- Industry Leading Experts
- 24Learning Paths
- Live Interactive Workshops