Implementing Role-based Access Control and Permissions
00:00 The first thing we're going to need to do is in our permissions utilities, we've got a couple TS ignores right here, and that's because we're trying to infer the user type from what the use user returns. Use user returns, the maybe user, which is coming from use optional user,
00:19 which comes from ultimately our root loader. So if we dive into the root loader, we're not loading enough of the information that our permissions utilities need to be able to determine the permissions for the user. So we're going to add some permissions, roles and permissions queries here.
00:36 So we're going to get the roles, and we want to select the names and the permissions, and from these permissions, we will select the action, true, and entity and access.
00:55 So with that now, we can come back to our permissions, we can get rid of all these TS ignores, and now TypeScript will be happier because we now have access to all of this stuff that we need on both the server and in the client to determine whether or not the user has a particular permission.
01:14 We can use these utils all over, and it's awesome. So with that done, let's go to the admin page first, and we'll add the require user with role utility that we put together. So we'll await, require user with role, that's coming from the permissions util, and we want to require that the user has the admin role.
01:34 So we're going to need to get the request data function args, and that's going to take the request, and then the role that we want to make sure that the user has is admin. So the request has the cookie, our utility will read that to determine who the user is, and then we'll look up the permissions or
01:52 the user's roles to make sure that they have the admin role. Now, in general, you want to avoid using utilities like this that are just like blanket user has these roles, because there could be a future where the role permissions change,
02:07 and so now we're saying the user can look at this page if they're an admin, but what if the admin role permissions change, so they can't do anything useful on this page anymore. So typically, you want to check for permissions specifically, but for something like an admin page,
02:24 it does make sense to lock that down in that way. So let's come over to our root, and Kelly put together a bit of UI for a link to the admin page if you're logged in as an admin. So here's what it looks like if I change that to true,
02:42 and now we've got our admin link right there. So let's use the user has role utility. That is not a hook. A hook is a function that uses other hooks, but this is not a hook. It's literally just a actually very simple function here.
03:00 So even though it starts with use, it's actually starts with user. So the user has the role admin, and then with that, we can conditionally display the admin link for our users so that they can get to the admin page. Now, if I come over here,
03:19 and right now we're showing the admin page. Yep, there we go. We got our refresh there, and I don't know why this is not going away, but whatever, this is not showing up, so that's exactly what we want. Awesome. Okay, so then we're gonna go to our note ID,
03:38 and we no longer need to do all this nonsense. In fact, we don't have to do any nonsense in our loader anymore because all of our permissions for what UI we display happen in the UI piece, which is kind of nice, and the reason that we can reasonably do that
03:55 is because the user's permissions are, like, it's not a ton of data, so we just send it all to the UI, and then the UI can make that happen. So here we'll say user has permission, and we want to pass the user, and whether the user has the particular permission for deleting a note,
04:15 and that's how we're gonna determine whether or not the user can see the display or see the bar that shows the notes. So Copilot tried to help. It was not helpful at all. So instead, what we're gonna do is right here, we'll say if they're the owner, then this is the special syntax
04:36 that we put together for our utility, and it's actually quite nice because it's all auto-completed. So if they're the owner, then we want to see whether they can delete a note that is their own. Otherwise, we want to delete a note that is any.
04:56 They can delete any note if they're not the owner, and if they can do that, then we can show them the Delete button. And with that, now, if I go over here, and I look at this user's note, there it is. We can see the Delete button. If I go to my own notes, nope, not Edit Profile, we're gonna go to My Notes, and there we go.
05:13 I can see the Delete button on my own as well. And then if I come over here to somebody who's not logged in, and we go to this user's notes, I cannot see the Delete button. I can't see the Edit button. That would be another thing that you would add support for in the future. Okay, great. So let's see.
05:33 We've got that piece done. Our loader has been emptied of any logic, and now our action we need to address. So all of this stuff that we did before, we no longer need to do because we have this handy utility called Require with Permission, User with Permission.
05:51 And the permission is actually gonna look pretty similar to what we had before. So first I'm gonna determine is Owner based on whether the user ID matches the note owner ID. And then based on that, I'm gonna pass the request here, Request.
06:09 And if we are the owner, then I want to make sure that I can delete my own notes. Otherwise, I have to be able to delete any note. Otherwise, I can't do any of this stuff. So we're gonna await this, and this comes from the utils that we put together here. And Require User with Permission, if the user doesn't have those permissions,
06:28 then it's gonna throw the JSON, the same thing that we were doing before, which is awesome that we can just have this one line and it will handle everything for us. We don't have to... I love that you can throw responses. Let me just say that. It's awesome. Okay, so we save that.
06:43 And now if I go back to another user and I say Delete, I am able to delete that user stuff. And then if we... Let's try this again. Actually, let's log in as somebody else. Let's come here, grab this user. And let's say that this user is kind of mad
07:04 because Cody keeps deleting his notes and he wants retribution. So we're gonna log in as this user. And then we're gonna go over to Cody. And oh man, I can't see that bar. That's super annoying. I don't like that. So we're gonna say that this user hacked into our code base because they really want to delete Cody's notes.
07:23 And they said, you know what? Can, well, const can delete is true. Take that, Cody. Now I'm gonna go delete your note. And boom, ah, you're not allowed to do that. So we have successfully protected our backend. Of course, if this user could hack our code,
08:03 So feel free to explore these utilities if you like. But what we did here was, and the things that I want you to take away from this exercise, was that to make our roles and permissions, our role-based access control really nice,
08:21 we added selecting the roles and permissions for each user. And then with that, we were able to feed that into some UI utilities so that we could determine what UI to display. And then on the, and I should mention also that like you're gonna need to do that whether or not your backend
08:39 automatically supports handling these permissions. So it is kind of annoying that you have to have like checks in your actions and stuff, but you're gonna have to have checks in your UI anyway. And so you're not ever gonna really avoid that. So in any case, you're gonna select the roles and permissions
08:59 to display some UI stuff based on the user's roles and permissions. And then we also have some backend utilities to allow checking for whether the user has particular permission to do a particular thing. And we updated our note ID.
09:17 Here, let's fix that there and get rid of you. So we've got our note ID route action that now can simply say require user with permission. And if they have permission to do that, then the code will continue. If they don't, it will throw a response and we can display something nice
09:35 with our general error boundary that we learned forever ago in the first workshop. So that is that, well done.