В аутентифицированных интерфейсных приложениях мы часто хотим изменить то, что видно пользователю, в зависимости от его роли. Например, гостевой пользователь может видеть сообщение, но только зарегистрированный пользователь или администратор видит кнопку для удаления этого сообщения.

Управление этой видимостью может стать кошмаром для UI-приложения. Вероятно, вы уже писали или видели такой код раньше:

if (user.role === ADMIN || user.auth && post.author === user.id) {
  <button onClick={this.deletePost.bind(this)}>Delete</button>
}

Этот код распространяется по приложению и обычно становится большой проблемой, когда владелец продукта запрашивает добавление дополнительных ролей в приложение. В конце концов вам нужно будет пройти все такие ifs и добавить дополнительные проверки.

В этой статье я продемонстрирую альтернативный способ реализации управления разрешениями с помощью аккуратной библиотеки, которая называется CASL. Это упрощает управление активными разрешениями пользователей и позволяет переписать предыдущий пример примерно так:

if (ability.can('delete', post)) {
  <button onClick={this.deletePost.bind(this}>Delete</button>
}

Вы впервые слышите о CASL? Вы можете прочитать Что такое CASL?.

Демо-приложение

В качестве демонстрации я сделал хорошо известное приложение TodoApp.

Правила разрешений для этого приложения:

  • пользователь может читать и создавать любые задачи для любого пользователя;
  • пользователь может обновлять или удалять только те задачи, которые ему назначены (опция «я» в раскрывающемся списке).

Я использовал React.js с CASL, чтобы упростить реализацию и масштабирование этих правил - на случай, если в будущем будут добавлены другие операции или объекты. Если вы хотите увидеть результат, посмотрите это репозиторий на Github.

Теперь давайте углубимся в детали реализации.

Обновление: теперь есть отдельный пакет, который позволяет без проблем интегрировать CASL в любое приложение React. См. Обновленный репозиторий для примера

Обновление №2: выпущен CASL 4.0, см. «« CASL 4.0. Что внутри?""

Определение разрешений пользователей

Прежде всего, давайте определим права наших пользователей в файле src / config /ility.js.

Давайте немного разберем этот код:

Первым аргументом, переданным методу define, является объект options. Когда CASL проверяет объект для определения разрешения, ему необходимо знать тип объекта, на который он смотрит. Для этого нужно передать свойство subjectName вoptions. В нашем случае мы ищем настраиваемое свойство __type, которое определяет тип объекта (по умолчанию CASL ищет свойство modelName или name в свойстве constructor объекта).

Второй аргумент - это функция DSL, которая принимает 2 аргумента: can и cannot (мы не используем cannot в этом примере). Мы определяем права пользователей, вызывая can функцию. Первый аргумент can - это действие (или массив действий). Обычно это будет действие CRUD, но вы можете указать все, что подходит для вашего приложения (например, вы можете определить can('visit', '/protected/path') и позже проверить ability.can('visit, '/protected/path')). Второй аргумент - это тип объекта, в нашем случае Todo, для которого применимо это правило.

Обратите внимание, что во втором вызове функции can мы передаем третий аргумент, который называется условиями. Это используется для проверки того, совпадает ли указанное значение assignee со свойством assignee объекта, который мы предоставим при проведении теста (например, ability.can('read', todo)). Если бы мы этого не сделали, любое задание могло быть обновлено или удалено любым пользователем, а не только исполнителем.

Проверка разрешений в React

Сначала вы можете подумать, что интеграция будет довольно простой: просто импортируйте созданные ability, добавьтеifs вокруг некоторых компонентов в render функции, и все готово. Это может сработать, но только до тех пор, пока вы не измените правила умений.

Почему? Потому что React повторно отображает компонент только в случае изменения его состояния или свойств. В нашем случае нам нужно повторно визуализировать компоненты при изменении ability правил, потому что нам нужно будет обновить правила разрешений, когда пользователь либо входит в систему, либо выходит из системы (или входит в систему под другой учетной записью).

Итак, путь - создать собственный Can компонент. У этого компонента будет два свойства:

  • run, который принимает имя действия;
  • on, который принимает субъект (субъект может быть строкой или объектом).

И одно государственное свойствоallowed, которое кэширует чек ability.can(this.props.run, this.props.on).

Если у пользователя есть разрешение на выполнение указанного действия над объектом, наш компонент будет отображать переданные дочерние элементы. В конце концов мы сможем проверять разрешения следующим образом:

Чтобы отслеживать обновления разрешений, мы можем использовать событие update экземпляра Ability. Мы можем сделать это с помощью хуков React: componentWillMount и componentWillUnmount:

Мы добавили setTimeout вызов, потому что CASL испускает update событие перед обновлением разрешений. В версии 1.1.1 CASL можно будет использовать событие theupdated.

Осталось только обернуть компоненты пользовательского интерфейса Can проверками, и все.

Чтобы поиграть с логикой разрешений, просто откройте Инструменты разработчика и введите:

  • ability.update([]) для сброса всех разрешений (т.е. в режиме только для чтения);
  • ability.update([{ subject: 'all', actions: 'manage' }]), чтобы получить полный доступ для управления всем;
  • ability.rules, чтобы получить список текущих способностей;
  • для получения дополнительной информации о возможных параметрах и настройке ability прочтите официальную документацию и / или задавайте вопросы в чате gitter.

Заворачивать

Благодаря этому у нас есть действительно хороший способ управления разрешениями пользователей в приложении React.

Я считаю, что <Can run="delete" on={this.props.todo}>...</Can> гораздо удобнее и понятнее, чем:

{ (user.role === ADMIN || user.auth && todo.assignee === user.id) &&
  <button onClick={this.deleteTodo.bind(this}>Delete</button> }

С помощью CASL мы можем более четко указать, что мы делаем. Кроме того, такие проверки обязательно будут использоваться в другом месте нашего приложения, и именно здесь CASL может нам помочь.

Просто напишите, чтобы прочитать и быть явным в своем коде. 😉

Заинтересованы в интеграции с другим фреймворком?

CASL имеет дополнительные пакеты для основных интерфейсных фреймворков, ознакомьтесь со статьями ниже: