Система прав доступа
Полное описание модели прав доступа AATex — RBAC-роли, иерархия досок, доступ к карточкам, поиск, скрытые карточки и multi-tenant изоляция.
На основании обсуждения команды + аудит кода. Дата: 17 марта 2026.
Глоссарий
| Термин | Расшифровка | Простым языком |
|---|---|---|
| RBAC | Role-Based Access Control | Управление доступом на основе ролей. Каждому пользователю назначается роль (owner, admin, manager, member, viewer), которая определяет что ему можно делать |
| DAG | Directed Acyclic Graph | Направленный ациклический граф — способ организации связей между досками: доска уровня 1 → уровня 2 → уровня 3, без зацикливания |
| Multi-tenant | Многоарендная архитектура | Одна система обслуживает множество организаций. Каждая организация — отдельный «арендатор», данные полностью изолированы |
| Primary Board | Основная доска | Доска уровня 1, на которой карточки создаются. «Дом» карточки |
| Dispatch Board | Диспетчерская доска | Доска уровня 2, показывает зеркала карточек из родительской доски |
| Personal Board | Личная доска | Доска уровня 3, привязана к конкретному пользователю. Тоже показывает зеркала |
| boardPositions | Позиции карточки на досках | JSON-объект в карточке, хранящий на каких dispatch/personal досках она отображается |
| Каскад | Card Cascade | Автоматическое распространение карточки на дочернюю доску при перемещении в привязанную колонку |
| Зеркало | Mirror | Та же самая карточка (не копия!), показанная на dispatch/personal доске через boardPositions |
| userIds | Список ID пользователей | Массив ID пользователей с доступом к доске/пространству. Пустой = доступ для всех участников организации |
| Иерархия | Hierarchy | Связь карточек: родитель (parentCardId) и дочерние подзадачи (subCardIds) |
| Endpoint | Точка доступа API | URL-адрес на сервере, обрабатывающий запрос (напр. GET /api/v1/cards/search) |
| CRUD | Create, Read, Update, Delete | Четыре базовые операции с данными |
| Soft delete | Мягкое удаление | Запись помечается isDeleted: true, можно восстановить |
| Hard delete | Физическое удаление | Запись полностью удаляется из базы, восстановить нельзя |
| canEdit | Можно редактировать | Флаг в результатах поиска — имеет ли пользователь право на редактирование карточки |
| isHidden | Скрытая карточка | Если true — карточка невидима для всех кроме admin/owner |
1. Роли (RBAC)
5 ролей в рамках организации:
| Роль | Орг. | Участники | Пространства | Доски | Создание | Правка | Удаление | Архив | Уд. досок |
|---|---|---|---|---|---|---|---|---|---|
| owner | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| admin | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| manager | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ |
| member | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✗ | ✗ | ✗ |
| viewer | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
2. Иерархия досок (DAG)
Основная доска (Primary, уровень 1) — карточки СОЗДАЮТСЯ здесь
└─ Диспетчерская (Dispatch, уровень 2) — ЗЕРКАЛО карточек
└─ Личная (Personal, уровень 3) — ЗЕРКАЛО карточек3. Доступ к доскам
3.1. Текущая реализация
Файл: auth-guard.ts → canAccessBoard()
| Условие | Кто видит |
|---|---|
userIds = [] + не персональная | Все участники организации |
userIds = [u1, u2, ...] | Только перечисленные + owner/admin |
Персональная (board.userId = X) | Только владелец X + userIds + owner/admin |
3.2. Доступ из бокового меню
В боковом меню отображаются только доски, к которым у пользователя есть доступ (через buildBoardAccessFilter). Если доска видна в меню — пользователь может на неё перейти.
4. Доступ к карточкам
4.1. Общий принцип
4.2. Сценарии доступа
Сценарий А: Прямой доступ к Primary
Primary Board A (userIds: [user1, user2]) └─ Карточка X
→ user1 видит и редактирует карточку X ✓
Сценарий Б: Доступ через Dispatch/Personal (зеркало)
Primary Board A (userIds: [admin]) — user1 НЕ имеет доступа
└─ Колонка "В работе" (attachedBoardId: Board B)
└─ Dispatch Board B (userIds: [] — открытая) — user1 ИМЕЕТ доступ
└─ Карточка X (зеркало)→ user1 видит и редактирует карточку X через Board B ✓
Сценарий В: Подзадачи через иерархию
Primary Board A (userIds: [admin]) — user1 НЕ имеет доступа
└─ Карточка X (parent)
└─ Карточка Y (child, subtask) — лежит на Board A
Personal Board C (userId: user1)
└─ Карточка X (зеркало) — попала через каскад→ user1 видит карточку X на Personal Board C ✓
→ user1 видит и редактирует подзадачу Y (родительская карточка X доступна) ✓
4.3. Текущая реализация
| Endpoint | Проверка | Статус |
|---|---|---|
| GET /cards/[id] | Все участники org видят любую | |
| PATCH /cards/[id] | canAccessCardBoards() ИЛИ canAccessCardViaHierarchy() | |
| DELETE /cards/[id] | canAccessCardBoards() (без hierarchy) | |
| GET /cards (по boardId) | canAccessBoard() перед списком | |
| GET /cards/by-ids | isCardAccessible() + hierarchy через contextCardId | |
| GET /cards/descendants | Без проверки доступа | |
| POST /cards/[id]/move-with-subtasks | Без проверки доступа | |
| POST /cards/[id]/archive | Без проверки доступа |
4.4. Целевые правила
- Пользователь может редактировать все карточки (включая подзадачи) на досках, к которым есть доступ.
- Доступ к зеркалу родительской карточки даёт доступ ко всем её подзадачам.
- owner/admin видят и редактируют ВСЁ.
5. Поиск карточек
5.1. Правила
| Поле | Совпадение | Просмотр | Редактирование |
|---|---|---|---|
| ID | Точное | Только с доступом | Только с доступом |
| Имя | Подстрока | Все карточки орг. | Только с доступом |
5.2. Текущее vs целевое
| Аспект | Текущее | Целевое |
|---|---|---|
| Разделение ID/имя | Единое поле | Два режима |
| Фильтрация по доступу (ID) | Нет | Только при наличии доступа |
| Фильтрация по доступу (имя) | Нет | Все + маркер read-only |
| Признак canEdit | Нет | Поле canEdit в каждом результате |
5.3. Необходимые изменения
- API: добавить параметр
searchType: 'id' | 'name' - Поиск по ID: фильтровать через
isCardAccessible()+ hierarchy - Поиск по имени: возвращать все, добавить поле
canEdit - Интерфейс: визуально отличать редактируемые карточки от read-only
6. Удаление и редактирование
Карточки и доски
| Действие | Admin | Обычный пользователь |
|---|---|---|
| Редактирование карточки | Любую | Только с доступом |
| Удаление карточки | Любую | Не может |
| Редактирование доски | Любую | Только с доступом |
| Удаление доски | Любую | Не может |
Комментарии
| Действие | Admin | Обычный пользователь |
|---|---|---|
| Редактирование | Любой | Только свой, в течение 5 минут |
| Удаление | Любой | Только свой, в течение 5 минут |
Необходимые изменения
- Комментарии — лимит 5 минут на редактирование/удаление для обычных пользователей
- Удаление карточек — ограничить до owner/admin (уточнить для manager)
- Удаление досок — уточнить для manager
8. Пространства (Spaces)
Файл: auth-guard.ts → canAccessSpace()
| Условие | Кто видит |
|---|---|
userIds = [] | Все участники организации |
userIds = [u1, u2, ...] | Только перечисленные + owner/admin |
9. Multi-tenant изоляция
Каждая организация — отдельный tenant. Данные одной организации НИКОГДА не видны из другой.
| Слой | Механизм |
|---|---|
| Frontend | Заголовок x-organization-id в каждом запросе |
| Next.js API | verifyOrgMembership(orgId, userId) |
| NestJS Guard | OrgMemberGuard — глобальный |
| Database | WHERE organizationId = :orgId |
10. Сводная таблица
| Функция | Текущее | Целевое | Статус |
|---|---|---|---|
| Доступ к доскам через userIds | ✓ | ✓ | Готово |
| Доступ к карточкам через boards + boardPositions | ✓ | ✓ | Готово |
| Доступ к подзадачам через иерархию | ~ | ✓ | Частично |
| Поиск: разделение ID/имя | ✗ | ✓ | Надо делать |
| Поиск: фильтрация по доступу | ✗ | ✓ | Надо делать |
| Комментарии: лимит 5 мин | ✗ | ✓ | Надо делать |
| Комментарии: admin удаляет любые | ✓ | ✓ | Готово |
| Удаление карточек: только admin | ~ | ✓ | Уточнить |
| Скрытые карточки (isHidden) | ✗ | ✓ | Надо делать |
| Фильтрация descendants по доступу | ✗ | ✓ | Надо делать |
| Проверка доступа при archive/move | ✗ | ✓ | Надо делать |
11. Приоритет реализации
Критично
- Скрытые карточки (isHidden)
- Комментарии — лимит 5 минут
Важно
- Поиск — разделение ID/имя + фильтрация по доступу
- Фильтрация descendants и archive/move-subtasks
Улучшение
- Уточнить права manager на удаление карточек/досок
12. Диаграмма модели доступа
┌─────────────────────────────────────────────────┐ │ ОРГАНИЗАЦИЯ │ │ │ │ Роли: owner > admin > manager > member > viewer │ │ │ │ ┌──────────────────────────────────────────┐ │ │ │ ПРОСТРАНСТВО (Space) │ │ │ │ userIds: [] → все | [u1,u2] → только │ │ │ │ │ │ │ │ ┌──────────────────────────────────┐ │ │ │ │ │ ДОСКА Primary (уровень 1) │ │ │ │ │ │ userIds: [] → все | [u1] → u1 │ │ │ │ │ │ │ │ │ │ │ │ ┌──────────────────────┐ │ │ │ │ │ │ │ КАРТОЧКА │ │ │ │ │ │ │ │ isHidden: true → 🔒 │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─ Подзадача │ │ │ │ │ │ │ │ └─ Подзадача │ │ │ │ │ │ │ └──────────────────────┘ │ │ │ │ │ │ │ каскад (boardPositions) │ │ │ │ │ │ ▼ │ │ │ │ │ │ ┌──────────────────────┐ │ │ │ │ │ │ │ ДОСКА Dispatch (ур 2)│ │ │ │ │ │ │ │ Зеркало карточки │ │ │ │ │ │ │ │ │ каскад │ │ │ │ │ │ │ │ ▼ │ │ │ │ │ │ │ │ ┌────────────────┐ │ │ │ │ │ │ │ │ │ ДОСКА Personal │ │ │ │ │ │ │ │ │ │ (уровень 3) │ │ │ │ │ │ │ │ │ │ Зеркало │ │ │ │ │ │ │ │ │ └────────────────┘ │ │ │ │ │ │ │ └──────────────────────┘ │ │ │ │ │ └──────────────────────────────────┘ │ │ │ └──────────────────────────────────────────┘ │ └─────────────────────────────────────────────────┘
Проверка доступа к карточке:
isHidden?→ если да, только admin/ownerboardId(Primary) → пользователь в userIds доски?boardPositions(Dispatch/Personal) → пользователь в userIds хотя бы одной?- Если #2 и #3 = нет → проверить иерархию (parent/child доступен?)
- owner/admin → всегда ДА
13. Вопросы для обсуждения
- Менеджер и удаление: Должен ли manager удалять карточки и доски? Сейчас у него есть разрешение на удаление карточек.
- Скрытые карточки — область действия: Скрывать только от конкретных пользователей или от ВСЕХ кроме admin/owner?
- Подзадачи скрытых карточек: Если родительская карточка скрыта — дочерние тоже автоматически скрыты?
- Редактирование комментариев админом: Может ли admin/owner редактировать чужие комментарии? (Сейчас — нет, только удалять)
- Поиск по ID: Числовой ID в системе или старый ID из Firestore?