От раскопок до космоса: permission-система без RBAC, которая вытащила нас из 2-летнего слоя if-ов и не тормозит, когда права плодятся
Программный комитет ещё не принял решения по этому докладу
Целевая аудитория
Тезисы
Если в вашей системе разграничение прав выглядит как 'if(isSupervisor && !isTrainer)', спрятанное где-то на 20-й странице — этот доклад для вас. У нас было 6 ролей и 2 года таких if-ов. Пока мы не построили permission-систему, которая выдержала форк на новую бизнес-единицу за 2 дня.
Контекст
Я работаю в ecom.tech — это продуктово-технологическая команда, которая делает технологии для ритейла, логистики и e-commerce. Внутри мы разрабатываем edukado — платформу управления стажировками для Самоката. Сегодня через неё проходит 600 обучений в месяц (20 000+ обучений за всё время) и это рабочий инструмент 1849 активных пользователей: 1284 наставника, 274 супервайзера, 197 сотрудников подбора, 60 админов, 20 тренеров, 14 руководителей.
Проблема
Изначально в системе было 2 роли: администратор и наставник. Просто, предсказуемо. Но бизнес рос, появлялись новые зоны ответственности — тренеры, подбор персонала, супервайзеры. На фоне огромного количества других требований про разграничение прав просто «забыли». Каждую новую роль на стороне фронтенда фиксировали точечным if-ом в коде, без единой модели, на бэке была свалка из пользователей в двух таблицах.
Как это выглядело на практике. Тренеры и группа подбора имели админский доступ — отдельной роли для них просто не было. Результат: сотрудник подбора из Новосибирска заходит в интерфейс и видит обучения всех регионов. Может ошибочно завершить чужое обучение. Просто потому, что интерфейс ему это позволяет. И такие инциденты были регулярными. Супервайзеры — те, кто управляет наставниками в регионах — вообще не имели доступа к системе. Бизнес просил: «дайте им хотя бы смотреть». Но даже «смотреть» требовало архитектуры.
За 2 года накопилось: 6 ролей, десятки ключей доступа, размазанных по 20+ страницам. Каждая ошибка — обращение в поддержку и точечный фикс в коде. В среднем на спринт.
Что нужно было спроектировать
Систему прав, которая: - выдерживает пересекающиеся, а не иерархические роли (один человек может быть наставником, тренером и супервайзером с разным набором действий); - даёт единую точку проверки вместо размазанных if-ов; - переживает смену набора ролей без переписывания архитектуры.
Как решали
Отказались от классического RBAC. Он предполагает наследование прав сверху вниз, а наши роли не вложены, а пересекаются — тренер не «выше» наставника, у него другой набор функций. Наследование означало бы случайные лишние права.
Вместо иерархии — плоская константа PERMISSIONS (в начале пути было 42 ключа, теперь — 70+ и их количество продолжает расти) и матрица UserRolesPermissions, сопоставляющая каждой роли её ключи. Каждое новое право — просто строка в константе и одна запись в матрице. Минус — и готово.
Поверх матрицы — 3 уровня ограничений (SELF / SELF_REGION / SELF_ROLE). Они отвечают не только на вопрос «есть ли право», но и «право на что именно»: свой профиль, свой регион, свою роль. Единая функция hasPermission применяется на трёх уровнях одновременно: - в навигации (какие пункты меню видны) - в действиях (когда рендерится кнопка) - в данных (какие записи возвращает запрос)
Потому что скрытая кнопка не спасает, если данные всё равно приходят с сервера.
Отдельная находка: сама матрица живет не как документ в Confluence (синхронизировать который «можно умом тронуться»), а как читаемый конфиг в коде — наглядная таблица «роль × право», которая одновременно служит инструментом «археологии» для легаси: новый человек в проекте за минуты видит, что реализовано и почему, вместо того чтобы распутывать двухлетний слой if-ов.
Профит
Добавление нового доступа к существующей фиче — 5 минут. Одна строка в матрице, проставить роли. Ограничивающий фактор — только цикл деплоя.
Систему адаптировали под компанию-побратима с другой оргструктурой, другим набором ролей, другой системой подразделений. Разграничение по ролям заняло 2 дня. Весь форк — месяц чистой разработки. Менять пришлось конфиг, а не архитектуру.
И ещё один уровень профита, который мы получили уже в процессе: перенесли конфигурацию прав с фронта на бэк. Точечные изменения теперь может вносить дежурная поддержка, а не разработчик.
Что бы сделали иначе
Если начинать заново: закладывать аудит логов для проверок прав с первого дня, покрывать permission-логику тестами сразу, а не постфактум, и синхронизировать модель прав с бэкендом на этапе проектирования. Но даже так — эта архитектура сэкономила нам месяцы разработки и пару седых волос.
Математик по образованию, участница ACM ICPC. В промышленной разработке с 2015 года — начинала в EPAM с enterprise SPA для финансового сектора. Сейчас React, TypeScript, GraphQL, миграции и архитектура. В ecom.tech с 2020 года: переезжала B2B-приложение с Angular на React, сейчас веду платформу управления обучением edukado, которая раскатывается на все операционные роли компании и смежные бизнес-единицы.
Специализация: frontend-архитектура, enterprise UI, платформенные решения, миграция сложных систем. Выступаю на профильных конференциях и на рок-концертах — и там, и там за клавиатурой.
Видео
Другие доклады секции
Архитектура и масштабируемость