--- name: project_permissions_rework description: "Переработка ролевых прав LearnSpace (registry/role_permissions/user_permissions): Phase A+B готовы, Phase C (кастомные роли) остаётся" metadata: node_type: memory type: project originSessionId: 60467058-b40e-4bd9-9f7f-d1e362e8039a --- Переработка **ролевой системы прав** (отдельной от content_access — см. [[project_content_access]]). Это система `registry.js` (ключи прав) + `role_permissions`/`user_permissions` + middleware `requirePermission` (читает права ЖИВЬЁМ из БД каждый запрос) + админ-вкладка «Доступ · роли» (`frontend/js/admin/sections/permissions.js`) + модалка прав пользователя (`users.js`, `up-modal`). План: `plans/permissions-rework/PLAN.md`. **Phase A + B ЗАВЕРШЕНЫ (2026-06-03), всё на master:** - **A1** (9ac2a61) — зависимости `requires` в реестре (questions.delete→manage, templates.public→manage, courses.interactive→manage, simulations.quiz→access). Право = own AND все requires. UI-каскад. - **A2** (b0e385b) — lint-тест `backend/tests/permissions-registry.test.js` (ключи requirePermission/perm есть в реестре) + метки theory/simulations переформулированы («…доступен роли»). - **A3** (7d474b4) — история изменений прав: `GET /api/permissions/log` (admin), кнопка на вкладке. - **A4** (6bd1532) — убран role-level `token_version` bump (серверное применение живое → не нужен массовый разлогин роли). User-level bump оставлен. - **B5** (0a24a66) — группы прав (поле GROUP в реестре → byRole.group), секции в UI + вкл/выкл группы. - **B6** (b95b639) — массово по классу: `POST /api/permissions/class/:id/bulk` (admin), всем ученикам. - **B7** (8b495f1) — пресеты-профили (PRESETS.student: full/focus/restricted/reset), `GET /api/permissions/presets` + `POST /api/permissions/class/:id/preset`; общий хелпер `applyPermsToClass`. - **B8** (a250d15) — временные права: миграция **053** (`user_permissions.expires_at`). Резолвер/`/me`/ `/users/:id` игнорируют просроченные; `seedDefaults` чистит. `setUserPermission(...,days)`. В модалке прав пользователя — бейдж «до ДАТА» + кнопка «врем.». Тесты: `permissions.test.js` 17/17, `permissions-registry.test.js` 2/2. Полный backend-набор в рамках baseline (3 Auth + флака «intro» chemistry8 под нагрузкой). **PHASE C (ПРОИЗВОЛЬНЫЕ КАСТОМНЫЕ РОЛИ) — ЗАВЕРШЕНА И ВЛИТА В master** (2026-06-03, fast-forward до `b4a5b1a`, запушено в origin; ветка `feature/custom-roles` осталась локально, можно удалить). План: `plans/permissions-rework/PHASE_C_DESIGN.md`. Модель (без рефактора 111 requireRole): кастомная роль НАСЛЕДУЕТ «базовые роли» (какие встроенные гейты проходит) + хранит функциональную базу в `users.role` (CHECK ок) + имя в `users.custom_role` + свой набор прав в role_permissions под именем роли. - C-1 (054): таблица `roles`(name,label,base_roles,is_builtin) + `auth.effectiveRoles()`; requireRole сверяет пересечение с effectiveRoles(customRole||role) — встроенные роли быстрый путь, 111 гейтов не задеты. - C-2 (055): `users.custom_role` (ADD COLUMN, без пересборки users); `updateRole` принимает кастомную роль → база=base_roles[0] + custom_role=имя; `authMiddleware`/`optionalAuth` → req.user.customRole. - C-3 (056): снят CHECK у role_permissions; `isEnabled(uid,permRole,baseRole,key)` = user→role_permissions [customRole]→фолбэк[base]→дефолт(base); getMyPermissions/getUserPermissions: roleMap база+оверлей. - C-4a: rolesController + `/api/roles` CRUD (admin, inline guards) + засев прав из базы; setPermission принимает кастомные роли (ключ по базе, хранит под именем). - C-4b: UI «Конструктор ролей» в `permissions.js` (#perm-roles в admin.html: создать/настроить права/ удалить) + выпадающий список ролей у пользователя в `users.js` (optgroup «Кастомные роли»). Тесты: custom-roles 8/8, roles-api 5/5, permissions 17/17, full backend в рамках baseline. **ВЛИТО в master 2026-06-03** (push ok, сервер перезапущен на master, /api/health = 200). Кнопка «Права» в карточке пользователя видна всем, кроме admin (фикс b4a5b1a). **НЕ делались (исходный дизайн Phase C, не выбраны):** C-10 делегирование учителю, C-11 пер-классовый скоуп прав. **Заметка от A2-линта:** ряд teacher-прав (`students.invite`, `sessions.reset`, `results.export`, `schedule.manage`, `templates.public`, `courses.interactive`) и `theory.access` НЕ enforce-ятся через `requirePermission` на сервере — потенциальные недогейченные точки, проверить отдельно. **Гочи:** новый роут требует inline-гейт (requireRole/requirePermission), иначе pre-commit route-lint блокирует (был случай с /class/:id/bulk). Сервер надо перезапускать, чтобы подхватить изменения.