Реализация в приложении ролевого управления доступом (RBAC) гарантирует, что только авторизованные пользователи получат разрешение на применение определенных функций и данных.

Из этой статьи вы узнаете, как реализовать RBAC в приложении Node.js и Express с помощью Permify — сервиса авторизации с открытым исходным кодом, позволяющего легко создавать разрешения для любого приложения и управлять ими.

Настройка проекта Node.js Express

Быстро создать скелет приложения для проекта Express.js позволяет инструмент express-generator. Чтобы начать работать, выполните следующие шаги.

Шаг 1. Установка express-generator

Если вы используете Node.js версии 8.2.0 или более поздней, запустите генератор приложений с помощью команды npx:

npx express-generator

Для более ранних версий Node можете установить генератор приложений как глобальный пакет npm:

npm install -g express-generator

Шаг 2. Создание приложения Express

После установки express-generator можно создать приложение Express. Отобразите параметры команды с помощью опции -h:

express -h

Вы увидите доступные варианты генерации приложения Express.

Например, чтобы создать приложение Express с именем permify-rbac-app с помощью движка представления Pug, можете выполнить команду:

express --view=pug permify-rbac-app

Эта команда создаст в текущем рабочем каталоге папку permify-rbac-app с необходимыми файлами и папками для приложения Express.

Шаг 3. Установка зависимостей

Перейдите в только что созданный каталог приложения Express:

cd permify-rbac-app

Установите зависимости проекта с помощью npm:

npm install express && npm install @permify-node

Шаг 4. Запуск приложения Express

На MacOS или Linux можно запустить приложение с помощью следующей команды:

DEBUG=permify-rbac-app:* npm start

Шаг 5. Получение доступа к приложению

После запуска приложения Express можете получить к нему доступ в браузере по ссылке http://localhost:3000/.

Структура каталогов

Созданное приложение Express будет иметь следующую структуру каталогов:

permify-rbac-app/
├── app.js
├── bin/
│ └── www
├── package.json
├── public/
│ ├── images/
│ ├── javascripts/
│ └── stylesheets/
│ └── style.css
├── routes/
│ ├── index.js
│ └── users.js
└── views/
├── error.pug
├── index.pug
└── layout.pug

Эта структура включает основной файл приложения (app.js), конфигурацию сервера (bin/www), маршруты, представления, публичные активы и файл package.json с зависимостями проекта.

Теперь, когда проект Express.js настроен, перейдем к реализации RBAC в приложении Express.

Реализация RBAC в Node.js и Express

Базовая реализация ролевого управления доступом (RBAC) в приложениях Node.js и Express включает несколько шагов. 

Шаг 1. Проектирование модели RBAC с помощью Permify

Permify — сервис авторизации с открытым исходным кодом, который позволяет разработчикам создавать системы авторизации. С помощью Permify вы можете моделировать авторизацию, создавать центральную службу авторизации в своей среде и выполнять проверку доступа из приложений и служб.

Для этого Permify предоставляет клиентские SDK, которые можно добавить в промежуточное ПО для отправки авторизационных запросов, таких как проверка доступа.

Permify предлагает мощный предметно-ориентированный язык (DSL) для определения ролей, разрешений и отношений. Permify Playground позволяет выполнять эксперименты и визуализации модели RBAC.

Теперь разработаем простую систему авторизации на основе файлов, в которой пользователи в вашей организации смогут получать доступ к документам в зависимости от их роли.

Различные роли, такие как Admin (администратор), Manager (менеджер) и Employee (сотрудник), могут иметь разные уровни доступа к просмотру, редактированию и удалению файлов.

Определение ролей и разрешений с помощью DSL Permify

Ниже приведен пример схемы DSL Permify для модели RBAC:

entity user {} 

entity organization {
// роли
relation admin @user
relation member @user
relation manager @user
relation agent @user

// разрешения на доступ к файлам организации
permission view_files = admin or manager or (member not agent)
permission delete_file = admin

// разрешения на доступ к файлам поставщика
permission view_vendor_files = admin or manager or agent
permission delete_vendor_file = agent
}

