feat: add multi-BU tenancy with per-user team scoping (Option B)

- Add bu_teams column to users table (migration + fresh schema)
- Create shared KNOWN_TEAMS constant and validateTeams helper
- Expose user teams in auth middleware, login, and /me responses
- Add bu_teams CRUD to user management routes with audit logging
- Make Ivanti FINDINGS_FILTERS configurable via IVANTI_BU_FILTER env var
- Add query-time team filtering to GET /findings and /findings/counts
- Update AuthContext with teams helpers and admin scope toggle
- Create AdminScopeToggle component (My Teams / All BUs)
- Scope ReportingPage findings fetch by user teams
- Scope CompliancePage team selector by user teams
- Scope ExportsPage findings exports by user teams
- Add BU teams multi-select to UserManagement create/edit forms
- Display team badges in user list table
This commit is contained in:
Jordan Ramos
2026-05-05 11:04:53 -06:00
parent af951fdc12
commit 2656df94d3
24 changed files with 999 additions and 127 deletions

View File

@@ -0,0 +1,61 @@
// AdminScopeToggle.js
// Two-state toggle for Admin users: "My Teams" vs "All BUs"
// Controls whether data on Reporting, Compliance, and Exports pages
// is scoped to the admin's assigned teams or shows everything.
import React from 'react';
import { useAuth } from '../contexts/AuthContext';
function AdminScopeToggle() {
const { isAdmin, adminScope, toggleAdminScope, hasTeams } = useAuth();
// Only render for Admin users who have teams assigned
// (if no teams assigned, both modes are identical — no toggle needed)
if (!isAdmin() || !hasTeams()) return null;
const isAllMode = adminScope === 'all';
return (
<div
style={{
display: 'inline-flex',
alignItems: 'center',
gap: '0.4rem',
padding: '0.25rem 0.5rem',
borderRadius: '0.375rem',
background: 'rgba(14, 165, 233, 0.05)',
border: '1px solid rgba(14, 165, 233, 0.2)',
fontSize: '0.7rem',
fontFamily: 'monospace',
userSelect: 'none',
}}
>
<span style={{ color: '#64748B', fontWeight: '500' }}>Scope:</span>
<button
onClick={toggleAdminScope}
aria-label={`Switch to ${isAllMode ? 'My Teams' : 'All BUs'} view`}
aria-pressed={isAllMode}
style={{
display: 'inline-flex',
alignItems: 'center',
gap: '0.25rem',
padding: '0.2rem 0.5rem',
borderRadius: '0.25rem',
border: 'none',
cursor: 'pointer',
fontFamily: 'monospace',
fontSize: '0.68rem',
fontWeight: '600',
letterSpacing: '0.02em',
transition: 'all 0.15s ease',
background: isAllMode ? 'rgba(139, 92, 246, 0.15)' : 'rgba(14, 165, 233, 0.15)',
color: isAllMode ? '#8B5CF6' : '#0EA5E9',
}}
>
{isAllMode ? '⊕ All BUs' : '⊙ My Teams'}
</button>
</div>
);
}
export default AdminScopeToggle;