Skip to content

Commit 0d6c83e

Browse files
authored
Merge pull request marmelab#10300 from marmelab/crm-can-access
Leverage canAccess in CRM demo
2 parents 716e58b + 5ae249c commit 0d6c83e

File tree

9 files changed

+50
-62
lines changed

9 files changed

+50
-62
lines changed

examples/crm/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@
1414
"faker": "~5.4.0",
1515
"lodash": "~4.17.5",
1616
"papaparse": "^5.4.1",
17-
"ra-data-fakerest": "^5.0.0",
17+
"ra-data-fakerest": "^5.3.0",
1818
"react": "^18.3.1",
19-
"react-admin": "^5.0.0",
19+
"react-admin": "^5.3.0",
2020
"react-cropper": "^2.3.3",
2121
"react-dom": "^18.3.1",
2222
"react-error-boundary": "^4.0.3",

examples/crm/src/layout/Header.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ import {
1212
Typography,
1313
} from '@mui/material';
1414
import {
15+
CanAccess,
1516
LoadingIndicator,
1617
Logout,
17-
usePermissions,
1818
UserMenu,
1919
useUserMenu,
2020
} from 'react-admin';
@@ -24,7 +24,6 @@ import { useConfigurationContext } from '../root/ConfigurationContext';
2424
const Header = () => {
2525
const { logo, title } = useConfigurationContext();
2626
const location = useLocation();
27-
const { permissions } = usePermissions();
2827

2928
let currentPath: string | boolean = '/';
3029
if (!!matchPath('/', location.pathname)) {
@@ -102,7 +101,9 @@ const Header = () => {
102101
<LoadingIndicator />
103102
<UserMenu>
104103
<ConfigurationMenu />
105-
{permissions === 'admin' && <UsersMenu />}
104+
<CanAccess resource="sales" action="list">
105+
<UsersMenu />
106+
</CanAccess>
106107
<Logout />
107108
</UserMenu>
108109
</Box>

examples/crm/src/login/SignupPage.tsx

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,8 @@ import {
66
TextField,
77
Typography,
88
} from '@mui/material';
9-
import { useMutation, useQuery } from '@tanstack/react-query';
10-
import {
11-
useDataProvider,
12-
useLogin,
13-
useNotify,
14-
usePermissions,
15-
} from 'react-admin';
9+
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
10+
import { useDataProvider, useLogin, useNotify } from 'react-admin';
1611
import { SubmitHandler, useForm } from 'react-hook-form';
1712
import { Navigate } from 'react-router';
1813
import { CrmDataProvider } from '../providers/types';
@@ -21,7 +16,7 @@ import { SignUpData } from '../types';
2116
import { LoginSkeleton } from './LoginSkeleton';
2217

2318
export const SignupPage = () => {
24-
const { refetch } = usePermissions();
19+
const queryClient = useQueryClient();
2520
const dataProvider = useDataProvider<CrmDataProvider>();
2621
const { logo, title } = useConfigurationContext();
2722
const { data: isInitialized, isPending } = useQuery({
@@ -43,7 +38,10 @@ export const SignupPage = () => {
4338
redirectTo: '/contacts',
4439
}).then(() => {
4540
notify('Initial user successfully created');
46-
refetch();
41+
// FIXME: We should probably provide a hook for that in the ra-core package
42+
queryClient.invalidateQueries({
43+
queryKey: ['auth', 'canAccess'],
44+
});
4745
});
4846
},
4947
onError: () => {
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// FIXME: This should be exported from the ra-core package
2+
type CanAccessParams<
3+
RecordType extends Record<string, any> = Record<string, any>,
4+
> = {
5+
action: string;
6+
resource: string;
7+
record?: RecordType;
8+
};
9+
10+
export const canAccess = <
11+
RecordType extends Record<string, any> = Record<string, any>,
12+
>(
13+
role: string,
14+
params: CanAccessParams<RecordType>
15+
) => {
16+
if (role === 'admin') {
17+
return true;
18+
}
19+
20+
// Non admins can't access the sales resource
21+
if (params.resource === 'sales') {
22+
return false;
23+
}
24+
25+
return true;
26+
};

examples/crm/src/providers/fakerest/authProvider.ts

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { AuthProvider } from 'react-admin';
22
import { Sale } from '../../types';
3+
import { canAccess } from '../commons/canAccess';
34
import { dataProvider } from './dataProvider';
45

56
export const DEFAULT_USER = {
@@ -50,19 +51,15 @@ export const authProvider: AuthProvider = {
5051
localStorage.getItem(USER_STORAGE_KEY)
5152
? Promise.resolve()
5253
: Promise.reject(),
53-
getPermissions: async () => {
54+
canAccess: async ({ signal, ...params }) => {
55+
// Get the current user
5456
const userItem = localStorage.getItem(USER_STORAGE_KEY);
5557
const localUser = userItem ? (JSON.parse(userItem) as Sale) : null;
56-
if (!localUser) {
57-
return Promise.reject('user is not logged in');
58-
}
58+
if (!localUser) return false;
5959

60-
// We fetch permissions from server to avoid local storage tampering
61-
const user = await dataProvider.getOne<Sale>('sales', {
62-
id: localUser.id,
63-
});
64-
65-
return user.data?.administrator ? 'admin' : 'user';
60+
// Compute access rights from the sale role
61+
const role = localUser.administrator ? 'admin' : 'user';
62+
return canAccess(role, params);
6663
},
6764
getIdentity: () => {
6865
const userItem = localStorage.getItem(USER_STORAGE_KEY);

examples/crm/src/sales/SalesCreate.tsx

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,14 @@ import {
44
SimpleForm,
55
useDataProvider,
66
useNotify,
7-
usePermissions,
87
useRedirect,
98
} from 'react-admin';
109
import { SubmitHandler } from 'react-hook-form';
11-
import { Navigate } from 'react-router';
1210
import { CrmDataProvider } from '../providers/types';
1311
import { SalesFormData } from '../types';
1412
import { SalesInputs } from './SalesInputs';
1513

1614
export function SalesCreate() {
17-
const { isPending, permissions } = usePermissions();
1815
const dataProvider = useDataProvider<CrmDataProvider>();
1916
const notify = useNotify();
2017
const redirect = useRedirect();
@@ -40,14 +37,6 @@ export function SalesCreate() {
4037
mutate(data);
4138
};
4239

43-
if (isPending) {
44-
return null;
45-
}
46-
47-
if (permissions !== 'admin') {
48-
return <Navigate to="/" />;
49-
}
50-
5140
return (
5241
<Container maxWidth="sm" sx={{ mt: 4 }}>
5342
<Card>

examples/crm/src/sales/SalesEdit.tsx

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,10 @@ import {
77
useDataProvider,
88
useEditController,
99
useNotify,
10-
usePermissions,
1110
useRecordContext,
1211
useRedirect,
1312
} from 'react-admin';
1413
import { SubmitHandler } from 'react-hook-form';
15-
import { Navigate } from 'react-router';
1614
import { CrmDataProvider } from '../providers/types';
1715
import { Sale, SalesFormData } from '../types';
1816
import { SalesInputs } from './SalesInputs';
@@ -26,8 +24,6 @@ function EditToolbar() {
2624
}
2725

2826
export function SalesEdit() {
29-
const { isPending, permissions } = usePermissions();
30-
3127
const { record } = useEditController();
3228

3329
const dataProvider = useDataProvider<CrmDataProvider>();
@@ -54,14 +50,6 @@ export function SalesEdit() {
5450
mutate(data);
5551
};
5652

57-
if (isPending) {
58-
return null;
59-
}
60-
61-
if (permissions !== 'admin') {
62-
return <Navigate to="/" />;
63-
}
64-
6553
return (
6654
<Container maxWidth="sm" sx={{ mt: 4 }}>
6755
<Card>

examples/crm/src/sales/SalesList.tsx

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,8 @@ import {
77
SearchInput,
88
TextField,
99
TopToolbar,
10-
usePermissions,
1110
useRecordContext,
1211
} from 'react-admin';
13-
import { Navigate } from 'react-router';
1412

1513
const SalesListActions = () => (
1614
<TopToolbar>
@@ -47,15 +45,6 @@ const OptionsField = (_props: { label?: string | boolean }) => {
4745
};
4846

4947
export function SalesList() {
50-
const { isPending, permissions } = usePermissions();
51-
if (isPending) {
52-
return null;
53-
}
54-
55-
if (permissions !== 'admin') {
56-
return <Navigate to="/" />;
57-
}
58-
5948
return (
6049
<Stack gap={4}>
6150
<List

yarn.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17360,7 +17360,7 @@ __metadata:
1736017360
languageName: unknown
1736117361
linkType: soft
1736217362

17363-
"ra-data-fakerest@npm:^5.0.0, ra-data-fakerest@npm:^5.3.1, ra-data-fakerest@workspace:packages/ra-data-fakerest":
17363+
"ra-data-fakerest@npm:^5.3.0, ra-data-fakerest@npm:^5.3.1, ra-data-fakerest@workspace:packages/ra-data-fakerest":
1736417364
version: 0.0.0-use.local
1736517365
resolution: "ra-data-fakerest@workspace:packages/ra-data-fakerest"
1736617366
dependencies:
@@ -17716,9 +17716,9 @@ __metadata:
1771617716
faker: "npm:~5.4.0"
1771717717
lodash: "npm:~4.17.5"
1771817718
papaparse: "npm:^5.4.1"
17719-
ra-data-fakerest: "npm:^5.0.0"
17719+
ra-data-fakerest: "npm:^5.3.0"
1772017720
react: "npm:^18.3.1"
17721-
react-admin: "npm:^5.0.0"
17721+
react-admin: "npm:^5.3.0"
1772217722
react-cropper: "npm:^2.3.3"
1772317723
react-dom: "npm:^18.3.1"
1772417724
react-error-boundary: "npm:^4.0.3"
@@ -17781,7 +17781,7 @@ __metadata:
1778117781
languageName: unknown
1778217782
linkType: soft
1778317783

17784-
"react-admin@npm:^5.0.0, react-admin@npm:^5.3.1, react-admin@workspace:packages/react-admin":
17784+
"react-admin@npm:^5.0.0, react-admin@npm:^5.3.0, react-admin@npm:^5.3.1, react-admin@workspace:packages/react-admin":
1778517785
version: 0.0.0-use.local
1778617786
resolution: "react-admin@workspace:packages/react-admin"
1778717787
dependencies:

0 commit comments

Comments
 (0)