access control by role plus by person in orgsettings

This commit is contained in:
Satyam Rastogi
2026-05-14 18:13:42 +05:30
parent 45301dbabd
commit 72be14369e
4 changed files with 388 additions and 6 deletions
+65 -4
View File
@@ -13,6 +13,7 @@ import { usePermissions } from '../../hooks/usePermissions';
import { toast } from 'sonner';
import { computeRolePermissionStats } from '../../utils/permissions';
import OrgTeamManagement from '../../components/owner/OrgTeamManagement';
import AccessControlPersonModal from '../../components/owner/AccessControlPersonModal';
const COMMISSION_TYPES = [
{ value: 'flat', label: 'Flat Amount', hint: 'Fixed dollar amount' },
@@ -231,13 +232,25 @@ const RoleManagementSection = ({ roles, members }) => {
// ────────────────────────────────────────────────────
// 3. Access Control Matrix (Full)
// ────────────────────────────────────────────────────
const AccessControlMatrix = ({ permissions, permissionModules, roles, onUpdate }) => {
const AccessControlMatrix = ({ permissions, permissionModules, roles, onUpdate, members, userPermissionOverrides, onSetUserOverride, onRemoveUserOverride }) => {
const [filterModule, setFilterModule] = useState('all');
const [filterRole, setFilterRole] = useState('all');
const [changeLog, setChangeLog] = useState([]);
const [showLog, setShowLog] = useState(false);
const [personModalModule, setPersonModalModule] = useState(null);
const snapshotRef = useRef(JSON.stringify(permissions));
const moduleOverrideCounts = useMemo(() => {
const counts = {};
for (const o of (userPermissionOverrides || [])) {
if (!counts[o.moduleKey]) counts[o.moduleKey] = new Set();
counts[o.moduleKey].add(o.userId);
}
const result = {};
for (const [k, v] of Object.entries(counts)) result[k] = v.size;
return result;
}, [userPermissionOverrides]);
const moduleEntries = useMemo(() => {
const entries = Object.entries(permissionModules);
if (filterModule === 'all') return entries;
@@ -454,7 +467,7 @@ const AccessControlMatrix = ({ permissions, permissionModules, roles, onUpdate }
<Shield size={12} className="text-zinc-400 dark:text-zinc-600" />
{module.label}
</div>
<div className="flex gap-0.5">
<div className="flex items-center gap-0.5">
<button
type="button"
onClick={() => toggleAllForModule(moduleKey, true)}
@@ -471,6 +484,19 @@ const AccessControlMatrix = ({ permissions, permissionModules, roles, onUpdate }
>
<Square size={10} />
</button>
<button
type="button"
onClick={() => setPersonModalModule(moduleKey)}
title={`Manage person overrides for ${module.label}`}
className="relative p-0.5 rounded hover:bg-blue-50 dark:hover:bg-blue-500/10 text-zinc-300 dark:text-zinc-700 hover:text-blue-500 transition-colors"
>
<Users size={10} />
{moduleOverrideCounts[moduleKey] > 0 && (
<span className="absolute -top-1 -right-1 w-3 h-3 rounded-full bg-blue-500 text-white text-[7px] font-bold flex items-center justify-center">
{moduleOverrideCounts[moduleKey]}
</span>
)}
</button>
</div>
</div>
</td>
@@ -531,7 +557,22 @@ const AccessControlMatrix = ({ permissions, permissionModules, roles, onUpdate }
<div className="p-4 space-y-3">
{moduleEntries.map(([moduleKey, module]) => (
<div key={moduleKey} className="space-y-1.5">
<span className="text-[10px] font-bold uppercase tracking-wider text-zinc-500">{module.label}</span>
<div className="flex items-center justify-between">
<span className="text-[10px] font-bold uppercase tracking-wider text-zinc-500">{module.label}</span>
<button
type="button"
onClick={() => setPersonModalModule(moduleKey)}
className="relative flex items-center gap-1 px-2 py-0.5 rounded-md text-[9px] font-bold bg-blue-50 dark:bg-blue-500/10 text-blue-600 dark:text-blue-400 border border-blue-200 dark:border-blue-500/20 hover:bg-blue-100 dark:hover:bg-blue-500/20 transition-colors"
>
<Users size={9} />
Person
{moduleOverrideCounts[moduleKey] > 0 && (
<span className="ml-0.5 w-3.5 h-3.5 rounded-full bg-blue-500 text-white text-[7px] font-bold flex items-center justify-center">
{moduleOverrideCounts[moduleKey]}
</span>
)}
</button>
</div>
<div className="flex flex-wrap gap-1.5">
{module.actions.map(action => {
const hasPermission = (permissions[moduleKey]?.[role.key] || []).includes(action);
@@ -607,6 +648,21 @@ const AccessControlMatrix = ({ permissions, permissionModules, roles, onUpdate }
)}
</AnimatePresence>
{/* ── Person Override Modal ── */}
<AccessControlPersonModal
isOpen={!!personModalModule}
onClose={() => setPersonModalModule(null)}
moduleKey={personModalModule || ''}
moduleLabel={personModalModule ? (permissionModules[personModalModule]?.label || personModalModule) : ''}
actions={personModalModule ? (permissionModules[personModalModule]?.actions || []) : []}
permissions={permissions}
roles={roles}
members={members}
userPermissionOverrides={userPermissionOverrides}
onSetOverride={onSetUserOverride}
onRemoveOverride={onRemoveUserOverride}
/>
{/* ── Info Footer ── */}
<div className="p-4 bg-zinc-50 dark:bg-white/[0.02] rounded-xl border border-zinc-200 dark:border-white/5">
<div className="flex items-start gap-2">
@@ -614,6 +670,7 @@ const AccessControlMatrix = ({ permissions, permissionModules, roles, onUpdate }
<div className="text-[11px] text-zinc-500 space-y-1">
<p><span className="font-bold text-zinc-700 dark:text-zinc-400">Org Owner</span> always has full access to all modules and cannot be restricted.</p>
<p>Permission changes apply immediately to all users with the affected role. Use the <span className="font-mono">Save</span> button to persist changes or <span className="font-mono">Reset</span> to revert.</p>
<p><span className="font-bold text-zinc-700 dark:text-zinc-400">Person overrides</span> take priority over role permissions. A denied person override blocks access even if the role grants it.</p>
</div>
</div>
</div>
@@ -1053,7 +1110,7 @@ const OrgSettings = () => {
id: 'access',
icon: Shield,
title: 'Access Control Matrix',
subtitle: 'Configure module-level permissions per role',
subtitle: 'Configure module-level permissions by role and by person',
color: '#10b981',
content: (
<AccessControlMatrix
@@ -1061,6 +1118,10 @@ const OrgSettings = () => {
permissionModules={store.PERMISSION_MODULES}
roles={store.orgRoles}
onUpdate={store.updateOrgPermissions}
members={store.orgMembers}
userPermissionOverrides={store.userPermissionOverrides}
onSetUserOverride={store.setUserPermissionOverride}
onRemoveUserOverride={store.removeUserPermissionOverride}
/>
),
},