How To Set Up the Advanced Permission Pro Extension
Published: 2024-05-07 20:01:12

The Advanced Permission module is a Pro extension that adds more granular control over content permissions. It provides the ability to create custom groups and assign them to users directly in the admin UI. 

A group is a set of rules that specify how users can create, edit, delete, and publish content, including creating new users and groups. The module provides granular control, allowing admins to give a group Create, Edit, Delete, and Publish permissions for each piece type on the site. Those four core permissions can be extended with new custom permissions.

The Advanced Permission extension also enables admins to give groups and individual users granular per-document permissions on specific pages and pieces.

**NOTE:** The name of the npm package of the Advanced Permission module is `@apostrophecms-pro/advanced-permission`. 

## Prerequisites

The requirements for setting up the Advanced Permission module are:

- An Apostrophe 3+ application: If you don’t already have one, make sure you meet the requirements and then follow the instructions in the development setup guide.
- An Apostrophe Pro or Apostrophe Assembly subscription: To gain access to the Advanced Permission module, you first need to join Apostrophe Pro or Apostrophe Assembly.

## Installing the Advanced Permission Module
After joining Apostrophe Pro or Apostrophe Assembly, you'll be added to the `@apostrophecms-pro` npm organization. This gives you the ability to install the Advanced Permission module in your Apostrophe project. 

**WARNING:** If you try to add `@apostrophecms-pro/advanced-permission` to your project's dependency before being added to `@apostrophecms-pro`, you’ll get the following error:

```
@apostrophecms-pro/advanced-permission' is not in this registry.
```

`@apostrophecms-pro/advanced-permission` is a private npm package. You must authenticate in npm before installing it. The recommended authentication method changes depending on whether you’re in a development or production environment.

**Development Setup**
In a development environment, run the following command to start the npm authentication procedure:

    npm login

This will produce an output as below:

    Login at:
    https://www.npmjs.com/login?next=/login/cli/<NPM_HASH>
    Press ENTER to open in the browser...

Press ENTER to open the npm login page in your default browser. Type in your credentials, sign in, and return to the CLI.

After logging in successfully, you’ll receive the following message: 

    Logged in on https://registry.npmjs.org/

**NOTE:** To keep the session alive, npm will create a global `.npmrc` configuration file containing your access token in the following line:

    //registry.npmjs.org/:_authToken=<YOUR_NPM_ACCESS_TOKEN>

`cd` to your project folder and then install the Advanced Permission Pro extension with the command below:

    npm install @apostrophecms-pro/advanced-permission

npm will use the authenticated URL in `.npmrc` to download the private `@apostrophecms-pro/advanced-permission` package. It will then install it as per usual.

**Production Setup**
In a production environment, authenticate npm commands by setting up a granular token related to the `@apostrophecms-pro` organization. Follow the official guide for guidance. 

After setting up an npm granular token, add a `.npmrc` file to the root folder of your Apostrophe project. Initialize it with the following line:

    //registry.npmjs.org/:_authToken=${NPM_TOKEN}

When launching an npm command, `${NPM_TOKEN}` will be replaced with the value read from the `NPM_TOKEN` environment variable. That URL will be used to retrieve packages from the npm registry as an authenticated user.

**WARNING:** Adding a local `.npmrc` file will override your global `.npmrc` file npm created on `npm login`. To avoid authorization issues while installing private packages from the `@apostrophecms-pro` npm organization, set the `NPM_TOKEN` environment variable on your local machine when working with a project that has a `.npmrc` defined as above. You can do that in your `.bashrc` file using `EXPORT NPM_TOKEN=``"``<YOUR_PERSONAL_NPM_TOKEN>``"`

In the production server, set the `NPM_TOKEN` env to the value of your npm granular token:

    export NPM_TOKEN="<YOUR_NPM_GRANULAR_TOKEN>"

When launching `npm install`, the production environment will now be able to install the `"@apostrophecms-pro/doc-template-library"` dependency in `package.json`.

## Enable the Module in Apostrophe
Enable the Advanced Permission extension by adding the following two modules to the `app.js` file:

    // app.js
    
    require('apostrophe')({
      shortName: 'my-project',
      modules: {
        // other modules...
    
        // enable the Advanced Permission extension
        '@apostrophecms-pro/advanced-permission-group': {},
        '@apostrophecms-pro/advanced-permission': {}
      },
      // remaining configs...
    });

**NOTE:** To use Advanced Permission in a multisite project, you can add the two modules outlined above to both the `site/index.js` and `dashboard/index.js` files. Adding the modules to the two files will enable the Pro extension for both the dashboard and all individual sites. It’s also possible to enable the extension only for the dashboard on individual sites. Before starting to use the Advanced Permission module in the dashboard, make sure the `privateDashboards` feature is set to `false`. This setting won’t affect individual sites.

