rolesController + routes/roles (admin, inline guards): GET список (с числом
пользователей), POST создать кастомную роль (имя-идентификатор + метка + base_roles;
засев прав из функциональной базы), PUT изменить, DELETE удалить (пользователей
возвращает на базу), GET /:name/permissions (эффективная карта база+оверлей + defs).
setPermission теперь принимает кастомные роли (ключ валидируется по базе, хранится
под именем роли). Смонтировано в server.js + тест-харнесс. Тест roles-api 5/5.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Миграция 056: снят CHECK с role_permissions.role (пересборка) → можно хранить
набор прав произвольной кастомной роли. isEnabled(uid,permRole,baseRole,key):
user override → role_permissions[customRole] → фолбэк role_permissions[base] →
дефолт реестра(base). requirePermission передаёт permRole=customRole||role.
getMyPermissions/getUserPermissions: roleMap = база + наложение кастомной роли.
Тест C-3: права кастомной роли перекрывают базу, фолбэк на базу. custom-roles 8/8,
permissions 17/17, backend без регрессий.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Миграция 053: user_permissions.expires_at (NULL = бессрочно). Резолвер isEnabled
+ /me + /users/:id игнорируют просроченные оверрайды (наследуют роль); seedDefaults
чистит просроченные строки. setUserPermission принимает days → выдаёт право на
срок (datetime('now','+N days')). API отдаёт expiresAt. Клиент: setUserPermission(...,days).
В модалке прав пользователя — бейдж «до ДАТА» + кнопка «врем.» (выдать на N дней).
Тест: срок хранится/отдаётся, просроченное игнорируется и вычищается. Backend pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PRESETS (student): «Полный доступ», «Режим фокуса» (без магазина/испытаний),
«Ограниченный» (+ без лаборатории), «Сбросить к стандарту роли». GET
/api/permissions/presets + POST /api/permissions/class/:id/preset (admin).
Рефактор: общий applyPermsToClass() (карта key→1/0/inherit) — его используют и
bulk, и preset. В блоке «Массово по классу» — кнопки пресетов (с подтверждением).
Тест: список + применение focus/reset + валидация. Backend pass (3 baseline-Auth).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
POST /api/permissions/class/:id/bulk { permission, enabled } (admin, явный
requireRole) — выставляет user_permissions всем ученикам класса (1/0/null=сброс),
точечный token_version bump каждому. Валидация: только студенческие ключи.
Клиент LS.setClassPermission. В админке «Доступ · роли» — блок «Массово по
классу»: выбор класса → у каждого права «включить/выключить всем / сбросить».
Тест: оверрайд всем + сброс + отклонение teacher-ключа. Backend 221 pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
requirePermission читает права из БД на каждый запрос → серверное применение
живое. Прежний bump token_version при role-level изменении разлогинивал ВСЕХ
пользователей роли из-за одного тумблера. Убрали его: изменение применяется
сразу на сервере, клиент подхватит при следующем /permissions/me. User-level
bump оставлен (точечно одному пользователю — целевое обновление, не массовое).
Тест 3 обновлён: role-level НЕ бампает token_version + значение сохраняется.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
GET /api/permissions/log (admin-only) — последние изменения ролевых прав (или
?user_id= для личных оверрайдов) из admin_audit_log; читаемый текст («включил
«X» для роли «учитель»») с резолвом меток через registry. Клиент LS.permissionsLog.
Вкладка «Доступ · роли»: блок «История изменений прав ролей» с кнопкой «Показать».
Тест: admin видит записи, не-админу 403. permissions 13/13.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
registry: поле requires (questions.delete→manage, templates.public→manage,
courses.interactive→manage, simulations.quiz→access), проброшено в byRole.
auth.requirePermission: вынесен isEnabled(); право = own AND все requires
(дочернее не работает без родителя). /me и /users/🆔 effective с учётом
requires + requires в ответе. UI permissions.js: каскад — дочернее с
невыполненной зависимостью неактивно (тумблер заблокирован + «Требует: …»).
Тест зависимости. План: plans/permissions-rework/PLAN.md. Backend 216 pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reset can downgrade effective access (override=1 vs role default=0),
so the user's JWT must be invalidated alongside the DELETE.
Wrapped in db.transaction for atomicity.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- backend/src/permissions/registry.js: single source of truth (PERMISSIONS map)
with all 24 keys (16 teacher + 8 student, student keys also cover free_student).
Exports isKnown(), listKeys(), byRole(), buildDefaultsMap().
- auth.js: PERM_DEFAULTS now sourced from registry.buildDefaultsMap();
new perm() helper validates key at registration time (crashes early on typos).
requirePermission() unchanged — backward compat preserved.
- permissionsController.js: ALL_PERMISSIONS now built from registry.byRole();
inline 24-entry array removed. API response shape unchanged.
- check-route-auth.js: validates every requirePermission/perm call key against
registry; lists unknown keys as errors before exit.
perm() added to GUARDS list so it counts as route protection.
Discrepancy noted: auth.js had free_student with same 8 keys as student;
permissionsController never seeded free_student rows. Registry documents
this via roles:[] array; buildDefaultsMap() correctly covers free_student.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds audit entries for:
- permission.set (role-level change)
- permission.user_set (per-user override)
- permission.user_reset (clear user override)
- feature.update (global feature flag toggle, per-key with old->new diff)
Old value captured for feature.update for full diff trail.
permissionsController: added audit import, wired audit() after each write.
adminController.updateFeatures: replaced bulk audit with per-key entries
capturing old value from app_settings before overwrite.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
setPermission / setUserPermission now bump token_version for affected
users so cached JWTs lose access immediately instead of after expiry.
Aligns with role-change pattern in adminController.updateRole.
Both writes wrapped in db.transaction() so token_version is only bumped
if the permission write itself succeeds.
Also cleaned up inline require('../db/db') calls to use top-level db.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>