Compare commits

...

1 Commits

Author SHA1 Message Date
b174ba4f14 feat(web): RBAC access guard on users settings page (MS21-RBAC-002/003/004)
All checks were successful
ci/woodpecker/push/web Pipeline was successful
2026-02-28 17:19:57 -06:00

View File

@@ -42,6 +42,7 @@ import {
AlertDialogHeader, AlertDialogHeader,
AlertDialogTitle, AlertDialogTitle,
} from "@/components/ui/alert-dialog"; } from "@/components/ui/alert-dialog";
import { fetchUserWorkspaces } from "@/lib/api/workspaces";
import { import {
deactivateUser, deactivateUser,
fetchAdminUsers, fetchAdminUsers,
@@ -105,6 +106,8 @@ export default function UsersSettingsPage(): ReactElement {
const [editError, setEditError] = useState<string | null>(null); const [editError, setEditError] = useState<string | null>(null);
const [isEditing, setIsEditing] = useState<boolean>(false); const [isEditing, setIsEditing] = useState<boolean>(false);
const [isAdmin, setIsAdmin] = useState<boolean | null>(null);
const loadUsers = useCallback(async (showLoadingState: boolean): Promise<void> => { const loadUsers = useCallback(async (showLoadingState: boolean): Promise<void> => {
try { try {
if (showLoadingState) { if (showLoadingState) {
@@ -129,6 +132,20 @@ export default function UsersSettingsPage(): ReactElement {
void loadUsers(true); void loadUsers(true);
}, [loadUsers]); }, [loadUsers]);
useEffect(() => {
fetchUserWorkspaces()
.then((workspaces) => {
const adminRoles: WorkspaceMemberRole[] = [
WorkspaceMemberRole.OWNER,
WorkspaceMemberRole.ADMIN,
];
setIsAdmin(workspaces.some((ws) => adminRoles.includes(ws.role)));
})
.catch(() => {
setIsAdmin(true); // fail open
});
}, []);
function resetInviteForm(): void { function resetInviteForm(): void {
setInviteForm(INITIAL_INVITE_FORM); setInviteForm(INITIAL_INVITE_FORM);
setInviteError(null); setInviteError(null);
@@ -212,6 +229,17 @@ export default function UsersSettingsPage(): ReactElement {
} }
} }
if (isAdmin === false) {
return (
<div className="p-8 max-w-2xl">
<div className="rounded-lg border border-red-200 bg-red-50 p-6 text-center">
<p className="text-lg font-semibold text-red-700">Access Denied</p>
<p className="mt-2 text-sm text-red-600">You need Admin or Owner role to manage users.</p>
</div>
</div>
);
}
return ( return (
<div className="max-w-6xl mx-auto p-6 space-y-6"> <div className="max-w-6xl mx-auto p-6 space-y-6">
<div className="flex items-start justify-between gap-4"> <div className="flex items-start justify-between gap-4">