Роли и разрешения:

  • Роли. В схеме определены роли для сущности организации, включая admin (администратор), member (член организации), manager (менеджер) и agent (агент). Эти роли определяют уровень доступа и разрешений, которыми обладают пользователи в организации.
  • Разрешения. Такие действия, как view_files (просмотр файлов), edit_files (редактирование файлов), delete_file (удаление файлов), view_vendor_files (просмотр файлов поставщиков), edit_vendor_files (редактирование файлов поставщиков) и delete_vendor_file (удаление файлов поставщиков), определяют конкретные разрешения, связанные с каждой ролью. Например, только администраторы могут удалять файлы организации, в то время как менеджеры и члены организации получают разные уровни доступа.

Типы ресурсов:

  • Файлы организации.
  • Файлы поставщиков.

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

Теперь, определив схему RBAC, перейдем к настройке локального сервера Permify.

Шаг 2. Настройка локального сервера Permify с помощью Docker

Docker играет важную роль в данной настройке, предоставляя контейнерную среду.

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

Далее рассмотрим шаги, необходимые для настройки сервера Permify с помощью контейнера Docker.

Запуск сервера Permify посредством контейнера Docker

  • Откройте окно терминала и выполните следующую команду, чтобы извлечь Docker-образ Permify Server и запустить контейнер:
sudo docker run -p 3476:3476 -p 3478:3478 ghcr.io/permify/permify serve

Эта команда загрузит образ Permify Server из реестра контейнеров GitHub и установит Permify, службу авторизации, со следующими настройками по умолчанию:

  • REST API, работающий на порту 3476.
  • Служба gRPC, работающая на порту 3478.
  • Данные авторизации хранятся в памяти компьютера.

Вы должны увидеть примерно такое сообщение:

┌────────────────────────────────────────────────────────┐
│ Permify v0.9.5 │
│ Fine-grained Authorization Service │
│ │
│ docs: ............... https://docs.permify.co │
│ github: .. https://github.com/Permify/permify │
│ blog: ............... https://permify.co/blog │
│ │
└────────────────────────────────────────────────────────┘
time=2024-03-22T14:59:09.851Z level=INFO msg="🚀 starting permify service..."
time=2024-03-22T14:59:09.851Z level=ERROR msg="Account ID is not set. Please fill in the Account ID for better support. Get your Account ID from https://permify.co/account"
time=2024-03-22T14:59:09.859Z level=INFO msg="🚀 grpc server successfully started: 3478"
time=2024-03-22T14:59:09.859Z level=INFO msg="🚀 invoker grpc server successfully started: 5000"
time=2024-03-22T14:59:09.867Z level=INFO msg="🚀 http server successfully started: 3476"

Проверка работоспособности сервера Permify Server

После запуска контейнера нужно убедиться, что Permify Server работает должным образом, обратившись к конечной точке проверки состояния. Откройте Postman и отправьте GET-запрос на http://localhost:3476/healthz. Если Permify Server работает корректно, вы должны увидеть ответ, указывающий на то, что служба работоспособна.

На изображении выше показано, что сервер Permify Server запущен. Теперь можно приступить к его интеграции в приложение Node.js и Express.

Шаг 3. Инициализация клиента Permify Node.js

В этом руководстве будем использовать Permify Node Client для контроля авторизации в приложении. Список доступных конечных точек можно найти в документах Permify Swagger. Будем использовать проверки контроля доступа Permify для защиты конечных точек.

Инициализируем клиент Permify Node Client: 

// create-tenant.js
const permify = require("@permify/permify-node");

const client = new permify.grpc.newClient({
endpoint: "localhost:3478",
})

Шаг 4. Настройка модели авторизации

Теперь, когда сервер Permify Server запущен, нужно настроить модель авторизации для сервиса Permify, после чего можно приступать к выполнению тестовых проверок доступа.

Чтобы настроить модель авторизации, отправим созданную схему в Permify с помощью метода Permify schema.write.

//create-schema.js

const permify = require("@permify/permify-node");

