feat: implement group-based access control (Admin, Standard_User, Leadership, Read_Only)

- Add user_group migration and created_by column migration
- Replace requireRole middleware with requireGroup
- Update all backend routes to use group-based authorization
- Add Standard_User conditional delete with ownership, state, and compliance checks
- Add cascade impact check for CVE deletes
- Update AuthContext with group-based permission helpers
- Update all frontend components for group-based rendering
- Update UserManagement UI with group dropdown, confirmation dialogs, self-demotion prevention
This commit is contained in:
jramos
2026-04-06 16:18:07 -06:00
parent 1ef57b0504
commit 73fd747576
19 changed files with 1171 additions and 149 deletions

View File

@@ -19,17 +19,26 @@ export default function UserMenu({ onManageUsers, onAuditLog }) {
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);
const getRoleBadgeColor = (role) => {
switch (role) {
case 'admin':
const getGroupBadgeColor = (group) => {
switch (group) {
case 'Admin':
return 'bg-red-100 text-red-800';
case 'editor':
case 'Standard_User':
return 'bg-blue-100 text-blue-800';
case 'Leadership':
return 'bg-purple-100 text-purple-800';
case 'Read_Only':
return 'bg-gray-100 text-gray-800';
default:
return 'bg-gray-100 text-gray-800';
}
};
const formatGroupName = (group) => {
if (!group) return '';
return group.replace(/_/g, ' ');
};
const handleLogout = async () => {
setIsOpen(false);
await logout();
@@ -62,7 +71,7 @@ export default function UserMenu({ onManageUsers, onAuditLog }) {
</div>
<div className="text-left hidden sm:block">
<p className="text-sm font-medium text-gray-900">{user.username}</p>
<p className="text-xs text-gray-500 capitalize">{user.role}</p>
<p className="text-xs text-gray-500">{formatGroupName(user.group)}</p>
</div>
<ChevronDown className={`w-4 h-4 text-gray-500 transition-transform ${isOpen ? 'rotate-180' : ''}`} />
</button>
@@ -72,8 +81,8 @@ export default function UserMenu({ onManageUsers, onAuditLog }) {
<div className="px-4 py-3 border-b border-gray-100">
<p className="text-sm font-medium text-gray-900">{user.username}</p>
<p className="text-sm text-gray-500">{user.email}</p>
<span className={`inline-block mt-2 px-2 py-1 rounded text-xs font-medium ${getRoleBadgeColor(user.role)}`}>
{user.role.charAt(0).toUpperCase() + user.role.slice(1)}
<span className={`inline-block mt-2 px-2 py-1 rounded text-xs font-medium ${getGroupBadgeColor(user.group)}`}>
{formatGroupName(user.group)}
</span>
</div>