RBAC: Enforce read-only viewer role across backend and frontend
- Add global WriteAccessGuard that blocks POST/PUT/PATCH/DELETE for viewer role - Add @AllowViewer() decorator for endpoints viewers need (switch-org, intro-seen, AI recommendations) - Add useIsReadOnly hook to auth store for frontend role checks - Hide write UI (add/edit/delete/import buttons, inline editors) in all 13 data pages for viewers - Disable inline NumberInputs on Budgets and Monthly Actuals pages for viewers - Skip onboarding wizard for viewer role users Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,7 @@ import { notifications } from '@mantine/notifications';
|
||||
import { IconPlus, IconEdit } from '@tabler/icons-react';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import api from '../../services/api';
|
||||
import { useIsReadOnly } from '../../stores/authStore';
|
||||
|
||||
interface ReserveComponent {
|
||||
id: string; name: string; category: string; description: string;
|
||||
@@ -26,6 +27,7 @@ export function ReservesPage() {
|
||||
const [opened, { open, close }] = useDisclosure(false);
|
||||
const [editing, setEditing] = useState<ReserveComponent | null>(null);
|
||||
const queryClient = useQueryClient();
|
||||
const isReadOnly = useIsReadOnly();
|
||||
|
||||
const { data: components = [], isLoading } = useQuery<ReserveComponent[]>({
|
||||
queryKey: ['reserve-components'],
|
||||
@@ -89,7 +91,7 @@ export function ReservesPage() {
|
||||
<Stack>
|
||||
<Group justify="space-between">
|
||||
<Title order={2}>Reserve Components</Title>
|
||||
<Button leftSection={<IconPlus size={16} />} onClick={() => { setEditing(null); form.reset(); open(); }}>Add Component</Button>
|
||||
{!isReadOnly && <Button leftSection={<IconPlus size={16} />} onClick={() => { setEditing(null); form.reset(); open(); }}>Add Component</Button>}
|
||||
</Group>
|
||||
<SimpleGrid cols={{ base: 1, sm: 3 }}>
|
||||
<Card withBorder p="md">
|
||||
@@ -139,7 +141,7 @@ export function ReservesPage() {
|
||||
{c.condition_rating}/10
|
||||
</Badge>
|
||||
</Table.Td>
|
||||
<Table.Td><ActionIcon variant="subtle" onClick={() => handleEdit(c)}><IconEdit size={16} /></ActionIcon></Table.Td>
|
||||
<Table.Td>{!isReadOnly && <ActionIcon variant="subtle" onClick={() => handleEdit(c)}><IconEdit size={16} /></ActionIcon>}</Table.Td>
|
||||
</Table.Tr>
|
||||
);
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user