On the first run of your project after enabling the Advanced Permission module, some database migrations will automatically occur. These create a group for each role found in existing users and link them to the group corresponding to their `role` field.

After adding the Advanced Permission extension, a “Groups” item will appear in the top left menu in the admin bar.

## Adding Custom Permissions
In addition to the Create, Edit, Delete, and Publish core permissions, new custom permissions can be defined through the `permissions` object in a piece-type `index.js` file for an individual piece or in `@apostrophecms/page-type/index.js` for all pages. You can’t define custom permissions in individual page types.

Much like the `fields` object, the `permissions` object takes an `add` property. This accepts permission properties with objects having the following three properties:

- `label`: A string that describes the new permission to the user. It determines what is shown in the group and per-document permission grids.
- `requires`: An optional string with the name of an existing permission or an object with multiple permissions (e.g., `requires: { $or: [ 'edit', 'create' ] }`). It determines whether the new permission is dependent on any other permission in the grid. For example, `requires: 'publish'` would require the admin to select the "Publish" permission for the document or document type before they could select the new permission.
- `perDoc`: An optional boolean to define whether the new permission should appear in the user and group per-document permission matrices. The default value is `false`.

For example, you can define a custom `decriptionField` permission with:

    module.exports = {
      // ...
      fields: {
        // ...
      },
      // ...
      permissions: {
        add: {
          decriptionField: {
            label: 'Description',
            requires: 'publish',
            perDoc: false
          }
        }
      }
    };

**TIP:** By using `permissions` in an Apostrophe core module at the project level, you can add a new custom permission to multiple document types. For example, extending the `@apostrophecms/piece-type` module with the `permissions` object would add the custom permissions to all pieces.

**`editPermission`: Limiting Access to a Single Field**
After defining a custom permission, you can assign it to a specific field of a piece type by using `editPermission`. This schema field property takes an object with the following two properties:

- `action`: A string with the name of one of the built-in permissions (e.g., `'``create``'`) or a custom permission.
- `type`: A string with the name of the module that permission is associated with. For core modules, make sure to prefix the module name with `@apostrophecms/` or `@apostrophecms-pro/`.

For example, you can assign the custom `decriptionField` permission to the `description` field of a `product` as below:

    module.exports = {
      // ...
      fields: {
        add: {
          description: {
            type: 'string',
            label: 'Description',
            textarea: true,
            editPermission: {
              action: 'descriptionField', // custom permission
              type: 'product'
            }
          },
          // other fields...
        },
      },
      // ...
    };
    

Only users who have been granted the Create and/or Modify permissions as well as the `descriptionField` permission will now be able to edit the description of `product` pieces.

**NOTE:** `perDoc: true` isn’t compatible with the `editPermission` feature. To grant a user per-document custom permission on a given field of a piece, follow this procedure instead:

1. Define a custom permission with `perDoc: false`.
2. Use `editPermission` to assign the custom permission to the desired field of the given piece.
3. Define a group with the selected custom permission for the given piece.
4. Assign the group to the user.
5. Grant the user per-document permission to Modify the documents of the given piece.

A complete example: defining the custom “Pricing” permission
Suppose your project has a `service` piece. You want to add a custom “Pricing” permission so that only users with this permission can edit the price of services on your site. That can be achieved with the `permissions` and `editPermission` objects in `modules/service/index.js` as below:

    module.exports = {
      extend: '@apostrophecms/piece-type',
      options: {
        label: 'Service'
      },
      fields: {
        add: {
          title: {
            type: 'string',
            label: 'Title'
          },
          description: {
            type: 'string',
            label: 'Description',
            textarea: true
          },
          price: {
            type: 'float',
            label: 'Price',
            editPermission: {
              action: 'pricingField',
              type: 'service'
            }
          }
        },
        group: {
          basics: {
            label: 'Basics',
            fields: [
              'title',
              'description',
              'price'
            ]
          }
        }
      },
      permissions: {
        add: {
          pricingField: {
            label: 'Pricing'
          }
        }
      }
    };

**Customized permission checks**
`editPermission` is not the only way to take advantage of custom permissions. You can also verify if a particular user has a custom permission (including per-document permissions) when coding your own routes and methods server-side with the following line:

    self.apos.permission.can(req, '<custom_permission_name>', doc)

The function returns `true` if the user has the custom permission, `false` otherwise. This approach opens the door to custom use cases involving permission verification.