Перейти до основного вмісту

ADR-003: Кастомні ролі та дозволи

  • Статус: прийнято
  • Дата: 2026-04-16
  • Автор: CTO Agent (KALA-141)

Контекст

Оригінальна авторизаційна модель використовувала фіксований Prisma-enum Role:

  • admin
  • dept_head
  • approver
  • employee

Цього вистачало для ранніх API-гейтів, але недостатньо для реальних компаній. Тенантам потрібні ролі на кшталт "Legal Specialist", "Finance Operator" або "Regional Manager" без очікування code-деплою. Водночас продукт ще не вимагав повноцінної RBAC-системи з many-to-many таблицею дозволів, ієрархією ролей, resource-specific grants і policy language.

Рішення

Впроваджуємо SystemRole як основну rule-модель:

model SystemRole {
id String
name String
slug String
isSystem Boolean
allDepartments Boolean
departmentIds String[]
permissions String[]
}

Вбудовані ролі зберігаються як SystemRole-записи з isSystem: true. Створені тенантом ролі мають isSystem: false. User.roleId вказує на role-запис, а legacy enum лишається в User.role для backward compatibility та fallback під час міграції.

Дозволи — рядкові ключі з центрального каталогу. Wildcard * дає всі відомі дозволи.

Розглянуті альтернативи

Залишити тільки enum

Відхилено. Це змушувало б кожну tenant-specific роль переносити у код застосунку і вимагало б деплою для зміни політики.

Many-to-many permissions table

Поки відхилено. Нормалізована permissions-таблиця з role-permission join rows потужна, але додає міграції, джойни, складність UI і policy drift ще до того, як у продукту буде достатньо вимог, щоб це виправдати.

JSON policy language

Відхилено. Policy DSL занадто гнучкий для поточного admin UI і його важче аудитити, ніж явні permission-ключі.

Наслідки

Позитивні

  • Кастомні ролі створюються і редагуються в рантаймі.
  • Вбудовані ролі лишаються захищеними через isSystem.
  • Перевірка дозволів проста: членство у масиві плюс розгортання wildcard.
  • Legacy-enum fallback тримає існуючих користувачів і тести робочими під час міграції.

Негативні

  • Permission-ключі мають лишатися стабільними — це persisted-рядки.
  • Перейменування дозволу вимагає data migration.
  • Нема resource-level policy language; department scope лишається основною віссю ізоляції.

Нотатки про реалізацію

  • src/lib/permission-catalog.ts володіє валідним списком ключів.
  • src/lib/permissions.ts обчислює capability-булеви та ефективні набори дозволів.
  • src/routes/roles.ts валідує role CRUD і захищає системні ролі.
  • Сід-дані створюють чотири вбудованих SystemRole-записи.