Skip to content

Commit f4500f4

Browse files
committed
Improve doc
1 parent 20e4cfb commit f4500f4

2 files changed

Lines changed: 278 additions & 100 deletions

File tree

docs/IfCanAccess.md

Lines changed: 100 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -5,65 +5,125 @@ title: "IfCanAccess"
55

66
# `<IfCanAccess>`
77

8-
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:
8+
This component, part of [the ra-rbac module](https://marmelab.com/ra-enterprise/modules/ra-rbac#ifcanaccess)<img class="icon" src="./img/premium.svg" />, renders its child only if the user has the right permissions.
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. 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`.
10+
## Usage
1411

15-
Additional props are passed down to the child element.
12+
Wrap the components that you want to add access control to with the `<IfCanAccess>` component.
13+
14+
For example, to display action buttons for a company record only if the user has the right permissions:
1615

1716
```jsx
1817
import { IfCanAccess } from '@react-admin/ra-rbac';
19-
import { Toolbar, DeleteButton, EditButton, ShowButton } from 'react-admin';
18+
import { Toolbar, DeleteButton, EditButton } from 'react-admin';
2019

21-
const RecordToolbar = () => (
20+
const CompanyRecordToolbar = () => (
2221
<Toolbar>
2322
<IfCanAccess action="edit">
2423
<EditButton />
2524
</IfCanAccess>
26-
<IfCanAccess action="show">
27-
<ShowButton />
28-
</IfCanAccess>
2925
<IfCanAccess action="delete">
3026
<DeleteButton />
3127
</IfCanAccess>
3228
</Toolbar>
3329
);
3430
```
3531

36-
## Showing An Access Denied Message Instead Of A Not Found Page
32+
With this code and the following user permissions:
3733

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.
34+
```jsx
35+
console.log(await authProvider.getPermissions())
36+
// [
37+
// { action: ["create", "edit"], resource: "companies" },
38+
// ...
39+
// ];
40+
```
3941

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:
42+
The `CompanyRecordToolbar` component will render the `<EditButton>` but not the `<DeleteButton>`.
4143

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';
44+
## Props
4845

49-
export const App = () => (
50-
<Admin dataProvider={dataProvider} authProvider={authProvider}>
51-
<Resource name="posts" {...posts} />
52-
</Admin>
53-
);
46+
| Prop | Required | Type | Default | Description |
47+
| --- | --- | --- | --- | --- |
48+
| `action` | Required | `string` | | The action to check, e.g. 'read', 'list', 'export', 'delete', etc. |
49+
| `resource` | Optional | `string` | | The resource to check, e.g. 'users', 'comments', 'posts', etc. Falls back to the current resource context if absent. |
50+
| `record` | Optional | `object` | | 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" }` |
51+
| `fallback` | Optional | `ReactNode` | `null` | The element to render when the user does not have the permission. Defaults to `null`. |
5452

55-
// in src/AccessDenied.tsx
56-
export const AccessDenied = () => (
57-
<Typography>You don't have the required permissions to access this page.</Typography>
58-
);
53+
Additional props are passed down to the child element.
54+
55+
## `action`
5956

57+
The `action` prop allows you to restrict a component to users who have a permission to use the specified action on the current resource.
58+
59+
For instance, if the user has the following permissions:
60+
61+
```jsx
62+
console.log(await authProvider.getPermissions())
63+
// [
64+
// { action: ["read", "create", "edit", "export"], resource: "companies" },
65+
// ...
66+
// ];
67+
```
68+
69+
To display the `ExportButton` in a `CompanyList` component, you would use:
70+
71+
```jsx
72+
<IfCanAccess action="export">
73+
<ExportButton />
74+
</IfCanAccess>
75+
```
76+
77+
## `resource`
78+
79+
By default, `<UseCanAccess>` uses the current resource (from the `ResourceContext`) to check permissions. You can override this behavior by passing the `resource` prop:
80+
81+
```jsx
82+
<IfCanAccess action="export" resource="companies">
83+
<ExportButton />
84+
</IfCanAccess>
85+
```
86+
87+
## `record`
88+
89+
RBAC allows to specify [record-level permissions](./AuthRBAC.md#record-level-permissions). These permissions are triggered when you specify the `record` prop.
90+
91+
For example, let's say a user has the permission to edit a company only if the company is in the same group as the user:
92+
93+
```jsx
94+
console.log(await authProvider.getPermissions())
95+
// [
96+
// { action: 'edit', resource: "companies', record: { group: 'middle_east' } },
97+
// ];
98+
```
99+
100+
To display the `EditButton` in a `CompanyShow` component, you would use:
101+
102+
```jsx
103+
const EditCompanyButton = () => {
104+
const record = useRecordContext();
105+
return (
106+
<IfCanAccess action="edit" record={record}>
107+
<EditButton />
108+
</IfCanAccess>
109+
);
110+
};
111+
```
112+
113+
## `fallback`
114+
115+
`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.
116+
117+
However, should you prefer to show an Access Denied screen in those cases, you can do so by specifying a `fallback` component in `<IfCanAccess>`:
118+
119+
```tsx
60120
// in src/posts/PostCreate.tsx
61121
import { Create, SimpleForm, TextInput } from 'react-admin';
62122
import { IfCanAccess } from '@react-admin/ra-rbac';
63-
import { AccessDenied } from '../AccessDenied';
123+
import { Navigate } from 'react-router-dom';
64124

65125
export const PostCreate = () => (
66-
<IfCanAccess action="create" fallback={<AccessDenied />}>
126+
<IfCanAccess action="create" fallback={<Navigate to="/access-denied" />}>
67127
<Create>
68128
<SimpleForm>
69129
<TextInput source="title" />
@@ -73,43 +133,29 @@ export const PostCreate = () => (
73133
);
74134
```
75135

76-
You can also choose to redirect users to a [custom route](https://marmelab.com/react-admin/CustomRoutes.html):
136+
**Tip**: This example uses a `Navigate` component to redirect to a custom page because you cannot use a `Redirect` component in this context. The `IfCanAccess` component uses a render prop, and `Redirect` only works in the render method of a component.
137+
138+
Note that you if you use the `fallback` prop for a CRUD page (Create, Edit, List, Show) as above, you must use the `<Resource>` component from `react-admin` rather than the one from `ra-rbac`. This is because `ra-rbac` already does the access control check, and would redirect to the Not Found page before the fallback component is rendered.
77139

78140
```tsx
79141
// In src/App.tsx
80142
import { Admin, CustomRoutes, Resource } from 'react-admin';
81143
import { Route } from 'react-router';
144+
82145
import { dataProvider } from './dataProvider';
83146
import { authProvider } from './authProvider';
84147
import posts from './posts';
85-
import { AccessDenied } from '../AccessDenied';
148+
149+
const AccessDenied = () => (
150+
<Typography>You don't have the required permissions to access this page.</Typography>
151+
);
86152

87153
export const App = () => (
88-
<Admin dataProvider={dataProvider} authProvider={authProvider}>
154+
<Admin dataProvider={dataProvider} authProvider={authProvider}>
89155
<CustomRoutes>
90156
<Route path="access-denied" element={<AccessDenied />} />
91157
</CustomRoutes>
92158
<Resource name="posts" {...posts} />
93159
</Admin>
94160
);
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-
);
115161
```

0 commit comments

Comments
 (0)