fix(perm): bump token_version on permission change (invalidates JWTs)

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>
This commit is contained in:
Maxim Dolgolyov
2026-05-17 14:15:51 +03:00
parent bd7a9dbee2
commit dd1adc0c69
@@ -203,9 +203,15 @@ function setPermission(req, res) {
return res.status(400).json({ error: 'Invalid role' }); return res.status(400).json({ error: 'Invalid role' });
if (!ALL_PERMISSIONS.find(p => p.key === permission && p.role === role)) if (!ALL_PERMISSIONS.find(p => p.key === permission && p.role === role))
return res.status(400).json({ error: 'Unknown permission' }); return res.status(400).json({ error: 'Unknown permission' });
db.prepare( db.transaction(() => {
'INSERT OR REPLACE INTO role_permissions (role, permission, enabled) VALUES (?, ?, ?)' db.prepare(
).run(role, permission, enabled ? 1 : 0); '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);
})();
res.json({ ok: true }); res.json({ ok: true });
} }
@@ -239,19 +245,19 @@ function getMyPermissions(req, res) {
/* ── GET /api/permissions/users/:id ──────────────────────────────────── */ /* ── GET /api/permissions/users/:id ──────────────────────────────────── */
function getUserPermissions(req, res) { function getUserPermissions(req, res) {
const uid = Number(req.params.id); const uid = Number(req.params.id);
const target = require('../db/db').prepare('SELECT id, role FROM users WHERE id = ?').get(uid); const target = db.prepare('SELECT id, role FROM users WHERE id = ?').get(uid);
if (!target) return res.status(404).json({ error: 'User not found' }); if (!target) return res.status(404).json({ error: 'User not found' });
seedDefaults(); seedDefaults();
// role-level values // role-level values
const roleRows = require('../db/db').prepare( const roleRows = db.prepare(
'SELECT permission, enabled FROM role_permissions WHERE role = ?' 'SELECT permission, enabled FROM role_permissions WHERE role = ?'
).all(target.role); ).all(target.role);
const roleMap = {}; const roleMap = {};
for (const r of roleRows) roleMap[r.permission] = r.enabled === 1; for (const r of roleRows) roleMap[r.permission] = r.enabled === 1;
// user-level overrides // user-level overrides
const userRows = require('../db/db').prepare( const userRows = db.prepare(
'SELECT permission, enabled FROM user_permissions WHERE user_id = ?' 'SELECT permission, enabled FROM user_permissions WHERE user_id = ?'
).all(uid); ).all(uid);
const userMap = {}; const userMap = {};
@@ -274,13 +280,19 @@ function getUserPermissions(req, res) {
function setUserPermission(req, res) { function setUserPermission(req, res) {
const uid = Number(req.params.id); const uid = Number(req.params.id);
const { permission, enabled } = req.body; const { permission, enabled } = req.body;
const target = require('../db/db').prepare('SELECT role FROM users WHERE id = ?').get(uid); const target = db.prepare('SELECT role FROM users WHERE id = ?').get(uid);
if (!target) return res.status(404).json({ error: 'User not found' }); if (!target) return res.status(404).json({ error: 'User not found' });
if (!ALL_PERMISSIONS.find(p => p.key === permission && p.role === target.role)) if (!ALL_PERMISSIONS.find(p => p.key === permission && p.role === target.role))
return res.status(400).json({ error: 'Unknown permission for this role' }); return res.status(400).json({ error: 'Unknown permission for this role' });
require('../db/db').prepare( db.transaction(() => {
'INSERT OR REPLACE INTO user_permissions (user_id, permission, enabled) VALUES (?, ?, ?)' db.prepare(
).run(uid, permission, enabled ? 1 : 0); 'INSERT OR REPLACE INTO user_permissions (user_id, permission, enabled) VALUES (?, ?, ?)'
).run(uid, permission, enabled ? 1 : 0);
// Invalidate existing JWT for this user immediately
db.prepare(
'UPDATE users SET token_version = token_version + 1 WHERE id = ?'
).run(uid);
})();
res.json({ ok: true }); res.json({ ok: true });
} }
@@ -289,11 +301,11 @@ function resetUserPermissions(req, res) {
const uid = Number(req.params.id); const uid = Number(req.params.id);
const { permission } = req.body; // optional: reset one key const { permission } = req.body; // optional: reset one key
if (permission) { if (permission) {
require('../db/db').prepare( db.prepare(
'DELETE FROM user_permissions WHERE user_id = ? AND permission = ?' 'DELETE FROM user_permissions WHERE user_id = ? AND permission = ?'
).run(uid, permission); ).run(uid, permission);
} else { } else {
require('../db/db').prepare('DELETE FROM user_permissions WHERE user_id = ?').run(uid); db.prepare('DELETE FROM user_permissions WHERE user_id = ?').run(uid);
} }
res.json({ ok: true }); res.json({ ok: true });
} }