const client = new permify.grpc.newClient({
endpoint: "localhost:3478",
})

client.schema.write({
tenantId: "t1",
schema: "entity user {} \n\nentity organization {\n\n relation admin @user \n relation member @user \n relation manager @user \n relation agent @user \n\n action view_files = admin or manager or (member not agent)\n action edit_files = admin or manager\n action delete_file = admin\n action view_vendor_files = admin or manager or agent\n action edit_vendor_files = admin or agent\n action delete_vendor_file = agent\n\n} "
}).then((response) => {
// обработка ответа
console.log(response)
})

Приведенный выше код создает новую схему с помощью библиотеки Permify.

Схема создается для подключения к серверу Permify, работающему на localhost с портом 3478, после чего вызывает метод write для определения схемы для указанного тенанта t1.

Схема определяет такие сущности, как user (пользователь) и organization (организация), а также их отношения и действия.

Теперь запустим этот сценарий:

node create-schema.js

Из вывода на скриншоте выше видно, что новая схема была успешно настроена с помощью Permify Node Js Client.

Мы успешно завершили настройку сервиса авторизации Permify. Теперь API работает с настроенной и готовой к использованию моделью авторизации!

На следующем этапе создадим промежуточное ПО для управления доступом.

Шаг 5. Создание промежуточного ПО для управления доступом

Здесь будет приведен пример разработки промежуточного ПО Express для обеспечения ролевого управления доступом на маршрутах.

Вы также узнаете, как реализовать конечную точку проверки доступа Permify в промежуточном ПО для проверки роли и прав пользователя перед разрешением доступа к защищенным ресурсам.

// auth.js

// Import Permify client
const permify = require('@permify/permify-node');

const client = new permify.grpc.newClient({
endpoint: "localhost:3478",
});

// Функция промежуточного ПО для проверки разрешений пользователя
const checkPermissions = (permissionType) => {
return async (req, res, next) => {
try {
// Удостоверьтесь в том, что существует req.params.id
if (!req.params.id) {
throw new Error('User ID is missing in the request parameters');
}

// Преобразуйте permissionType в строку, если это необходимо
const permTypeString = String(permissionType);

// Подготовьте данные для запроса проверки Permify
const checkRes = await client.permission.check({
tenantId: 't1',
metadata: {
schemaVersion: '',
snapToken: '',
depth: 20,
},
entity: {
type: 'organization',
id: "1",
},
permission: permTypeString, // Use the converted permissionType
subject: {
type: 'user',
id: req.params.id,
},
});

if (checkRes.can === 1) {
// Если пользователь авторизован
req.authorized = 'authorized';
next();
} else {
// Если пользователь не авторизован
req.authorized = 'not authorized';
next();
}
} catch (err) {
console.error('Error checking permissions:', err.message); // Log the actual error message
res.status(500).send(err.message); // Send the actual error message to the client for debugging purposes
}
};
};

module.exports = checkPermissions;

Приведенный выше код создан с целью реализации функции промежуточного ПО checkPermission, использующей библиотеку Permify для проверки разрешений пользователя на основе предоставленного типа разрешения.

При выполнении она извлекает идентификатор пользователя (ID) из параметров запроса, при необходимости преобразует тип разрешения (permission type) в строку, затем отправляет запрос на проверку разрешения с помощью метода Permify  "permission.check" на сервер Permify. Если запрос авторизован, он добавляет в объект запроса слово "authorized" («авторизован”), в противном случае — "not authorized" («не авторизован”).

Ошибки далее регистрируются и возвращаются клиенту для отладки.

Теперь интегрируем созданное выше промежуточное ПО в приложение Node.js и Express, чтобы обеспечить ролевое управление доступом и гарантировать, что только авторизованные пользователи с соответствующими ролями и разрешениями могут получить доступ к определенным маршрутам.

Шаг 6. Защита маршрутов с помощью RBAC

Защитим маршруты с помощью созданного промежуточного ПО. Применим промежуточное ПО checkPermissions для защиты различных маршрутов в приложении.

// app.js

