Skip to content

Commit 20e4cfb

Browse files
committed
[Doc] - document support for wildcard action in canAccess and IfCanAccess fallback
1 parent 60a4332 commit 20e4cfb

3 files changed

Lines changed: 132 additions & 3 deletions

File tree

docs/IfCanAccess.md

Lines changed: 85 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ title: "IfCanAccess"
77

88
This component, part of [the ra-rbac module](https://marmelab.com/ra-enterprise/modules/ra-rbac#ifcanaccess)<img class="icon" src="./img/premium.svg" />, relies on the `authProvider` to render its child only if the user has the right permissions. It accepts the following props:
99

10-
- `action` (string, required): the action to check, e.g. 'read', 'list', 'export', 'delete', etc.
11-
- `resource` (string, optional): the resource to check, e.g. 'users', 'comments', 'posts', etc. Defaults to the current resource.
12-
- `record` (object, optional): the record to check. If passed, the child only renders if the user has permissions for that record, e.g. `{ id: 123, firstName: "John", lastName: "Doe" }`
10+
- `action` (`string`, required): the action to check, e.g. 'read', 'list', 'export', 'delete', etc.
11+
- `resource` (`string`, optional): the resource to check, e.g. 'users', 'comments', 'posts', etc. Falls back to the current resource context if absent.
12+
- `record` (`object`, optional): the record to check. If passed, the child only renders if the user has permissions for that record, e.g. `{ id: 123, firstName: "John", lastName: "Doe" }`
13+
- `fallback` (`ReactNode`, optional): The element to render when the user does not have the permission. Defaults to `null`.
1314

1415
Additional props are passed down to the child element.
1516

@@ -31,3 +32,84 @@ const RecordToolbar = () => (
3132
</Toolbar>
3233
);
3334
```
35+
36+
## Showing An Access Denied Message Instead Of A Not Found Page
37+
38+
`ra-rbac` shows a Not Found page when users try to access a page they don't have the permissions for. It is considered good security practice not to disclose to a potentially malicious user that a page exists if they are not allowed to see it.
39+
40+
However, should you prefer to show an Access Denied screen in those cases, you can do so by using the `Resource` component from `react-admin` instead of the one from `ra-rbac` and leveraging the `IfCanAccess` component in your views:
41+
42+
```tsx
43+
// In src/App.tsx
44+
import { Admin, Resource } from 'react-admin';
45+
import { dataProvider } from './dataProvider';
46+
import { authProvider } from './authProvider';
47+
import posts from './posts';
48+
49+
export const App = () => (
50+
<Admin dataProvider={dataProvider} authProvider={authProvider}>
51+
<Resource name="posts" {...posts} />
52+
</Admin>
53+
);
54+
55+
// in src/AccessDenied.tsx
56+
export const AccessDenied = () => (
57+
<Typography>You don't have the required permissions to access this page.</Typography>
58+
);
59+
60+
// in src/posts/PostCreate.tsx
61+
import { Create, SimpleForm, TextInput } from 'react-admin';
62+
import { IfCanAccess } from '@react-admin/ra-rbac';
63+
import { AccessDenied } from '../AccessDenied';
64+
65+
export const PostCreate = () => (
66+
<IfCanAccess action="create" fallback={<AccessDenied />}>
67+
<Create>
68+
<SimpleForm>
69+
<TextInput source="title" />
70+
</SimpleForm>
71+
</Create>
72+
</IfCanAccess>
73+
);
74+
```
75+
76+
You can also choose to redirect users to a [custom route](https://marmelab.com/react-admin/CustomRoutes.html):
77+
78+
```tsx
79+
// In src/App.tsx
80+
import { Admin, CustomRoutes, Resource } from 'react-admin';
81+
import { Route } from 'react-router';
82+
import { dataProvider } from './dataProvider';
83+
import { authProvider } from './authProvider';
84+
import posts from './posts';
85+
import { AccessDenied } from '../AccessDenied';
86+
87+
export const App = () => (
88+
<Admin dataProvider={dataProvider} authProvider={authProvider}>
89+
<CustomRoutes>
90+
<Route path="access-denied" element={<AccessDenied />} />
91+
</CustomRoutes>
92+
<Resource name="posts" {...posts} />
93+
</Admin>
94+
);
95+
96+
// in src/AccessDenied.tsx
97+
export const AccessDenied = () => (
98+
<Typography>You don't have the required permissions to access this page.</Typography>
99+
);
100+
101+
// in src/posts/PostCreate.tsx
102+
import { Create, SimpleForm, TextInput } from 'react-admin';
103+
import { IfCanAccess } from '@react-admin/ra-rbac';
104+
import { Navigate } from 'react-router-dom';
105+
106+
export const PostCreate = () => (
107+
<IfCanAccess action="create" fallback={<Navigate to="/access-denied" />}>
108+
<Create>
109+
<SimpleForm>
110+
<TextInput source="title" />
111+
</SimpleForm>
112+
</Create>
113+
</IfCanAccess>
114+
);
115+
```

docs/canAccess.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,44 @@ const ProductList = () => {
6868

6969
**Tip**: Ra-rbac actually proposes a `<Datagrid>` component that hides columns depending on permissions. Check [the RBAC documentation](./AuthRBAC.md) for details.
7070

71+
You don't have to provide an `action` if you just want to know whether users can access any screen of the resource. This is useful to leverage `canAccess` in an `<Admin>` component children function:
72+
73+
```tsx
74+
import { Admin, ListGuesser, EditGuesser } from 'react-admin';
75+
import { Resource, canAccess } from '@react-admin/ra-rbac';
76+
import { dataProvider } from './dataProvider';
77+
78+
const authProvider = {
79+
checkAuth: () => Promise.resolve(),
80+
login: () => Promise.resolve(),
81+
logout: () => Promise.resolve(),
82+
checkError: () => Promise.resolve(),
83+
getPermissions: () =>
84+
Promise.resolve([
85+
{ action: 'list', resource: 'products' },
86+
{ action: 'edit', resource: 'categories' },
87+
]),
88+
};
89+
90+
export const MyApp = () => (
91+
<Admin authProvider={authProvider} dataProvider={dataProvider}>
92+
{(permissions: Permissions) => (
93+
<>
94+
{canAccess({ permissions, resource: 'products' }) ? (
95+
<Resource name="products" list={ListGuesser} />
96+
) : null}
97+
{canAccess({ permissions, resource: 'categories' }) ? (
98+
<Resource name="categories" list={ListGuesser} edit={EditGuesser} />
99+
) : null}
100+
{canAccess({ permissions, resource: 'commands' }) ? (
101+
<Resource name="commands" list={ListGuesser} />
102+
) : null}
103+
</>
104+
)}
105+
</Admin>
106+
);
107+
```
108+
109+
In this example, users will see the products list and will be able to click on its category link to edit the category. However, they won't see the categories list nor the commands list.
110+
71111
**Tip**: Instead of calling `usePermissions` and `canAccess`, you can call [the `useCanAccess` hook](./useCanAccess.md).

docs/useCanAccess.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,13 @@ const authProvider= {
3838
}),
3939
};
4040

41+
const { canAccess: canUseCompanyResource } = useCanAccess({
42+
resource: 'companies',
43+
}); // canUseCompanyResource is true
44+
const { canAccess: canUseCompanyResourceFromWildcard } = useCanAccess({
45+
resource: 'companies',
46+
action: '*',
47+
}); // canUseCompanyResourceFromWildcard is true
4148
const { canAccess: canReadCompanies } = useCanAccess({ action: "read", resource: "companies" }); // canReadCompanies is true
4249
const { canAccess: canCreatePeople } = useCanAccess({ action: "create", resource: "people" }); // canCreatePeople is true
4350
const { canAccess: canExportPeople } = useCanAccess({ action: "export", resource: "people" }); // canExportPeople is false

0 commit comments

Comments
 (0)