Документация
Архитектура

Система прав доступа

Полное описание модели прав доступа AATex — RBAC-роли, иерархия досок, доступ к карточкам, поиск, скрытые карточки и multi-tenant изоляция.

На основании обсуждения команды + аудит кода. Дата: 17 марта 2026.

Глоссарий

ТерминРасшифровкаПростым языком
RBACRole-Based Access ControlУправление доступом на основе ролей. Каждому пользователю назначается роль (owner, admin, manager, member, viewer), которая определяет что ему можно делать
DAGDirected 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Точка доступа APIURL-адрес на сервере, обрабатывающий запрос (напр. GET /api/v1/cards/search)
CRUDCreate, Read, Update, DeleteЧетыре базовые операции с данными
Soft deleteМягкое удалениеЗапись помечается isDeleted: true, можно восстановить
Hard deleteФизическое удалениеЗапись полностью удаляется из базы, восстановить нельзя
canEditМожно редактироватьФлаг в результатах поиска — имеет ли пользователь право на редактирование карточки
isHiddenСкрытая карточкаЕсли true — карточка невидима для всех кроме admin/owner

1. Роли (RBAC)

5 ролей в рамках организации:

РольОрг.УчастникиПространстваДоскиСозданиеПравкаУдалениеАрхивУд. досок
owner
admin
manager
member
viewer
owner/admin обходят все проверки доступа к доскам, пространствам и карточкам.

2. Иерархия досок (DAG)

Основная доска (Primary, уровень 1)     — карточки СОЗДАЮТСЯ здесь
  └─ Диспетчерская (Dispatch, уровень 2) — ЗЕРКАЛО карточек
       └─ Личная (Personal, уровень 3)   — ЗЕРКАЛО карточек
Ключевой принцип: карточка «живёт» на Primary Board (уровень 1). На Dispatch и Personal досках — зеркала (та же карточка с записью в boardPositions). Это НЕ копии.

3. Доступ к доскам

3.1. Текущая реализация

Файл: auth-guard.tscanAccessBoard()

УсловиеКто видит
userIds = [] + не персональнаяВсе участники организации
userIds = [u1, u2, ...]Только перечисленные + owner/admin
Персональная (board.userId = X)Только владелец X + userIds + owner/admin

3.2. Доступ из бокового меню

В боковом меню отображаются только доски, к которым у пользователя есть доступ (через buildBoardAccessFilter). Если доска видна в меню — пользователь может на неё перейти.

4. Доступ к карточкам

4.1. Общий принцип

Карточки живут на доске 1 уровня (Primary), на 2 и 3 уровнях — это зеркала. Пользователь видит и может редактировать карточку, если у него есть доступ хотя бы к одной из досок, где она присутствует.

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

Ключевой кейс: пользователь без доступа к доске 1 уровня, но с доступом к доске 2/3 уровня — ДОЛЖЕН видеть зеркальные карточки.

Сценарий В: Подзадачи через иерархию

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-idsisCardAccessible() + hierarchy через contextCardId
GET /cards/descendantsБез проверки доступа
POST /cards/[id]/move-with-subtasksБез проверки доступа
POST /cards/[id]/archiveБез проверки доступа

4.4. Целевые правила

  1. Пользователь может редактировать все карточки (включая подзадачи) на досках, к которым есть доступ.
  2. Доступ к зеркалу родительской карточки даёт доступ ко всем её подзадачам.
  3. owner/admin видят и редактируют ВСЁ.

6. Удаление и редактирование

Карточки и доски

ДействиеAdminОбычный пользователь
Редактирование карточкиЛюбуюТолько с доступом
Удаление карточкиЛюбуюНе может
Редактирование доскиЛюбуюТолько с доступом
Удаление доскиЛюбуюНе может

Комментарии

ДействиеAdminОбычный пользователь
РедактированиеЛюбойТолько свой, в течение 5 минут
УдалениеЛюбойТолько свой, в течение 5 минут

Необходимые изменения

  1. Комментарии — лимит 5 минут на редактирование/удаление для обычных пользователей
  2. Удаление карточек — ограничить до owner/admin (уточнить для manager)
  3. Удаление досок — уточнить для manager

7. Скрытые карточки

Новая фича. Возможность скрыть карточки от всех кроме админов. Даже если карточка на личной/диспетчерской доске — скрытая карточка не видна обычному пользователю.
ДействиеAdmin/OwnerОбычный пользователь
Видит скрытую карточкуДаНет, даже при доступе к доске
РедактируетДаНет
Устанавливает флагДаНет

Реализация

  • Схема БД: поле isHidden: boolean в таблице cards (по умолчанию false)
  • Фильтрация: во всех запросах для не-admin: WHERE is_hidden = false
  • Endpoints: GET /cards, GET /cards/[id], GET /cards/by-ids, GET /cards/search, GET /cards/descendants, GET /cards/archived, PATCH /cards/[id]
  • UI: Переключатель «Скрыть» (для admin) + визуальный индикатор (иконка замка / полупрозрачность)

8. Пространства (Spaces)

Файл: auth-guard.tscanAccessSpace()

УсловиеКто видит
userIds = []Все участники организации
userIds = [u1, u2, ...]Только перечисленные + owner/admin
Fallback: если пространство закрыто, но у пользователя есть доступ хотя бы к одной доске внутри — доступ предоставляется.

9. Multi-tenant изоляция

Каждая организация — отдельный tenant. Данные одной организации НИКОГДА не видны из другой.

СлойМеханизм
FrontendЗаголовок x-organization-id в каждом запросе
Next.js APIverifyOrgMembership(orgId, userId)
NestJS GuardOrgMemberGuard — глобальный
DatabaseWHERE 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)    │  │        │    │    │
│  │  │  │  │ Зеркало        │  │        │    │    │
│  │  │  │  └────────────────┘  │        │    │    │
│  │  │  └──────────────────────┘        │    │    │
│  │  └──────────────────────────────────┘    │    │
│  └──────────────────────────────────────────┘    │
└─────────────────────────────────────────────────┘

Проверка доступа к карточке:

  1. isHidden? → если да, только admin/owner
  2. boardId (Primary) → пользователь в userIds доски?
  3. boardPositions (Dispatch/Personal) → пользователь в userIds хотя бы одной?
  4. Если #2 и #3 = нет → проверить иерархию (parent/child доступен?)
  5. owner/admin → всегда ДА

13. Вопросы для обсуждения

  1. Менеджер и удаление: Должен ли manager удалять карточки и доски? Сейчас у него есть разрешение на удаление карточек.
  2. Скрытые карточки — область действия: Скрывать только от конкретных пользователей или от ВСЕХ кроме admin/owner?
  3. Подзадачи скрытых карточек: Если родительская карточка скрыта — дочерние тоже автоматически скрыты?
  4. Редактирование комментариев админом: Может ли admin/owner редактировать чужие комментарии? (Сейчас — нет, только удалять)
  5. Поиск по ID: Числовой ID в системе или старый ID из Firestore?