From 6bd153273533004e9407043e7b4626cacdd629f8 Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Wed, 3 Jun 2026 14:17:32 +0300 Subject: [PATCH] =?UTF-8?q?feat(permissions):=20A4=20=E2=80=94=20=D1=83?= =?UTF-8?q?=D0=B1=D1=80=D0=B0=D1=82=D1=8C=20role-level=20token=5Fversion?= =?UTF-8?q?=20bump=20(=D0=BD=D0=B5=D1=82=20=D0=BC=D0=B0=D1=81=D1=81=D0=BE?= =?UTF-8?q?=D0=B2=D0=BE=D0=B3=D0=BE=20=D1=80=D0=B0=D0=B7=D0=BB=D0=BE=D0=B3?= =?UTF-8?q?=D0=B8=D0=BD=D0=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- backend/src/controllers/permissionsController.js | 16 +++++++--------- backend/tests/permissions.test.js | 11 ++++++++--- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/backend/src/controllers/permissionsController.js b/backend/src/controllers/permissionsController.js index d932449..5a5ef61 100644 --- a/backend/src/controllers/permissionsController.js +++ b/backend/src/controllers/permissionsController.js @@ -39,15 +39,13 @@ function setPermission(req, res) { return res.status(400).json({ error: 'Invalid role' }); if (!ALL_PERMISSIONS.find(p => p.key === permission && p.role === role)) return res.status(400).json({ error: 'Unknown permission' }); - db.transaction(() => { - db.prepare( - 'INSERT OR REPLACE INTO role_permissions (role, permission, enabled) VALUES (?, ?, ?)' - ).run(role, permission, enabled ? 1 : 0); - // Invalidate JWTs for all users of that role so the change takes effect immediately - db.prepare( - 'UPDATE users SET token_version = token_version + 1 WHERE role = ?' - ).run(role); - })(); + // Серверное применение прав — ЖИВОЕ: requirePermission() читает role_permissions + // из БД на каждый запрос (auth.js). Поэтому role-level изменение НЕ инвалидирует + // сессии — раньше bump token_version разлогинивал ВСЕХ пользователей роли из-за + // одного тумблера. Клиент подхватит новые права при следующем /permissions/me. + db.prepare( + 'INSERT OR REPLACE INTO role_permissions (role, permission, enabled) VALUES (?, ?, ?)' + ).run(role, permission, enabled ? 1 : 0); audit(req, 'permission.set', `role:${role}/${permission}`, `enabled=${enabled ? 1 : 0}`); res.json({ ok: true }); } diff --git a/backend/tests/permissions.test.js b/backend/tests/permissions.test.js index 5442c18..6fc2160 100644 --- a/backend/tests/permissions.test.js +++ b/backend/tests/permissions.test.js @@ -41,8 +41,8 @@ describe('Permissions', () => { assert.equal(res.status, 401, `expected 401, got ${res.status}`); }); - // ── 3. Admin can toggle role-level permission + token_version bumped ─────── - it('admin toggles role permission and student token_version is bumped', async () => { + // ── 3. Role-level toggle сохраняется и НЕ инвалидирует сессии (live enforcement) ─ + it('admin toggles role permission; token_version НЕ бампается (нет массового разлогина)', async () => { const tvBefore = db.prepare('SELECT token_version FROM users WHERE id = ?') .get(studentUser.userId).token_version; @@ -52,9 +52,14 @@ describe('Permissions', () => { assert.equal(res.status, 200, `expected 200, got ${res.status}: ${JSON.stringify(res.body)}`); assert.equal(res.body.ok, true); + // Значение сохранено в role_permissions — сервер применяет его живо (requirePermission читает БД). + const row = db.prepare('SELECT enabled FROM role_permissions WHERE role = ? AND permission = ?') + .get('student', 'tests.free'); + assert.equal(row.enabled, 0, 'role-level значение сохранено'); + const tvAfter = db.prepare('SELECT token_version FROM users WHERE id = ?') .get(studentUser.userId).token_version; - assert.ok(tvAfter > tvBefore, `token_version should increase (was ${tvBefore}, got ${tvAfter})`); + assert.equal(tvAfter, tvBefore, 'role-level изменение НЕ должно разлогинивать пользователей роли'); // Restore await inject('POST', '/api/permissions', {