@strapi/plugin-users-permissions 0.0.0-next.de5394e73076ccf7aca1e28dc68894e3c43f8b91 → 0.0.0-next.e037a7b2d1f48b7bd8bcd0d9400ca0f9ded8a982

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/admin/src/components/Permissions/index.js +2 -4
  2. package/admin/src/{permissions.js → constants.js} +1 -3
  3. package/admin/src/index.js +12 -13
  4. package/admin/src/pages/AdvancedSettings/index.js +48 -35
  5. package/admin/src/pages/EmailTemplates/index.js +66 -55
  6. package/admin/src/pages/Providers/index.js +66 -64
  7. package/admin/src/{hooks → pages/Roles/hooks}/usePlugins.js +15 -8
  8. package/admin/src/pages/Roles/index.js +12 -9
  9. package/admin/src/pages/Roles/pages/CreatePage.js +190 -0
  10. package/admin/src/pages/Roles/pages/EditPage.js +211 -0
  11. package/admin/src/pages/Roles/{ListPage → pages/ListPage}/components/TableBody.js +44 -14
  12. package/admin/src/pages/Roles/{ListPage → pages/ListPage}/index.js +31 -32
  13. package/admin/src/pages/Roles/{ListPage → pages/ListPage}/utils/api.js +2 -4
  14. package/admin/src/translations/zh-Hans.json +80 -80
  15. package/admin/src/utils/index.js +0 -1
  16. package/documentation/content-api.yaml +1 -1
  17. package/jest.config.front.js +1 -1
  18. package/package.json +10 -10
  19. package/server/bootstrap/index.js +35 -0
  20. package/server/controllers/auth.js +46 -13
  21. package/server/controllers/user.js +12 -1
  22. package/server/middlewares/rateLimit.js +41 -21
  23. package/admin/src/hooks/index.js +0 -5
  24. package/admin/src/hooks/useFetchRole/index.js +0 -67
  25. package/admin/src/hooks/useFetchRole/reducer.js +0 -31
  26. package/admin/src/hooks/useForm/index.js +0 -70
  27. package/admin/src/hooks/useForm/reducer.js +0 -40
  28. package/admin/src/hooks/useRolesList/index.js +0 -65
  29. package/admin/src/hooks/useRolesList/init.js +0 -5
  30. package/admin/src/hooks/useRolesList/reducer.js +0 -31
  31. package/admin/src/pages/AdvancedSettings/utils/api.js +0 -18
  32. package/admin/src/pages/EmailTemplates/utils/api.js +0 -18
  33. package/admin/src/pages/Providers/reducer.js +0 -54
  34. package/admin/src/pages/Providers/utils/api.js +0 -26
  35. package/admin/src/pages/Providers/utils/createProvidersArray.js +0 -21
  36. package/admin/src/pages/Roles/CreatePage.js +0 -185
  37. package/admin/src/pages/Roles/EditPage.js +0 -197
  38. package/admin/src/pages/Roles/ProtectedCreatePage.js +0 -15
  39. package/admin/src/pages/Roles/ProtectedEditPage.js +0 -15
  40. package/admin/src/pages/Roles/ProtectedListPage.js +0 -17
  41. package/admin/src/utils/getRequestURL.js +0 -5
@@ -1,4 +1,4 @@
1
- import React, { useMemo, useRef, useState } from 'react';
1
+ import * as React from 'react';
2
2
 