// Импорт необходимых модулей
const express = require('express');
const permify = require("@permify/permify-node");
const authMiddleware = require('./auth'); // Import the auth middleware

// Создайте приложение Express
const app = express();

// Определите пользовательское промежуточное ПО для заполнения userInfo
app.use((req, res, next) => {
// Имитация аутентификации пользователя и заполнение userInfo
req.userInfo = {
id: req.params.id // Extract the id from request params
// При необходимости добавьте другую информацию о пользователе
};
next();
});

// Определите маршруты

// Маршрут для '/users/:id', на котором нужно обеспечить проверку прав доступа
app.get('/users/viewFiles/:id', authMiddleware('view_files'), (req, res) => {
// Если промежуточное ПО разрешает прохождение запроса, обработайте логику маршрутизации здесь
if (req.authorized === 'authorized') {
res.send('You have access to this user route');
} else {
res.status(403).send('You are not authorized to access this user resource');
}
});

// Маршрут для '/admin/deleteVendorFiles/:id', на котором необходимо выполнить проверку прав доступа
app.get('/admin/deleteVendorFiles/:id', authMiddleware('delete_vendor_file'), (req, res) => {
// Если промежуточное ПО разрешает прохождение запроса, обработайте логику маршрута здесь
if (req.authorized === 'authorized') {
res.send('You have access to this admin route');
} else {
res.status(403).send('You are not authorized to access this admin resource');
}
});

// Запустите сервер
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});

Этот код устанавливает приложение Express на порт 3000, где определенные маршруты защищены с помощью пользовательского промежуточного ПО под названием authMiddleware.

Это промежуточное ПО, импортированное из файла auth.js, интегрируется с Permify для проверки разрешений. Ниже приведены маршруты, защищенные промежуточным ПО.

Маршруты, защищенные authMiddleware:

  • Маршрут для /users/viewFiles/:id. Этот маршрут гарантирует, что только пользователи с правами на просмотр файлов могут получить доступ к этому маршруту.
  • Маршрут для /admin/viewFiles/:id. Этот маршрут гарантирует, что только администраторы, имеющие право удалять файлы поставщика, могут получить доступ к этому маршруту.

Применяя authMiddleware к этим маршрутам, вы ограничиваете доступ на основе разрешений, предоставленных Permify.

Протестируем нашу реализацию!

Тестирование реализации RBAC

Допустим, у нас есть пользователь с идентификатором alice. Проверим, может ли alice получить доступ к конечной точке API /users/viewFiles/, которая доступна только администраторам, менеджерам или членам организации, не являющимися агентами, согласно определенной ранее схеме.

Как и ожидалось, идентификатор пользователя alice не имеет доступа к этой конечной точке API. Присвоим alice роль члена организации, используя метод data.write клиента Permify Nodejs:

// write-relationship.js
const permify = require("@permify/permify-node");

const client = new permify.grpc.newClient({
endpoint: "localhost:3478",
})

client.data.write({
tenantId: "t1",
metadata: {
schemaVersion: ""
},
tuples: [{
entity: {
type: "organization",
id: "1"
},
relation: "member",
subject: {
type: "user",
id: "alice"
}
}]
}).then((response) => {
// обработка ответа
console.log(response)
})

Запустим этот код и попробуем посетить конечную точку API /users/viewFiles/ с помощью Postman.

Теперь, после выполнения кода, Alice может успешно получить доступ к конечной точке API /users/viewFiles/.

Заключение

Важно понимать, что авторизация — это не одноразовая настройка, а постоянный процесс.

Поэтому необходимо регулярно пересматривать свою модель, проводить тщательное тестирование и адаптировать ее по мере развития приложения.

Это руководство служит прочной основой для внедрения RBAC в приложение Node.js.

Однако вы можете настраивать модель RBAC в точном соответствии с вашими уникальными требованиями.

Используя возможности Permify, вы упростите управление разрешениями и создадите устойчивую и безопасную среду приложения.

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи Ege Aytin: Implementing Role Based Access Control (RBAC) in Node.js and Express App

Предыдущая статьяJAVA: разница между параметрами JVM -D, -X, -XX