feat(rbac): make Settings authorization permission-aware (#1738)

* feat(rbac): make Settings authorization permission-aware

Align Settings visibility and mutations with the existing permission matrix so Node Admin can edit node-scoped operational settings while system and credential surfaces stay Admin-protected.

* fix(rbac): tighten settings permission buckets and tests

Collapse settings key permission maps into one source of truth, and cover mixed PATCH atomicity plus image-update enabled writes.

* fix(rbac): tighten Settings scoped grants and CI assertions

Empty settings PATCH fails closed, node:manage is scoped to the active
node, system-only Settings stay hidden without system:settings, and
Check updates / webhooks mutate gates follow the permission matrix.

* fix(rbac): defer Settings section fallback until authz is ready

Keep deep links to permission-gated sections (e.g. license) intact while
can() is still fail-closed during permission metadata load.

* docs(settings): clarify Notifications channels vs routing authz

Channels use node:manage via /api/agents; routing and mute stay Admin-only.
This commit is contained in:
Anso
2026-07-30 10:25:13 -04:00
committed by GitHub
parent c704cb54d2
commit a3026f47a8
46 changed files with 812 additions and 180 deletions
+9 -4
View File
@@ -1,6 +1,6 @@
import type { FleetTab } from '@/lib/events';
import type { SectionId } from '@/components/settings/types';
import { getSettingsItem } from '@/components/settings/registry';
import { getSettingsItem, isItemVisible, isItemLocked } from '@/components/settings/registry';
import type { ActiveView } from '@/lib/router/routeTypes';
import { HUB_ONLY_VIEWS } from '@/lib/router/routeTypes';
@@ -75,9 +75,14 @@ export function isSettingsSectionHidden(section: SectionId, ctx: ReachabilityCon
if (!authzReady(ctx)) return false;
const item = getSettingsItem(section);
if (!item) return true;
if (ctx.isRemote && item.hiddenOnRemote) return true;
if (item.adminOnly && !ctx.isAdmin) return true;
if (item.tier === 'paid' && !ctx.isPaid) return true;
const visibility = {
isRemote: ctx.isRemote,
isAdmin: ctx.isAdmin,
isPaid: ctx.isPaid,
can: ctx.can,
};
if (!isItemVisible(item, visibility)) return true;
if (isItemLocked(item, visibility)) return true;
// fleet-mesh stays reachable: snapshot_documentation lives there even when
// Mesh discovery is off.
return false;