3
3
  import {
4
4
  ContentLayout,
@@ -13,7 +13,6 @@ import {
13
13
  Thead,
14
14
  Tr,
15
15
  Typography,
16
- useNotifyAT,
17
16
  VisuallyHidden,
18
17
  } from '@strapi/design-system';
19
18
  import {
@@ -22,6 +21,9 @@ import {
22
21
  onRowClick,
23
22
  SettingsPageTitle,
24
23
  stopPropagation,
24
+ useAPIErrorHandler,
25
+ useCollator,
26
+ useFetchClient,
25
27
  useFocusWhenNavigate,
26
28
  useNotification,
27
29
  useOverlayBlocker,
@@ -29,103 +31,102 @@ import {
29
31
  useTracking,
30
32
  } from '@strapi/helper-plugin';
31
33
  import { Pencil } from '@strapi/icons';
32
- import has from 'lodash/has';
33
34
  import upperFirst from 'lodash/upperFirst';
34
35
  import { useIntl } from 'react-intl';
35
36
  import { useMutation, useQuery, useQueryClient } from 'react-query';
36
37
 
37
38
  import FormModal from '../../components/FormModal';
38
- import pluginPermissions from '../../permissions';
39
+ import { PERMISSIONS } from '../../constants';
39
40
  import { getTrad } from '../../utils';
40
41
 
41
- import { fetchData, putProvider } from './utils/api';
42
- import createProvidersArray from './utils/createProvidersArray';
43
42
  import forms from './utils/forms';
44
43
 
45
44
  export const ProvidersPage = () => {
46
- const { formatMessage } = useIntl();
47
- useFocusWhenNavigate();
48
- const { notifyStatus } = useNotifyAT();
45
+ const { formatMessage, locale } = useIntl();
49
46
  const queryClient = useQueryClient();
50
47
  const { trackUsage } = useTracking();
51
- const trackUsageRef = useRef(trackUsage);
52
- const [isOpen, setIsOpen] = useState(false);
53
- const [isSubmiting, setIsSubmiting] = useState(false);
54
- const [providerToEditName, setProviderToEditName] = useState(null);
48
+ const [isOpen, setIsOpen] = React.useState(false);
49
+ const [providerToEditName, setProviderToEditName] = React.useState(null);
55
50
  const toggleNotification = useNotification();
56
51
  const { lockApp, unlockApp } = useOverlayBlocker();
52
+ const { get, put } = useFetchClient();
53
+ const { formatAPIError } = useAPIErrorHandler();
54
+ const formatter = useCollator(locale, {
55
+ sensitivity: 'base',
56
+ });
57
57
 
58
- const updatePermissions = useMemo(() => {
59
- return { update: pluginPermissions.updateProviders };
60
- }, []);
58
+ useFocusWhenNavigate();
61
59
 
62
60
  const {
63
- isLoading: isLoadingForPermissions,
61
+ isLoading: isLoadingPermissions,
64
62
  allowedActions: { canUpdate },
65
- } = useRBAC(updatePermissions);
63
+ } = useRBAC({ update: PERMISSIONS.updateProviders });
66
64
 
67
- const {
68
- isLoading: isLoadingForData,
69
- data: modifiedData,
70
- isFetching,
71
- } = useQuery('get-providers', () => fetchData(toggleNotification), {
72
- onSuccess() {
73
- notifyStatus(
74
- formatMessage({
75
- id: getTrad('Providers.data.loaded'),
76
- defaultMessage: 'Providers have been loaded',
77
- })
78
- );
79
- },
80
- initialData: {},
81
- });
65
+ const { isLoading: isLoadingData, data } = useQuery(
66
+ ['users-permissions', 'get-providers'],
67
+ async () => {
68
+ const { data } = await get('/users-permissions/providers');
82
69
 
83
- const isLoading = isLoadingForData || isFetching;
70
+ return data;
71
+ },
72
+ {
73
+ initialData: {},
74
+ }
75
+ );
84
76
 
85
- const submitMutation = useMutation(putProvider, {
77
+ const submitMutation = useMutation((body) => put('/users-permissions/providers', body), {
86
78
  async onSuccess() {
87
- await queryClient.invalidateQueries('get-providers');
79
+ await queryClient.invalidateQueries(['users-permissions', 'providers']);
80
+
88
81
  toggleNotification({
89
- type: 'info',
82
+ type: 'success',
90
83
  message: { id: getTrad('notification.success.submit') },
91
84
  });
92
85
 
93
- trackUsageRef.current('didEditAuthenticationProvider');
94
- setIsSubmiting(false);
86
+ trackUsage('didEditAuthenticationProvider');
87
+
95
88
  handleToggleModal();
96
89
  unlockApp();
97
90
  },
98
- onError() {
91
+ onError(error) {
99
92
  toggleNotification({
100
93
  type: 'warning',
101
- message: { id: 'notification.error' },
94
+ message: formatAPIError(error),
102
95
  });
96
+
103
97
  unlockApp();
104
- setIsSubmiting(false);
105
98
  },
106
99
  refetchActive: false,
107
100
  });
108
101
 
109
- const providers = useMemo(() => createProvidersArray(modifiedData), [modifiedData]);
102
+ const providers = Object.entries(data)
103
+ .reduce((acc, [name, provider]) => {
104
+ const { icon, enabled, subdomain } = provider;
105
+
106
+ acc.push({
107
+ name,
108
+ icon: icon === 'envelope' ? ['fas', 'envelope'] : ['fab', icon],
109
+ enabled,
110
+ subdomain,
111
+ });
112
+
113
+ return acc;
114
+ }, [])
115
+ .sort((a, b) => formatter.compare(a.name, b.name));
110
116
 
111
- const rowCount = providers.length;
117
+ const isLoading = isLoadingData || isLoadingPermissions;
112
118
 
113
- const isProviderWithSubdomain = useMemo(() => {
119
+ const isProviderWithSubdomain = React.useMemo(() => {
114
120
  if (!providerToEditName) {
115
121
  return false;
116
122
  }
117
123
 
118
124
  const providerToEdit = providers.find((obj) => obj.name === providerToEditName);
119
125
 
120
- return has(providerToEdit, 'subdomain');
126
+ return !!providerToEdit?.subdomain;
121
127
  }, [providers, providerToEditName]);
122
128
 
123
- const pageTitle = formatMessage({
124
- id: getTrad('HeaderNav.link.providers'),
125
- defaultMessage: 'Providers',
126
- });
127
-
128
- const layoutToRender = useMemo(() => {
129
+ const layoutToRender = React.useMemo(() => {
129
130
  if (providerToEditName === 'email') {
130
131
  return forms.email;
131
132
  }
@@ -149,20 +150,21 @@ export const ProvidersPage = () => {
149
150
  };
150
151
 
151
152
  const handleSubmit = async (values) => {
152
- setIsSubmiting(true);
153
-
154
153
  lockApp();
155
154
 
156
- trackUsageRef.current('willEditAuthenticationProvider');
155
+ trackUsage('willEditAuthenticationProvider');
157
156
 
158
- const body = { ...modifiedData, [providerToEditName]: values };
159
-
160
- submitMutation.mutate({ providers: body });
157
+ submitMutation.mutate({ providers: { ...data, [providerToEditName]: values } });
161
158
  };
162
159
 
163
160
  return (
164
161
  <Layout>
165
- <SettingsPageTitle name={pageTitle} />
162
+ <SettingsPageTitle
163
+ name={formatMessage({
164
+ id: getTrad('HeaderNav.link.providers'),
165
+ defaultMessage: 'Providers',
166
+ })}
167
+ />
166
168
  <Main>
167
169
  <HeaderLayout
168
170
  title={formatMessage({
@@ -170,11 +172,11 @@ export const ProvidersPage = () => {
170
172
  defaultMessage: 'Providers',
171
173
  })}
172
174
  />
173
- {isLoading || isLoadingForPermissions ? (
175
+ {isLoading ? (
174
176
  <LoadingIndicatorPage />
175
177
  ) : (
176
178
  <ContentLayout>
177
- <Table colCount={3} rowCount={rowCount + 1}>
179
+ <Table colCount={3} rowCount={providers.length + 1}>
178
180
  <Thead>
179
181
  <Tr>
180
182
  <Th>
@@ -247,9 +249,9 @@ export const ProvidersPage = () => {
247
249
  )}
248
250
  </Main>
249
251
  <FormModal
250
- initialData={modifiedData[providerToEditName]}
252
+ initialData={data[providerToEditName]}
251
253
  isOpen={isOpen}
252
- isSubmiting={isSubmiting}
254
+ isSubmiting={submitMutation.isLoading}
253
255
  layout={layoutToRender}
254
256
  headerBreadcrumbs={[
255
257
  formatMessage({
@@ -267,7 +269,7 @@ export const ProvidersPage = () => {
267
269
  };
268
270
 
269
271
  const ProtectedProvidersPage = () => (
270
- <CheckPagePermissions permissions={pluginPermissions.readProviders}>
272
+ <CheckPagePermissions permissions={PERMISSIONS.readProviders}>
271
273
  <ProvidersPage />
272
274
  </CheckPagePermissions>
273
275
  );
@@ -3,8 +3,7 @@ import { useEffect } from 'react';
3
3
  import { useNotification, useFetchClient, useAPIErrorHandler } from '@strapi/helper-plugin';
4
4
  import { useQueries } from 'react-query';
5
5
 
6
- import pluginId from '../pluginId';
7
- import { cleanPermissions, getTrad } from '../utils';
6
+ import { cleanPermissions, getTrad } from '../../../utils';
8
7
 
9
8
  export const usePlugins = () => {
10
9
  const toggleNotification = useNotification();
@@ -21,19 +20,23 @@ export const usePlugins = () => {
21
20
  { data: routes, isLoading: isLoadingRoutes, error: routesError, refetch: refetchRoutes },
22
21
  ] = useQueries([
23
22
  {
24
- queryKey: [pluginId, 'permissions'],
23
+ queryKey: ['users-permissions', 'permissions'],
25
24
  async queryFn() {
26
- const res = await get(`/${pluginId}/permissions`);
25
+ const {
26
+ data: { permissions },
27
+ } = await get(`/users-permissions/permissions`);
27
28
 
28
- return res.data.permissions;
29
+ return permissions;
29
30
  },
30
31
  },
31
32
  {
32
- queryKey: [pluginId, 'routes'],
33
+ queryKey: ['users-permissions', 'routes'],
33
34
  async queryFn() {
34
- const res = await get(`/${pluginId}/routes`);
35
+ const {
36
+ data: { routes },
37
+ } = await get(`/users-permissions/routes`);
35
38
 
36
- return res.data.routes;
39
+ return routes;
37
40
  },
38
41
  },
39
42
  ]);
@@ -63,8 +66,12 @@ export const usePlugins = () => {
63
66
  const isLoading = isLoadingPermissions || isLoadingRoutes;
64
67
 
65
68
  return {
69
+ // TODO: these return values need to be memoized, otherwise
70
+ // they will create infinite rendering loops when used as
71
+ // effect dependencies
66
72
  permissions: permissions ? cleanPermissions(permissions) : {},
67
73
  routes: routes ?? {},
74
+
68
75
  getData: refetchQueries,
69
76
  isLoading,
70
77
  };
@@ -3,24 +3,27 @@ import React from 'react';
3
3
  import { AnErrorOccurred, CheckPagePermissions } from '@strapi/helper-plugin';
4
4
  import { Route, Switch } from 'react-router-dom';
5
5
 
6
- import pluginPermissions from '../../permissions';
7
- import pluginId from '../../pluginId';
6
+ import { PERMISSIONS } from '../../constants';
8
7
 
9
- import ProtectedRolesCreatePage from './ProtectedCreatePage';
10
- import ProtectedRolesEditPage from './ProtectedEditPage';
11
- import ProtectedRolesListPage from './ProtectedListPage';
8
+ import { ProtectedRolesCreatePage } from './pages/CreatePage';
9
+ import { ProtectedRolesEditPage } from './pages/EditPage';
10
+ import { ProtectedRolesListPage } from './pages/ListPage';
12
11
 
13
12
  const Roles = () => {
14
13
  return (
15
- <CheckPagePermissions permissions={pluginPermissions.accessRoles}>
14
+ <CheckPagePermissions permissions={PERMISSIONS.accessRoles}>
16
15
  <Switch>
17
16
  <Route
18
- path={`/settings/${pluginId}/roles/new`}
17
+ path="/settings/users-permissions/roles/new"
19
18
  component={ProtectedRolesCreatePage}
20
19
  exact
21
20
  />
22
- <Route path={`/settings/${pluginId}/roles/:id`} component={ProtectedRolesEditPage} exact />
23
- <Route path={`/settings/${pluginId}/roles`} component={ProtectedRolesListPage} exact />
21
+ <Route
22
+ path="/settings/users-permissions/roles/:id"
23
+ component={ProtectedRolesEditPage}
24
+ exact
25
+ />
26
+ <Route path="/settings/users-permissions/roles" component={ProtectedRolesListPage} exact />
24
27
  <Route path="" component={AnErrorOccurred} />
25
28
  </Switch>
26
29
  </CheckPagePermissions>
@@ -0,0 +1,190 @@
1
+ import * as React from 'react';
2
+
3
+ import {
4
+ Button,
5
+ ContentLayout,
6
+ Flex,
7
+ Grid,
8
+ GridItem,
9
+ HeaderLayout,
10
+ Main,
11
+ Textarea,
12
+ TextInput,
13
+ Typography,
14
+ } from '@strapi/design-system';
15
+ import {
16
+ CheckPagePermissions,
17
+ Form,
18
+ SettingsPageTitle,
19
+ useFetchClient,
20
+ useNotification,
21
+ useOverlayBlocker,
22
+ useTracking,
23
+ } from '@strapi/helper-plugin';
24
+ import { Check } from '@strapi/icons';
25
+ import { Formik } from 'formik';
26
+ import { useIntl } from 'react-intl';
27
+ import { useMutation } from 'react-query';
28
+ import { useHistory } from 'react-router-dom';
29
+
30
+ import UsersPermissions from '../../../components/UsersPermissions';
31
+ import { PERMISSIONS } from '../../../constants';
32
+ import getTrad from '../../../utils/getTrad';
33
+ import { createRoleSchema } from '../constants';
34
+ import { usePlugins } from '../hooks/usePlugins';
35
+
36
+ export const CreatePage = () => {
37
+ const { formatMessage } = useIntl();
38
+ const toggleNotification = useNotification();
39
+ const { goBack } = useHistory();
40
+ const { lockApp, unlockApp } = useOverlayBlocker();
41
+ const { isLoading: isLoadingPlugins, permissions, routes } = usePlugins();
42
+ const { trackUsage } = useTracking();
43
+ const permissionsRef = React.useRef();
44
+ const { post } = useFetchClient();
45
+ const mutation = useMutation((body) => post(`/users-permissions/roles`, body), {
46
+ onError() {
47
+ toggleNotification({
48
+ type: 'warning',
49
+ message: {
50
+ id: 'notification.error',
51
+ defaultMessage: 'An error occurred',
52
+ },
53
+ });
54
+ },
55
+
56
+ onSuccess() {
57
+ trackUsage('didCreateRole');
58
+
59
+ toggleNotification({
60
+ type: 'success',
61
+ message: {
62
+ id: getTrad('Settings.roles.created'),
63
+ defaultMessage: 'Role created',
64
+ },
65
+ });
66
+
67
+ // Forcing redirecting since we don't have the id in the response
68
+ goBack();
69
+ },
70
+ });
71
+
72
+ const handleCreateRoleSubmit = async (data) => {
73
+ lockApp();
74
+
75
+ // TODO: refactor. Child -> parent component communication is evil;
76
+ // We should either move the provider one level up or move the state
77
+ // straight into redux.
78
+ const permissions = permissionsRef.current.getPermissions();
79
+
80
+ await mutation.mutate({ ...data, ...permissions, users: [] });
81
+
82
+ unlockApp();
83
+ };
84
+
85
+ return (
86
+ <Main>
87
+ {/* TODO: This needs to be translated */}
88
+ <SettingsPageTitle name="Roles" />
89
+ <Formik
90
+ enableReinitialize
91
+ initialValues={{ name: '', description: '' }}
92
+ onSubmit={handleCreateRoleSubmit}
93
+ validationSchema={createRoleSchema}
94
+ >
95
+ {({ handleSubmit, values, handleChange, errors }) => (
96
+ <Form noValidate onSubmit={handleSubmit}>
97
+ <HeaderLayout
98
+ primaryAction={
99
+ !isLoadingPlugins && (
100
+ <Button type="submit" loading={mutation.isLoading} startIcon={<Check />}>
101
+ {formatMessage({
102
+ id: 'global.save',
103
+ defaultMessage: 'Save',
104
+ })}
105
+ </Button>
106
+ )
107
+ }
108
+ title={formatMessage({
109
+ id: 'Settings.roles.create.title',
110
+ defaultMessage: 'Create a role',
111
+ })}
112
+ subtitle={formatMessage({
113
+ id: 'Settings.roles.create.description',
114
+ defaultMessage: 'Define the rights given to the role',
115
+ })}
116
+ />
117
+ <ContentLayout>
118
+ <Flex
119
+ background="neutral0"
120
+ direction="column"
121
+ alignItems="stretch"
122
+ gap={7}
123
+ hasRadius
124
+ paddingTop={6}
125
+ paddingBottom={6}
126
+ paddingLeft={7}
127
+ paddingRight={7}
128
+ shadow="filterShadow"
129
+ >
130
+ <Flex direction="column" alignItems="stretch">
131
+ <Typography variant="delta" as="h2">
132
+ {formatMessage({
133
+ id: getTrad('EditPage.form.roles'),
134
+ defaultMessage: 'Role details',
135
+ })}
136
+ </Typography>
137
+
138
+ <Grid gap={4}>
139
+ <GridItem col={6}>
140
+ <TextInput
141
+ name="name"
142
+ value={values.name || ''}
143
+ onChange={handleChange}
144
+ label={formatMessage({
145
+ id: 'global.name',
146
+ defaultMessage: 'Name',
147
+ })}
148
+ error={errors?.name ? formatMessage({ id: errors.name }) : false}
149
+ required
150
+ />
151
+ </GridItem>
152
+ <GridItem col={6}>
153
+ <Textarea
154
+ id="description"
155
+ value={values.description || ''}
156
+ onChange={handleChange}
157
+ label={formatMessage({
158
+ id: 'global.description',
159
+ defaultMessage: 'Description',
160
+ })}
161
+ error={
162
+ errors?.description ? formatMessage({ id: errors.description }) : false
163
+ }
164
+ required
165
+ />
166
+ </GridItem>
167
+ </Grid>
168
+ </Flex>
169
+
170
+ {!isLoadingPlugins && (
171
+ <UsersPermissions
172
+ ref={permissionsRef}
173
+ permissions={permissions}
174
+ routes={routes}
175
+ />
176
+ )}
177
+ </Flex>
178
+ </ContentLayout>
179
+ </Form>
180
+ )}
181
+ </Formik>
182
+ </Main>
183
+ );
184
+ };
185
+
186
+ export const ProtectedRolesCreatePage = () => (
187
+ <CheckPagePermissions permissions={PERMISSIONS.createRole}>
188
+ <CreatePage />
189
+ </CheckPagePermissions>
190
+ );