feat(permissions): C-3 — пер-ролевые права кастомных ролей (резолвер + конфиг)
Миграция 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>
This commit is contained in:
@@ -60,28 +60,32 @@ function requireRole(...roles) {
|
||||
}
|
||||
|
||||
/* ── Разрешено ли ОДНО право: user override → role override → дефолт реестра ── */
|
||||
function isEnabled(uid, role, key) {
|
||||
// Просроченный временный оверрайд (expires_at в прошлом) игнорируем — наследуем роль.
|
||||
/* Разрешено ли ОДНО право: user override (без просрочки) → role_permissions[permRole]
|
||||
→ role_permissions[baseRole] (фолбэк для кастомной роли) → дефолт реестра(baseRole).
|
||||
Для встроенной роли permRole === baseRole. */
|
||||
function isEnabled(uid, permRole, baseRole, key) {
|
||||
const userRow = db.prepare(
|
||||
"SELECT enabled FROM user_permissions WHERE user_id = ? AND permission = ? AND (expires_at IS NULL OR expires_at > datetime('now'))"
|
||||
).get(uid, key);
|
||||
if (userRow !== undefined) return userRow.enabled === 1;
|
||||
const roleRow = db.prepare(
|
||||
'SELECT enabled FROM role_permissions WHERE role = ? AND permission = ?'
|
||||
).get(role, key);
|
||||
return roleRow !== undefined ? roleRow.enabled === 1 : (PERM_DEFAULTS[role]?.[key] ?? false);
|
||||
let roleRow = db.prepare('SELECT enabled FROM role_permissions WHERE role = ? AND permission = ?').get(permRole, key);
|
||||
if (roleRow === undefined && permRole !== baseRole) {
|
||||
roleRow = db.prepare('SELECT enabled FROM role_permissions WHERE role = ? AND permission = ?').get(baseRole, key);
|
||||
}
|
||||
return roleRow !== undefined ? roleRow.enabled === 1 : (PERM_DEFAULTS[baseRole]?.[key] ?? false);
|
||||
}
|
||||
|
||||
/* ── Проверка права с учётом зависимостей (requires): own AND все requires ── */
|
||||
function requirePermission(key) {
|
||||
return (req, res, next) => {
|
||||
if (req.user?.role === 'admin') return next();
|
||||
const role = req.user?.role;
|
||||
const baseRole = req.user?.role;
|
||||
const uid = req.user?.id;
|
||||
if (!role) return res.status(401).json({ error: 'Unauthorized' });
|
||||
if (!baseRole) return res.status(401).json({ error: 'Unauthorized' });
|
||||
const permRole = req.user?.customRole || baseRole; // кастомная роль конфигурит права своим именем
|
||||
|
||||
const reqs = (registry.PERMISSIONS[key] && registry.PERMISSIONS[key].requires) || [];
|
||||
const ok = isEnabled(uid, role, key) && reqs.every(r => isEnabled(uid, role, r));
|
||||
const ok = isEnabled(uid, permRole, baseRole, key) && reqs.every(r => isEnabled(uid, permRole, baseRole, r));
|
||||
if (ok) return next();
|
||||
logDenied(req, 'perm_denied', key);
|
||||
return res.status(403).json({ error: 'Permission denied' });
|
||||
|
||||
Reference in New Issue
Block a user