Lesson Description
The "Applying Create/Update Rules to Backend" 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 applies the "pickPermittedFields" function to the create and update document services to strip out unauthorized fields before saving to the database, then updates the UI form to conditionally hide fields the user can't modify, demonstrating the full write-side field permission flow end to end.
Transcript from the "Applying Create/Update Rules to Backend" Lesson
[00:00:00]
>> Kyle Cook: So let's very first get started with the UI because that's the easiest for us to see, and actually, instead of doing the UI first, we're going to do the backend first so we can see what happens if they pass in incorrect data. So let's go all the way down into our services layer. That's where we're going to want to do this, and we want to be in the documents folder specifically, and we're going to be messing with the create and with the updates.
[00:00:19]
Let me just get my reference code up so I don't make any mistakes. I've made enough of those today, that's for sure. Okay, so if we go into our create document section, currently, all we're doing is we're just checking, can they create this document, but we're not checking can they create all the fields that they're trying to create, because they're passing up a bunch of data that comes up with them, and we want to restrict that data to only the fields that they have access to.
[00:00:42]
So what we can do is make sure we have our permissions hoisted up a little bit higher because we're going to be using them all the way up here instead of our code. Before we even filter down our data with our schema and everything like that, what I want to do is I want to get our restricted data. This is the data after we restrict what they have access to. So we're just going to say that that is equal to permissions.pickPermittedFields.
[00:01:02]
We're going to pass it in all of our checks, so document, we're going to be doing a create in this particular case. We're going to do our data that we want to pass along just like that, and our data is coming from up there. That is correct. Let me see if there's an error going on with this. So this new data we passed in doesn't always have to be a full object of a document. Sometimes we may only pass a content and a title, for example.
[00:01:26]
So instead, I want to restrict this to a partial object just like that. That should clean up our TypeScript errors in here, which it does. So now we're passing along that data that we want to be able to restrict this to. So essentially it's taking in the title, content, status, and is locked, and it's going to return to us a partial set of data that only contains the fields that the user has access to.
[00:01:46]
So if we try to pass up a status and the user can't modify the status, it will not include that in our new data. So with our restricted data, essentially any place we used our original data, such as here, we'll replace that with our restricted data, and everything else inside this section should stay the same. Now to test this before we move on to update, let's actually see if this works. So let's go and we're going to log out and log in as an author user, and we're going to attempt to create a new document with a title, doesn't matter what it is, content doesn't matter what it is, and I'm going to try to set a status of published, and I'm going to try to set this as a locked document.
[00:02:19]
Currently, they do not have access to update status or lock, they should not be able to do that, so when I create this document, it should remove those fields from my request and only save the title and content. And when I do create my document, looks like we get invalid data. Let me try setting it back to this, see if we still get invalid data, we do. So it looks like we have a little bit of a different problem going on.
[00:02:40]
Ah, yes, I know what that problem is. That is because our schema, so there's document schema, that's what's validating our data to make sure it's correct. Currently we're stripping out the status and is locked property because they don't have access to that, but our schema, if we look at that, says that these are required properties. So we just want to make these optional, there we go, and we'll set this one to optional as well.
[00:03:00]
That way if they get stripped out because the user doesn't have access to them, it won't throw an error. That should actually solve that particular problem for us. And here, our new document, this new document currently has some optional properties assigned to it, so we want to make sure here our status will default to a draft status if it doesn't exist. So we'll say result.data.status, or we'll default it to draft.
[00:03:30]
Same thing for is locked. We will default this to false if it's not defined, because by default it will not be locked. So now let's go ahead and test this, we have published, we have lock, when we create our document, you'll notice it creates it as a draft document that is not locked, because it took those fields that we didn't have access to, completely stripped them out of the request before we sent them to the database.
[00:03:49]
And the nice thing about doing all of this inside of a service layer is that we don't have to keep all of this in our head mentally when we're writing a brand new function. We just say, okay, I'm going to use the document service. I can pass it in whatever data I want, and this function will make sure to handle massaging that data down, making sure it's validated, making sure the user has all the permissions to do everything.
[00:04:09]
All of that will be handled automatically for us. Now, if we wanted to do the exact same thing for update, we could essentially do what we did right here. So moving into our update section, we want to get our restricted data, so I'll just kind of copy all this code, go into our update section. Right here, we're going to get our restricted data. We can pass in the actual document we're trying to do this on because when we're updating, we have an existing document to reference against.
[00:04:37]
There we go. That gives us our restricted data, and then we can use that restricted data down here for our parsing, and everything else should work perfectly fine. So let's test this as well before we modify our UI. I'm going to come in here. I'm going to try to change the status to publish and lock to locked again, and we'll change something else just so we can see if this worked or not. And when we save, you can see that the title updated properly, but you can see my draft status did not change and my lock status did not change as well.
[00:05:02]
So now let's make it so that when we go to these pages, we don't show the things the user doesn't have access to because it's bad UI design to do that. So inside of our code, we need to find that form, and I believe the form for this is in our components. We have a document form right here. You can see inside here we have two different form fields, one right here for status, and the second one is for is locked.
[00:05:23]
Essentially, we just want to wrap these so that we can only access them if we have the ability to modify these fields. So I'm going to create a variable called canModify, and it's going to have a property for is locked and status. So we're going to say is locked. If we can modify the is locked property, then I want to render out all the code for this form field right here, and this form field, if we look at it, is for that is locked property.
[00:05:47]
I then can do the exact same thing up here. This one is for my status, and again, copy that down, and if we look at this form field, you can see this is our status form field right here. So we're able to only hide and show those based on this canModify field, which we're going to be passing into this form because again, since we're using React and JSX, this is a client component. All the code on here runs on the client, so we need to pass our permission down into this component, which is what we're going to use this canModify object for.
[00:06:21]
So this canModify object is just going to be an object that contains a status boolean and it is locked boolean. There we go. That cleans up all the errors inside of here. Now all we need to do is everywhere we use this, we need to actually pass along that data because you'll notice anywhere we have our document forms, such as our new document page or edit document page, we need to pass along that canModify field which contains is locked, which we can just set to permissions.check.
[00:06:49]
We're doing a document and in this case is our new document, so we can pass in create, and we don't actually have a document yet, so we'll pass in undefined as the document itself and then we can pass in the field we want to check which is is locked. There we go, we can now copy that down, do the exact same thing for our status and make sure that this says status as well, and that cleans up any errors we have inside this section.
[00:07:14]
And if I just copy this canModify, and we do the exact same thing on our edit page as well, so we can come into here, scroll down to where we have that document form. Oh, this is our project page, sorry. Here we go, here's our edit page, there's our document form. We can just paste this directly in there, and in this case, since we're doing an update instead of a create, we can replace undefined with the actual document we're trying to update itself.
[00:07:38]
There we go, and that should solve all of our problems. If we go back to our UI immediately, you can see those fields I do not have access to have been removed. If I log in as an editor, editors have access to a couple more fields. So if I go to something that they can edit, you can see it looks like it's not showing up properly. I may have messed up in my controls when I set up their fields. Let's go to that section.
[00:08:06]
Editor permissions, edit content, title, and status. That does look correct. Let me just make sure it's not a caching thing. I'll do a refresh here. And let's check on the document because I may have made a typo in my form. Scroll down, status is for status, is locked is for that one. That's correct. The only other thing I can think of is on the actual edit page is locked is set up correct, status is set up correct.
[00:08:35]
Oh, these are for create, not update, because I'm on the update page. These should say update instead of create. The actual editor doesn't have create permissions for anything, which is why this is returning false. Now you can see though that I updated that properly. You can see I can change this to published, and of course we're getting an error. Let me just see if it's something to do with this.
[00:09:00]
Let's try that. Actually let's just refresh our page first. That could be part of the problem. Not quite. Let me take a look here. Oh, it's probably to do with our schema. Let me just double check our schema again. We have these set to optional, optional, that is fine. Let me give a second here. Go into our service. I just want to see what the data coming in is and what our restricted data is. Oh, I already see the problem.
[00:09:38]
So, if we look here, again, I did the same mistake, create, this should say update, is just a copy paste problem. I'm in the update function. The reason it was returning not the right data is because editors can't create anything, so it was essentially returned an empty object. Now if I actually click save, we should see it updated the status to publish, and finally, if I log in as an admin, you'll see they're not restricted from anything, if I actually click on the right link here.
[00:10:01]
Here we go, we'll click on edit. You can see they can modify anything. I could unlock this document and the lock has gone away from that document, so they have full permission for everything. So with that done, we have done all of our field level write permissions that we need to handle inside of our applications. We've implemented environment-based rules, that was super simple. I wish it was all that simple.
[00:10:20]
We then did our field-level permissions for reading as well as for writing. The final thing that we need to actually implement inside of our code is the automatic database filtering. Now this is something that based on our code is actually something that's not too hard to implement. It's just a little bit of complicated TypeScript code, and that's because we already have conditions that essentially go from one thing to another thing.
[00:10:41]
All we need to do is we need to modify our conditions from this JSON object to essentially a database query, and with Drizzle, it's not too complicated to do. And the nice thing about doing it this way is that we now have all of our permissions in one place because we don't have to worry about having permissions duplicated between our database and our permission handler, it's all going to be handled by that permission handler.
Learn Straight from the Experts Who Shape the Modern Web
- 250+In-depth Courses
- Industry Leading Experts
- 24Learning Paths
- Live Interactive Workshops