Merge feature/permissions-hardening: RBAC hardening + B-lite + P0 UX

Phase A (security): permission registry, audit log on perm/feature changes,

token_version bump on permission changes.

B-lite: requireFeature middleware blocks API on disabled global flags.

P0 UX: search, modified-dot, confirm on critical perms, badge wording.

Conflict resolution: admin.js monolith was restructured into

frontend/js/admin/sections/* by feature/admin-redesign merge. P0 UX

edits (originally in monolith) were manually ported to:

- sections/permissions.js — modDot, confirm gate, filterPermissions

- sections/users.js — 'Инд.' → 'Индивидуально' badge in user-perms modal

admin.html search input + dot CSS auto-merged cleanly.
This commit is contained in:
Maxim Dolgolyov
2026-05-17 14:51:05 +03:00
11 changed files with 407 additions and 252 deletions
+7 -4
View File
@@ -451,13 +451,16 @@ function updateFeatures(req, res) {
'flashcards', 'knowledge_map', 'board', 'biochem', 'live_quiz', 'classroom'];
const updates = req.body;
const stmt = db.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES (?, ?)");
const changed = [];
const getOld = db.prepare("SELECT value FROM app_settings WHERE key = ?");
for (const [name, enabled] of Object.entries(updates)) {
if (!allowed.includes(name)) continue;
stmt.run(`feature_${name}_enabled`, enabled ? '1' : '0');
changed.push(`${name}=${enabled ? 'on' : 'off'}`);
const settingKey = `feature_${name}_enabled`;
const oldRow = getOld.get(settingKey);
const oldVal = oldRow ? oldRow.value : null;
const newVal = enabled ? '1' : '0';
stmt.run(settingKey, newVal);
audit(req, 'feature.update', `feature:${name}`, `${oldVal} -> ${newVal}`);
}
if (changed.length) audit(req, 'features.update', null, changed.join(', '));
res.json({ ok: true });
}