In this chapter, we're going to go ahead and add access control to our CMS and also to purchased content. So only people who actually have an order will be able to see and load some special content. So we actually already added one type of access control, but I'm not sure if you have noticed. As always, confirm that you are on your master branch and confirm that you have merged everything you need. You can do that by running git status and then bun run dev.
So what I want you to do is I want you to go to your login screen and go ahead and log in with the user who is most certainly not an admin. So just a random user. And right now, if you go to this random user's dashboard, you're going to see that we have too much information here. This random user can access and create new users from the dashboard, new media, new categories, new products, new tags, new tenants, new orders, and new reviews. So we have no protection whatsoever what can be done with this CMS here.
And we have already implemented one type of access control here, which is which user has access to all tenants. So we have made it so that only the super admin has access to other tenants. So at least that's a good thing, right? So this is what we're going to do now. We're going to go inside of lib and we will create access.tsutil and then we are going to import user from payload types so we can import the type user here, and you might also want to import client user from payload.
And then expert const is super admin here and simply accept the user to be a type of user, client user or null. And inside of here return a boolean to check if user roles includes super-admin. Basically the same thing that we check for here. And you can always confirm in your users collection that you actually have the super admin option. So just go ahead and create this.
Now you can go inside of your payload config, and you can use the is super admin and pass in the user. And just make sure to import is super admin from your new lib access field. So we're now gonna go ahead and add these access controls to some other collections. You can see that in here I have outlined all the collections we have to go through. Let's start with users.
Inside of users here, we already kind of experimented with field access, simply by setting everything to true. We are now going to modify this. I'm going to preserve read values to be true, But create will only be available for super admins. So we can extract the request from here, and then call our is super admin from lib access, and simply passing request.user inside. So now only super admins will be able to create and update the array field access.
And we can do the same thing for the tenant field access. So right now, only super admins can modify the tenancy. So if you go and click here, you should no longer be able to change your tenants here. You can see that this button is now disabled for me. So if I go ahead and comment these out, Actually, maybe I need to revert them.
I don't know if this is enough to enable it. But yes, you can see how now I can change the tenant or I can click here or I can just create a new tenant. So nothing that we should actually be able to do because we are going to restrict our shop, I mean, our platform to only allow one tenant per user. You can, of course, explore this later and maybe allow 50 tenants per user if you want to. So that's why we added that.
And now we also have to introduce some access controls here in the actual users collection. So I don't see the point in non-admin users to even see the users collection here. And they most certainly shouldn't be able to create new users from the CMS. So because of that, we're going to go ahead and only allow the read here. So they will still be able to read access user information, specifically their own user information from the CMS.
But if they are attempting to create new users, we are going to restrict this by confirming that the user trying to do that is a super admin. So now when you refresh, you can see that you no longer have a way to create new users unless you are a super admin. But when it comes to delete, we actually want to do the same thing, right? So is super admin request user? The reason I'm setting delete for the super admin is so that we have a way to carefully delete the user, their products, and also you know associated reviews and orders.
We have to be careful with that, right? So that's why for now I'm only gonna allow super admins to delete users. But when it comes to updating the user, we are okay with allowing the user to do that themselves. So I'm going to extract request and the ID. And in here, I'm first going to check if is super admin request.user in that case we can just return true basically it's okay you have all the access you need but if you are not a super admin I will still do one more check which will basically be return if request user?id is identical to the id that you are trying to edit.
This basically means you are trying to update yourself. So if someone goes to their profile and wants to change their username or their email, we are going to allow that. So if you don't do this, basically, imagine that we just added return is superadmin request that user. And refresh now. You can see that you can no longer update your information here.
But that's not something we want. We want to allow the user to update that if they are updating themselves. But now I see this button called force unlock so I'm not exactly sure if I want that. So I will explore if there's a way to change this. But you know this is how you would allow the user to modify themselves.
The force unlock is most likely to forcefully verify the user. So what we're going to do now is we're going to go ahead and do one more thing, which is remove the users entirely from here and here. So only the admin should see them there. Normal users should not be able to see that, right? They have no reason to access that.
So in here we do the reverse logic. If not, is superadmin. So now when you refresh, the users should be completely gone. And one more important thing to do here is the role field, modify the access here, update, and go ahead and extract the request and pass in is super admin request dot user. So now if you go back to your profile here you will see that you can no longer change your role.
There we go. So there probably is a way to also block the force unlock button the same way we just blocked the role whilst allowing the user to modify everything else about themselves. Great, so we now completed the users collection access control. Now let's go into categories. So categories in here, I don't think there is anything normal users should be able to do besides read them.
So I'm going to create an access here. And can I just copy what I have here? Read create update, Let's just add it here. And let's just import super admin from lib access. And this will also be true for read, create, update, and delete.
Like this. There we go. And can I also add delete here? I'm not sure in the tenant build. No, it doesn't have delete.
OK. So for the categories, only allow read to be true. Everything else should be for super admin. And more so, in fact, so now if you go to categories, you should no longer be able to add new categories. You can see the button here is disabled.
But the same as the user field, there is absolutely no reason someone who is not an admin should see that in their dashboard. Only the super admin will be creating new categories. No need for a normal user to do that. Great. So now we protected the categories so we can mark this as complete.
Now let's go inside of our products collections here. So in here let's go ahead and do the following. When we create the access field, I'm going to allow read to be true. I'm going to then add a create method to do the following. We will extract the request from here.
Then as always, I'm first going to check if is superadmin request user. In that case, immediately return true. But now what I want to do is I want to extract the tenant from here so request user tenants and now you might be wondering how can we know if this will be populated or not? By default, in here, the depth is set to two. So we actually have access to the tenants object.
So you can add tenant like this. And then just return boolean tenant and in here you want to do stripe details submitted. So what does this mean? First of all, we have to fix this error by adding as tenant here from our payload types. Basically what we have done now is we have restricted tenants from creating products unless they have submitted their Stripe details.
So you can see that the tenant that I have here, Antonio, doesn't have Stripe details submitted, which means that they should not be able to create new products. So this is the part that's going to come in handy for our Stripe Connect feature. So when a user goes to edit their tenant here, they're going to have to submit their Stripe details and Only then are they're going to be allowed to create new products. Obviously, now we can easily bypass that. So if you need your user to create the product, you can just comment this out.
So you might be wondering, what about other access rules here? You actually don't need the explicit read here at all. You don't have to worry about other access rules, because we handle that in the payload config here. We have connected the products with the tenants. So all the rules are already handled here.
And we don't have to actually modify anything else. But something that I will do is go inside of the products here. And at the end, I want to add a name, content. And a type will be text area. For now, I'm going to add to do change to rich text.
The reason I don't want to immediately add it to rich text is because we have to install a package. So I just want to bother you with that now. And you can add a description here so the user knows what this is. Protected content only visible to customers after purchase. Add product documentation, downloadable files, getting started guides, and bonus materials.
Supports markdown formatting. So the user now knows that when they create new product, they will be able to add this content, which will later only be visible to customers after purchase. Great. Obviously, we are later going to change this to rich text, and we will also change the description to rich text, right? The description can also be formatted in a better way.
And That is actually it for the products access control. Now let's go into tags. So tags will be identical to categories. There is no reason anyone who is not a super admin should be able to see them here, like this, and just import is super admin. So now by default, when you refresh your dashboard, you should no longer be able to create new tags.
And now we're just going to also hide them here. There we go. So when you refresh, this should no longer exist for someone who is not an admin. And That's it for tags. And now we're just going to revisit our tenants field.
Just to confirm that we didn't have to add anything special here, but I think we do have to do some things here. So let's add an access here. And for Create, I'm going to go ahead and check if is super admin here, so request user. And for Delete, I will do the same thing. And for the update, I actually want to allow the user to do that.
But I'm not exactly sure if I have to do it this way. I think that by default, each user should be able to modify their own tenant. Let me check. Yes, by default, they are completely allowed to modify their tenant from here. So I think that's fine, right?
I just don't want them to be able to create new tenants from here. And I don't want them to be able to create new tenants from here. And I don't want them to be able to delete tenants. You can see only edit should be available for them. And we shouldn't allow users to edit these two.
So let's go ahead in here. So we actually don't need a read-only here. Instead, we can do proper access here. So instead of admin, let's use access for this. And Edit or update here will extract the request and only allow it to super admins.
Normal users will populate this by submitting their Stripe details. But super admin can come in and the super admin can then modify it manually if they need to. And in here we can add the description here, Stripe account ID associated with your shop. Simply so the user knows what this actually is. So you can now go here, go to your tenant, and there we go.
You can no longer modify these fields at all, and you see a description of what they are. So stripe account ID associated with your shop. And in here, you cannot create products until you submit your stripe details. We are later going to add a button to submit the stripe details. Great.
So I think that now We have good control here and no one should be able to do anything they shouldn't be able to do, right? So I think that marks it for the tenants. And now let's go ahead and review our orders. So when it comes to orders, I think you kind of have a choice here, because maybe you want to allow the owner of the shop to look at all the orders that they have. But I think that maybe then you would have to, the best way to do that would be to add the orders to the multi-tenant plugin, but then you would also have to be careful, and every time you create an order, you would need to pass the associated tenant ID in there.
So decide for yourself if that's something you want or not. So what I'm going to do here is I'm going to add the access control, and I'm just going to block everything inside. So there's no reason that someone who is not an admin should manipulate orders in any way through the CMS. At least that's what I will decide for my platform. Even for read, there is no need for them to read anyone's orders.
There we go. So non-admin users now so far can only see products, tenants, their own tenants, and they seem to be able to see anyone's reviews. So let's go ahead and modify this as well. But I just want to go through orders to ensure that all of these here look like this. If you want to, you can add a description here.
Checkout session associated with this order or with the order, stripe checkout session associated with the order, so they know what this is, right? So they can easily find it in the Stripe dashboard if they need it to. So I think that marks the orders as complete. And now let's go to the reviews. And the reviews can have the exact same rules as the orders.
Why? Well, because we have a different UI for reviews. We have the library, right? So there's no need for reviews to be visible through the CMS. So let's just add this here and completely hide it from anyone who is not an admin.
There we go. And now, the only thing that's left here is media. So here's what I'm going to do in regards to media. I'm completely OK with anyone reading this, right? And also creating media.
But I don't want media to be here in the collection. I think it's just confusing people this way. So what I'm just going to do is add admin, hidden, extract the user. And if it's not super admin, it's not going to be visible in the sidebar. So media will be something that anyone will be able to modify, But I just don't want to accept delete.
Let's do that. Request dot is super admin request user like this. There we go. So now as you can see, the only thing available to someone who is not an admin are their own products because only this tenants products will load and the tenants field. So you can decide if you want to show the tenants or not.
I think it's cool to show it because this is technically your shop, your store, right? So you want the user to be able to go there so that they can change this to super cool store, right? And save this because later on when they go to localhost 3000 that is going to be the name of their store right so if I click here it should now read that new name here Let's just wait a second for this to compile. There we go, super cool store. So I kind of feel it's okay to allow the tenants field here so they can quickly go here.
Now I don't know how you feel about allowing users to change subdomains. So if you want to you could prevent that from happening here. Let me just go inside of my tenants. If you want to, you can just copy one of these, go to slug and add access control here. And perhaps only allow the admin to update the slug.
Now no one besides the admin can update the subdomain. The reason you might want this is changing subdomains can often lead to spam or things like that, and you probably need some kind of verification process in place, not to mention the conflicts and all that. So perhaps it would be better to just tell the user, which we already do on the registration page, we tell the user, whatever you enter as your username will be your store's subdomain, right? So if you want to, you can disable that field and only allow the user to change the store name and the image of their store. Great.
So I think that's it for the access control. And now let's talk about this, restrict purchased content. So inside of our products here, we have created the new content field here. And we don't have to add any access here because products by themselves are associated with the tenant, which means that only the author of the product will be able to see this field. But there is a problem.
Every time we do a slug products, Or do I do them like this? Collection products. I think this is just the procedures. So we have to take a look at all the procedures where we actually fetch our products. So I think we should primarily focus on the procedures in the actual products module.
Let's go here first. In here, we should have the get one procedure. And we should also have the getMany procedure. So what you should do is you should go into getMany, go where your data is, and in here you can add select content and just set it to false. So this way it will not leak through the API.
And you can do the same thing inside of your get one here. So let me just find the product. There we go. And just select content set to false. So now the products router will never leak the premium content.
Let's go ahead and see where else we fetch the products. So we just fixed the products modules. Let's look at the... So in the reviews, I think it's completely fine for us to load the content here because the only person who will be able to access this is the person that has actually purchased, that has actually created that review. So I think in this case, it's completely fine.
But yeah, I think nowhere else is it dangerous, because reviews is something for authenticated users and users that have purchased products. Library is for that as well. The only thing is the checkout here. Let me just see. So this checkout is for the purchase.
So we are looking for the products here. Yeah, perhaps here you could add select content false just so it doesn't leak somehow. But I don't think this can leak in any way. We don't return this back. I don't think you need to do that here.
The only thing we return back here is the checkout URL. I don't think there's any way this can leak, right? So I think we're actually completely good to go here. We have protected the main procedure which users look at, which is on the actual home page. So let me see if we can check that out or not in the Network tab now.
Basically, when someone loads this, I'm trying to see if I can load. But the only thing I can, since we are using prefetching, it's already loaded. But I think we can trigger a refetch somehow by changing the window tab. And then sometimes that causes a refetch. Oh, looks like not now, but yeah, I think we have protected this.
Let's see, result data, Is it this? There we go. And in here, you can see how we don't have the content anywhere because we are protecting it. And then what we can do is we can go inside of our product view, specifically in the library modules, and then you have something new here. So you should now have access to the content.
So instead of no special content here, you can now go ahead and check if we have data.content, and then in here, you can add a paragraph, rendering data content, or render that there is no special content added, if this was a donation or something like that. Later on, we're going to render the content through a proper rich text element, which will support uploaded files and anything like that is going to look even better. Great. So I think that we achieved what was the goal of this chapter, which was to implement access control and restrict CMS access. And if you want to check, you can now log out from here and enter your admin.
And your admin should now have all the superpowers. They can change to any tenant, they can create everything they want, They can look at all orders, and they can also help any user they need. So you can see that in here, I can change the slug of my store. Well, it's not my store because I'm the admin, so someone else's store. The only thing that we still don't allow is the Stripe account ID.
But I think we should allow people to change this as well. I mean, admins, exclusively admins. So let's go quickly into tenants here. So we are saying that if is super admin, you should be able to update it. So I'm not sure why it's not allowing me to update at this point.
Oh, because we've set it to read only. So remove read only. Let me search. Do we have any other read only? We don't.
Okay. There we go. So now the super admin can decide if Stripe details are submitted or not. I don't recommend checking this manually, right? But if you just want to help your user or something, that's how you can do it.
There we go. So now our super admin has actual superpowers, whereas all other users, let's just confirm by adding another user here. There we go. You can see this other user can't even create new products, right? The reason they cannot create new products is because they still haven't submitted their Stripe details.
So that's what we're going to do in the next chapter. We are going to go ahead and actually enable submitting the Stripe details here so that then users are enabled to create new products and then we can do some proper fee sharing throughout our platform. Amazing! So let's go ahead and do 24-axis control branch git checkout branch 24 access control git add git commit 24 access control and git push uorigin 24 access control Once you have pushed and switched your branch, confirmed with the graph, you can go to GitHub and open up a new pull request here. And now let's go ahead and review our changes one more time before we merge.
And here we have the summary. We have some new features. Enhanced security with role-based access control limiting sensitive operations. Create, read, update, delete to privileged users, in our case, super admins, mention of the additional content, and improved contextual information in admin interfaces. As always, we can see a more in-depth walkthrough right here.
And two sequence diagrams, one representing how our access control or role-based access control works, and the one on the bottom is our dynamic product content, which should only be rendered to people who have purchased a product. In here we have some comments left. This one is suggesting to include the content inside of here simply because it recognized that we conditionally check if a user has purchased this. But we don't have to do it here because the reason we check if someone has purchased instead of our products procedures is because, let me just find what we changed. The reason we do that is simply so we can showcase view in a library button.
So that's the only reason we are doing that instead of here. So there's no need to change this to true and then conditionally do it like that. It's completely fine because the way we render the product content is in the library page. In here it is suggesting that we open up the read access to other users but we don't want that. I only want the admin to see that.
So I'm gonna go ahead and confirm this merge 24 access control. As always I will confirm that it's here there we go and now let's go ahead and go back and git pull origin and then your main or master branch git status and there we go everything is merged and up to date that's it for this chapter amazing job and see you in the next one