Context
PR #524 scoped the inventory tree, the topology page, the connections list, and the live inventory stream and events to a user's RBAC connection/node grants (previously only their VMs were filtered). The remaining connection/node-bearing read endpoints still return data for connections a scoped user is not granted, and should get the same RBAC infra-scope filtering. This was deliberately deferred from #524 because the surfaces are not uniform and some carry regression risk.
Enterprise-only. Affects connection/node/vm-scoped RBAC users on the provider tenant. Admins and global-scoped users must remain unaffected, and this must compose with (not replace) the existing tenant/vDC isolation.
Building blocks already in place (from #524)
lib/rbac/infraScope.ts exposes:
getRbacInfraScope(userId, tenantId?) returns the scope or null (null = admin/unrestricted, no filtering).
isConnectionVisible(scope, connId), filterVisibleConnections(list, scope), applyRbacInfraFilter(cluster, scope).
- Scope shape:
{ fullConnections: Set<string>; nodesByConnection: Map<string, Set<string>> }.
Pattern: resolve rbacScope once (skip when admin/null), then filter records by isConnectionVisible and, for node-bearing records under a node scope, by node membership.
Scope
Low risk (compose with the existing vDC filter; extend the existing *Scope tests)
These already filter by connectionId plus node/pool via the vDC mask; add the RBAC connection+node filter as an AND-gate, never filtering when the scope is null:
app/api/v1/changes/route.ts
app/api/v1/changes/recent/route.ts
app/api/v1/events/route.ts (top-level; preserve the existing node and vmid filtering)
Higher risk (need care)
app/api/v1/dashboard/route.ts: the per-VM and per-node lists can be filtered, but the capacity KPIs (CPU/RAM/storage percentages) are aggregated across all scoped connections. Filter the lists without under-counting the aggregates. It already uses filterVmsByPermission and filterNodesByPermission.
app/api/v1/orchestrator/alerts/{route,active,summary,[id],rules}: all gated by isAlertVisibleToTenant (rule ownership plus connection, node, pool and vmid). The correct approach is to extend that single helper to also honor the RBAC role scope, not to bolt on a parallel isConnectionVisible filter. summary is a pure count (follows automatically) and [id] already returns 404 for out-of-scope records.
Tests
Per-endpoint *Scope.test.ts (extend existing where present): a node-scoped user sees only their connection/node records, plus an admin passthrough case.
Sonar
Verify the new-code quality gate. Note that app/api/v1/inventory/stream/route.ts (from #524) has no direct SSE test because its prune delegates to unit-tested pure helpers; confirm new-code coverage holds, and add a coverage exclusion or a small test if the gate flags it.
Context
PR #524 scoped the inventory tree, the topology page, the connections list, and the live inventory stream and events to a user's RBAC connection/node grants (previously only their VMs were filtered). The remaining connection/node-bearing read endpoints still return data for connections a scoped user is not granted, and should get the same RBAC infra-scope filtering. This was deliberately deferred from #524 because the surfaces are not uniform and some carry regression risk.
Enterprise-only. Affects connection/node/vm-scoped RBAC users on the provider tenant. Admins and global-scoped users must remain unaffected, and this must compose with (not replace) the existing tenant/vDC isolation.
Building blocks already in place (from #524)
lib/rbac/infraScope.tsexposes:getRbacInfraScope(userId, tenantId?)returns the scope or null (null = admin/unrestricted, no filtering).isConnectionVisible(scope, connId),filterVisibleConnections(list, scope),applyRbacInfraFilter(cluster, scope).{ fullConnections: Set<string>; nodesByConnection: Map<string, Set<string>> }.Pattern: resolve
rbacScopeonce (skip when admin/null), then filter records byisConnectionVisibleand, for node-bearing records under a node scope, by node membership.Scope
Low risk (compose with the existing vDC filter; extend the existing *Scope tests)
These already filter by
connectionIdplus node/pool via the vDC mask; add the RBAC connection+node filter as an AND-gate, never filtering when the scope is null:app/api/v1/changes/route.tsapp/api/v1/changes/recent/route.tsapp/api/v1/events/route.ts(top-level; preserve the existing node and vmid filtering)Higher risk (need care)
app/api/v1/dashboard/route.ts: the per-VM and per-node lists can be filtered, but the capacity KPIs (CPU/RAM/storage percentages) are aggregated across all scoped connections. Filter the lists without under-counting the aggregates. It already usesfilterVmsByPermissionandfilterNodesByPermission.app/api/v1/orchestrator/alerts/{route,active,summary,[id],rules}: all gated byisAlertVisibleToTenant(rule ownership plus connection, node, pool and vmid). The correct approach is to extend that single helper to also honor the RBAC role scope, not to bolt on a parallelisConnectionVisiblefilter.summaryis a pure count (follows automatically) and[id]already returns 404 for out-of-scope records.Tests
Per-endpoint
*Scope.test.ts(extend existing where present): a node-scoped user sees only their connection/node records, plus an admin passthrough case.Sonar
Verify the new-code quality gate. Note that
app/api/v1/inventory/stream/route.ts(from #524) has no direct SSE test because its prune delegates to unit-tested pure helpers; confirm new-code coverage holds, and add a coverage exclusion or a small test if the gate flags it.