Wednesday, 19 December, 2018 UTC


Summary

Being of the main contributors of Strapi (the most advanced open-source headless CMS to manage your content with no effort.) I'm often asked on Slack:
Community member: Hey Cyril, do you have an ETA on that Administrators Roles Plugin? I really need that feature for my client...
Me: Man we're stabilising the framework and making it way more extensible than it already is, so honestly, I have no idea.
Well last weekend I was feeling the need of trying to find a pure front-end solution to hide plugins and content types depending on the admin's role so the Strapi user can answer to their clients needs.
The idea behind this article is to provide a quick way to hide some views for a specific user's admin role. However, since this is a front-end hack, I need to state that the back-end won't make any difference between the "sub-roles" set for the administration panel (so it's really not secure) and such logic might need to be applied in both the Strapi's UI and your client's site.

‼️
DISCLAIMER
We won't provide any support for bugs related to this article and since the codebase evolves really fast, this logic is really likely to change
(it won't be really stable if I make a minor modification into the admin).
👇
We also recommend to use a Mac/Linux computer to follow this tutorial.
We're currently encountering issues with Windows and our front-end stack.

Now that everything has been said, let's dig into some practical example!

Project setup

1️⃣ In a terminal window, clone the repository.
git clone [email protected]:strapi/strapi.git  
2️⃣ Then launch the setup.
cd strapi && npm run setup  
3️⃣ Open a new terminal window, create a new project and set the desired database.
strapi new blog --dev  
4️⃣ Start the server.
cd blog && strapi start  
5️⃣ In a new terminal window, launch the front-end server.
cd blog/admin && npm start  
6️⃣ Go to localhost:4000/admin.

API setup

In this tutorial we will create a blog that contains articles. Each article is linked to several tags and vice versa, one category is linked to several categories. To create these content types we will use the Content-type Builder plugin that is already installed in each generated project.

Creating the needed Content-Types

Go to the Content Type Builder plugin and create the following content-types.
👉 Article
  • name (string)
  • description (text): if you want the Rich Text Feature enabled visit the Advanced Settings Tab and select Display as WYSIWYG
Then, click on the Save button and the Content Type should appear in the left menu. Repeat the process for the others content-types.
👉 Tag
  • name (string)
  • articles (many-to-many relation with Article)
👉 Category
  • title (string)
  • articles (many-to-one relation with Article)
At this point, you should have 4 models displayed in the left menu. BTW, I'm always amazed by how fast Strapi is to create an API.
Now that the blog is created I'm going to explain my approach to hide and block some plugins depending on the admin user's role.

Approach

Currently, only admin users can access the administration panel, those admin users have all rights : they can create/delete/modify both Content Types and Data, set up permissions and modify the app's settings.
In order to hide some parts of the administration panel we need to add a field to the existing User Model like admin_layout that we're going to use to pimp up our UI. When the user will logs in, the front-end will retrieve this "sub role" and depending on it, will remove elements or functionalities from the front-end.
The created "sub role" will only affect the front-end, for the back-end those admin users will still have all the rights so you might need to make the same approach in your client application.

Modify the User Model

For my blog API I want to create three "sub roles" : admin, author and editor. To do so, go to the User model in the content-type-builer and add a new enumeration field called admin_layout.
👉 User
  • ... (previous fields)
  • admin_layout (enumeration, required): admin,author,editor.
Don't forget to visit to the Advanced Settings Tab, click on required and save the modifications. Finally, your model should look like:
We have now created a specific field admin_layout that is necessary for the admin to hide elements from the menu.
The only problem now is that the first registered user (you since you started with a fresh project) doesn't have this property so, we're going to modify the users-permissions plugin.

Updating the Users-Permissions plugin.

Since we created a dev project for the sake of the example we need to make sure the first registered user has the admin_layout "sub role" of type admin for your production one. So we need to set this default role from the front-end of the users-permissions plugin.
With this modification the first registered user will have the admin sub role.
At this point, we have just created our API and added a new field to the existing User model. We are now going to see how to hide some plugins and then, how to hide Content Types and views depending on the admin user's sub role.

⚠️  Don't forget to empty all your users and refresh your front-end  ⚠️
To do so, go to the Content Manager and click on the trash button.
Then restart your app and refresh your browser.

Part 1: Hiding the Plugins

In the first part we're going to tackle the question of hiding/removing some plugins depending on the user's role. Here what we want to do is delete a plugin from the UI only (it will still remain active in the back-end) and we will handle the special case of hiding the Users & Permissions one.

Back-End Setup

At some point we need to tell the administration which plugins to hide for a specific user "sub role". To do so, we need to create an adminLayout.js file where we will write the UI's permissions and that the front-end will retrieve. Since this is a pure front-end "hack" the back-end configuration is pretty easy.
1- Create the adminLayout.js file.

2- Send it to the front-end.
This step is the only back-end configuration that we need to do, the rest will only be a front-end hack...

♻️ Don't forget to restart your server ♻️
cd blog && strapi start

Front-End Setup

Now that our back-end is ready we need to undertake some complex front-end modifications. The first one is in the /admin folder. We are going to modify the following elements AdminPage, App, AppLoader, LeftMenuLinkContainer, Logout.
Modifying the App Container
Here we just need to make a minor update in order to tell the AppLoader container to stop showing a loader if a plugin is deleted from our store. Since there will be a difference between the number of plugins that the front-end should load and the ones that are loaded if we don't update this part the application will show a loader indefinitely.

Modifying the AppLoader Container
Before getting any further we have to update the shouldLoad instance of this container.

Modifying the AdminPage Container
In this section we're are going :
  • Create a new constant and an action to set our menu depending on the user's sub role
  • Update our current reducer with a new key adminLayout
  • Create a new selector so we can retrieve the adminLayout property in our saga
  • Update the saga in order:
    • To retrieve the created file from the backend.
    • To delete the desired plugin from the front-end.
  • Modify the way we load the plugins
1️⃣ Create a new constant and action in the AdminPage container

2️⃣ Update our current reducer with a new key adminLayout

3️⃣ Create a new selector so we can retrieve the adminLayout property in our saga

4️⃣ Update the saga in order to retrieve the adminLayout and hide the desired plugins

5️⃣ Modifying the way we load our plugins

At this point you may have noticed that we handle a special case we trying to hide the users-permissions plugin for a specific role. This is because if you delete this plugin from our main store the authentication won't be applied in our front-end so the user won't be able to make any request to the backend. In order to hide this plugin in the application we're going to update the LeftMenuLinkContainer component. We already handled the case of trying to access this plugin using the url since it will display the BlockerComponent.
Modifying the LeftMenuLinkContainer Component
The idea here is just to remove the link from the menu.

Modifying the Logout Component
After all this modifications, we're facing a new issue: how to reset all our modification if a user logs out and logs in with a new account? We have to "reset" our layout and the easiest way is to reload our application.

Part 2: Hiding Content Types

What if we want to hide some Content Types and maybe give access to the list view but prevent an admin user from creating a new entry?
To do so, we need to add new keys to the adminLayout.js file so the front-end will know which content types to show and which create views to block

Back-End Setup

Here we need to add two new keys to our adminLayout.js user's role : contentTypesToHide and contentTypeToReadOnly, you'll need to make the modifications and restart your Strapi server.

♻️ Don't forget to restart your server ♻️
cd blog && strapi start

Approach
In this part, we will not only hide content types from the left menu but also prevent some view from being accessed by using our BlockerComponent.
Before digging into some code we need to understand how the content types' links are displayed in the left menu. Here what happens is that when the content-manager is mounted in our application his bootstrap.js file is called and it inserts into our main store a plugin property containing the leftMenuSections which has all our content types' link. Then, the LeftMenuLinkContainer "search" this property in all our plugins to display the different links.
Like we did in the first part of this tutorial we will modify the AdminPage's saga so it updates these keys by removing the desired content types from one plugins's object. To make the front-end a bit more secure not only will we delete the content types from the left menu but also we will make sure a content type can't be accessed (if said so) by typing the url directly into the browser.
To do so, we will create an object ctmAdminLayout in the AdminPage's reducer that we're going to pass to the content-manager using the old React context API.
The admin is using the first context api and I will update it to the last one soon so you might need to make some further modifications.
Here are the elements that we need to modify:
  • The AdminPage container
  • The Content-Manager EditPage
  • The Content-Manager ListPage
Update the AdminPage
Since we're want to store a new property in the reducer we need to create a new constant, action and add it into the reducer.
1️⃣ Create a new constant:

2️⃣ Create a new action:

3️⃣ Update the reducer:
At this point nothing is happening in our application the User model is still displaying even though we have set it to be hidden in our adminLayout.js file. Like we did in the part one we are going to develop the logic in the saga.
4️⃣ Update the saga to remove the content types from the left menu:
Finally we need to pass the ctmAdminLayout object to the content-manager plugin.
5️⃣ Update the index.js
The code from above will only remove the corresponding content types from the left menu. We now need to modify the content manager to make the appropriate views unaccessible by setting directly the URL in the browser.
Update the Content-Manager plugin
As we did with the users-permissions plugin we are going to use the BlockerComponent to prevent a user from accessing a view that he can't.
As you may have seen I have introduced a contentTypestoReadOnly key in the adminLayout.js file it allows a user to just display the data in the ListPage container. So if this condition is met, we need to hide the create button first and then block the view if he tries to access a content type by URL.
In this part we only to update two containers in the content-manager:
  • The ListPage
  • The EditPage with the same logic:
1️⃣ Update the ListPage container:

2️⃣ Update the EditPage container:

♻️ Don't forget to refresh your front-end ♻️

Well, we did a lot of modifications but I think that you can extend this logic to fit your needs. As a reminder the code might need to be updated with the releases but it will be a temporary solution until we release the Admin & Permissions plugin.