@strapi/plugin-users-permissions 0.0.0-next.c593a2735b35c69d2c421003b6f80adf72fcf16e → 0.0.0-next.ce51df0e18404afc8a1aa7f504c1006a7a221459

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 (40) hide show
  1. package/admin/src/components/Permissions/index.js +2 -4
  2. package/admin/src/index.js +16 -35
  3. package/admin/src/pages/AdvancedSettings/index.js +46 -30
  4. package/admin/src/pages/EmailTemplates/index.js +64 -53
  5. package/admin/src/pages/Providers/index.js +64 -62
  6. package/admin/src/{hooks → pages/Roles/hooks}/usePlugins.js +15 -8
  7. package/admin/src/pages/Roles/index.js +10 -7
  8. package/admin/src/pages/Roles/pages/CreatePage.js +190 -0
  9. package/admin/src/pages/Roles/pages/EditPage.js +211 -0
  10. package/admin/src/pages/Roles/{ListPage → pages/ListPage}/components/TableBody.js +44 -14
  11. package/admin/src/pages/Roles/{ListPage → pages/ListPage}/index.js +29 -30
  12. package/admin/src/pages/Roles/{ListPage → pages/ListPage}/utils/api.js +2 -4
  13. package/admin/src/translations/zh-Hans.json +80 -80
  14. package/admin/src/utils/index.js +0 -1
  15. package/documentation/content-api.yaml +1 -1
  16. package/jest.config.front.js +1 -1
  17. package/package.json +10 -10
  18. package/server/bootstrap/index.js +35 -0
  19. package/server/controllers/auth.js +46 -13
  20. package/server/controllers/user.js +12 -1
  21. package/server/middlewares/rateLimit.js +41 -21
  22. package/admin/src/hooks/index.js +0 -5
  23. package/admin/src/hooks/useFetchRole/index.js +0 -67
  24. package/admin/src/hooks/useFetchRole/reducer.js +0 -31
  25. package/admin/src/hooks/useForm/index.js +0 -70
  26. package/admin/src/hooks/useForm/reducer.js +0 -40
  27. package/admin/src/hooks/useRolesList/index.js +0 -65
  28. package/admin/src/hooks/useRolesList/init.js +0 -5
  29. package/admin/src/hooks/useRolesList/reducer.js +0 -31
  30. package/admin/src/pages/AdvancedSettings/utils/api.js +0 -18
  31. package/admin/src/pages/EmailTemplates/utils/api.js +0 -18
  32. package/admin/src/pages/Providers/reducer.js +0 -54
  33. package/admin/src/pages/Providers/utils/api.js +0 -26
  34. package/admin/src/pages/Providers/utils/createProvidersArray.js +0 -21
  35. package/admin/src/pages/Roles/CreatePage.js +0 -185
  36. package/admin/src/pages/Roles/EditPage.js +0 -197
  37. package/admin/src/pages/Roles/ProtectedCreatePage.js +0 -15
  38. package/admin/src/pages/Roles/ProtectedEditPage.js +0 -15
  39. package/admin/src/pages/Roles/ProtectedListPage.js +0 -17
  40. package/admin/src/utils/getRequestURL.js +0 -5
@@ -1,8 +1,7 @@
1
- import React, { useMemo, useState } from 'react';
1
+ import React, { useState } from 'react';
2
2
 
3
3
  import {
4
4
  ActionLayout,
5
- Button,
6
5
  ContentLayout,
7
6
  HeaderLayout,
8
7
  Layout,
@@ -16,9 +15,11 @@ import {
16
15
  VisuallyHidden,
17
16
  } from '@strapi/design-system';
18
17
  import {
18
+ CheckPagePermissions,
19
19
  CheckPermissions,
20
20
  ConfirmDialog,
21
21
  EmptyStateLayout,
22
+ LinkButton,
22
23
  LoadingIndicatorPage,
23
24
  NoPermissions,
24
25
  SearchURLQuery,
@@ -33,20 +34,17 @@ import {
33
34
  } from '@strapi/helper-plugin';
34
35
  import { Plus } from '@strapi/icons';
35
36
  import { useIntl } from 'react-intl';
36
- import { useMutation, useQuery, useQueryClient } from 'react-query';
37
- import { useHistory } from 'react-router-dom';
37
+ import { useMutation, useQuery } from 'react-query';
38
38
 
39
- import { PERMISSIONS } from '../../../constants';
40
- import pluginId from '../../../pluginId';
41
- import { getTrad } from '../../../utils';
39
+ import { PERMISSIONS } from '../../../../constants';
40
+ import { getTrad } from '../../../../utils';
42
41
 
43
42
  import TableBody from './components/TableBody';
44
43
  import { deleteData, fetchData } from './utils/api';
45
44
 
46
- const RoleListPage = () => {
45
+ export const RolesListPage = () => {
47
46
  const { trackUsage } = useTracking();
48
47
  const { formatMessage, locale } = useIntl();
49
- const { push } = useHistory();
50
48
  const toggleNotification = useNotification();
51
49
  const { notifyStatus } = useNotifyAT();
52
50
  const [{ query }] = useQueryParams();
@@ -56,26 +54,21 @@ const RoleListPage = () => {
56
54
  const [roleToDelete, setRoleToDelete] = useState();
57
55
  useFocusWhenNavigate();
58
56
 
59
- const queryClient = useQueryClient();
60
-
61
- const updatePermissions = useMemo(() => {
62
- return {
63
- create: PERMISSIONS.createRole,
64
- read: PERMISSIONS.readRoles,
65
- update: PERMISSIONS.updateRole,
66
- delete: PERMISSIONS.deleteRole,
67
- };
68
- }, []);
69
-
70
57
  const {
71
58
  isLoading: isLoadingForPermissions,
72
59
  allowedActions: { canRead, canDelete },
73
- } = useRBAC(updatePermissions);
60
+ } = useRBAC({
61
+ create: PERMISSIONS.createRole,
62
+ read: PERMISSIONS.readRoles,
63
+ update: PERMISSIONS.updateRole,
64
+ delete: PERMISSIONS.deleteRole,
65
+ });
74
66
 
75
67
  const {
76
68
  isLoading: isLoadingForData,
77
69
  data: { roles },
78
70
  isFetching,
71
+ refetch,
79
72
  } = useQuery('get-roles', () => fetchData(toggleNotification, notifyStatus), {
80
73
  initialData: {},
81
74
  enabled: canRead,
@@ -94,11 +87,6 @@ const RoleListPage = () => {
94
87
 
95
88
  const isLoading = isLoadingForData || isFetching;
96
89
 
97
- const handleNewRoleClick = () => {
98
- trackUsage('willCreateRole');
99
- push(`/settings/${pluginId}/roles/new`);
100
- };
101
-
102
90
  const handleShowConfirmDelete = () => {
103
91
  setShowConfirmDelete(!showConfirmDelete);
104
92
  };
@@ -121,7 +109,7 @@ const RoleListPage = () => {
121
109
 
122
110
  const deleteMutation = useMutation((id) => deleteData(id, toggleNotification), {
123
111
  async onSuccess() {
124
- await queryClient.invalidateQueries('get-roles');
112
+ await refetch();
125
113
  },
126
114
  });
127
115
 
@@ -158,12 +146,17 @@ const RoleListPage = () => {
158
146
  })}
159
147
  primaryAction={
160
148
  <CheckPermissions permissions={PERMISSIONS.createRole}>
161
- <Button onClick={handleNewRoleClick} startIcon={<Plus />} size="S">
149
+ <LinkButton
150
+ to="/settings/users-permissions/roles/new"
151
+ onClick={() => trackUsage('willCreateRole')}
152
+ startIcon={<Plus />}
153
+ size="S"
154
+ >
162
155
  {formatMessage({
163
156
  id: getTrad('List.button.roles'),
164
157
  defaultMessage: 'Add new role',
165
158
  })}
166
- </Button>
159
+ </LinkButton>
167
160
  </CheckPermissions>
168
161
  }
169
162
  />
@@ -240,4 +233,10 @@ const RoleListPage = () => {
240
233
  );
241
234
  };
242
235
 
243
- export default RoleListPage;
236
+ export const ProtectedRolesListPage = () => {
237
+ return (
238
+ <CheckPagePermissions permissions={PERMISSIONS.accessRoles}>
239
+ <RolesListPage />
240
+ </CheckPagePermissions>
241
+ );
242
+ };
@@ -1,11 +1,9 @@
1
1
  import { getFetchClient } from '@strapi/helper-plugin';
2
2
 
3
- import { getRequestURL } from '../../../../utils';
4
-
5
3
  export const fetchData = async (toggleNotification, notifyStatus) => {
6
4
  try {
7
5
  const { get } = getFetchClient();
8
- const { data } = await get(getRequestURL('roles'));
6
+ const { data } = await get('/users-permissions/roles');
9
7
  notifyStatus('The roles have loaded successfully');
10
8
 
11
9
  return data;
@@ -22,7 +20,7 @@ export const fetchData = async (toggleNotification, notifyStatus) => {
22
20
  export const deleteData = async (id, toggleNotification) => {
23
21
  try {
24
22
  const { del } = getFetchClient();
25
- await del(`${getRequestURL('roles')}/${id}`);
23
+ await del(`/users-permissions/roles/${id}`);
26
24
  } catch (error) {
27
25
  toggleNotification({
28
26
  type: 'warning',
@@ -1,82 +1,82 @@
1
1
  {
2
- "BoundRoute.title": "绑定路由到",
3
- "EditForm.inputSelect.description.role": "新验证身份的用户将被赋予所选角色。",
4
- "EditForm.inputSelect.label.role": "认证用户的默认角色",
5
- "EditForm.inputToggle.description.email": "不允许用户使用不同的认证提供者(绑定的相同的电子邮件地址)来创建多个帐户。",
6
- "EditForm.inputToggle.description.email-confirmation": "启用(ON)后,新注册的用户会收到一封确认电子邮件。",
7
- "EditForm.inputToggle.description.email-confirmation-redirection": "确认您的电子邮件后,选择将您重定向到的位置。",
8
- "EditForm.inputToggle.description.email-reset-password": "应用程序的重置密码页面的 URL",
9
- "EditForm.inputToggle.description.sign-up": "当禁用(OFF)时,注册过程将被禁止。任何人无论使用任何的供应商都不可以订阅。",
10
- "EditForm.inputToggle.label.email": "每个电子邮件地址一个帐户",
11
- "EditForm.inputToggle.label.email-confirmation": "启用电子邮件确认",
12
- "EditForm.inputToggle.label.email-confirmation-redirection": "重定向 URL",
13
- "EditForm.inputToggle.label.email-reset-password": "重置密码页面 URL",
14
- "EditForm.inputToggle.label.sign-up": "启用注册",
15
- "EditForm.inputToggle.placeholder.email-confirmation-redirection": "例如: https://yourfrontend.com/reset-password",
16
- "EditForm.inputToggle.placeholder.email-reset-password": "例如: https://yourfrontend.com/reset-password",
17
- "EditPage.form.roles": "角色详情",
18
- "Email.template.data.loaded": "电子邮件模板已加载",
19
- "Email.template.email_confirmation": "邮箱地址确认",
20
- "Email.template.form.edit.label": "编辑模板",
21
- "Email.template.table.action.label": "操作",
22
- "Email.template.table.icon.label": "图标",
23
- "Email.template.table.name.label": "名称",
24
- "Form.advancedSettings.data.loaded": "高级设置数据已加载",
25
- "HeaderNav.link.advancedSettings": "高级设置",
26
- "HeaderNav.link.emailTemplates": "电子邮件模板",
27
- "HeaderNav.link.providers": "提供者",
28
- "Plugin.permissions.plugins.description": "定义 {name} 插件所有允许的操作。",
29
- "Plugins.header.description": "下面只列出路由绑定的操作。",
30
- "Plugins.header.title": "权限",
31
- "Policies.header.hint": "选择应用程序或插件的操作,然后点击 COG 图标显示绑定的路由",
32
- "Policies.header.title": "高级设置",
33
- "PopUpForm.Email.email_templates.inputDescription": "如果你不确定如何使用变量, {link}",
34
- "PopUpForm.Email.link.documentation": "查看我们的文档",
35
- "PopUpForm.Email.options.from.email.label": "发件人地址",
36
- "PopUpForm.Email.options.from.email.placeholder": "kai@doe.com",
37
- "PopUpForm.Email.options.from.name.label": "发件人名称",
38
- "PopUpForm.Email.options.from.name.placeholder": "Kai Doe",
39
- "PopUpForm.Email.options.message.label": "消息",
40
- "PopUpForm.Email.options.object.label": "主题",
41
- "PopUpForm.Email.options.object.placeholder": "请为%APP_NAME%确认邮箱地址",
42
- "PopUpForm.Email.options.response_email.label": "回复邮件",
43
- "PopUpForm.Email.options.response_email.placeholder": "kai@doe.com",
44
- "PopUpForm.Providers.enabled.description": "如果禁用,用户将无法使用此供应商。",
45
- "PopUpForm.Providers.enabled.label": "启用",
46
- "PopUpForm.Providers.key.label": "客户端 ID",
47
- "PopUpForm.Providers.key.placeholder": "文本",
48
- "PopUpForm.Providers.redirectURL.front-end.label": "重定向 URL",
49
- "PopUpForm.Providers.redirectURL.label": "添加到{provider}应用配置的跳转URL",
50
- "PopUpForm.Providers.secret.label": "客户端秘钥",
51
- "PopUpForm.Providers.secret.placeholder": "文本",
52
- "PopUpForm.Providers.subdomain.label": "主机URI(子域名)",
53
- "PopUpForm.Providers.subdomain.placeholder": "my.subdomain.com",
54
- "PopUpForm.header.edit.email-templates": "编辑电子邮件模版",
55
- "PopUpForm.header.edit.providers": "编辑提供商",
56
- "Providers.data.loaded": "提供商已加载",
57
- "Providers.status": "状态",
58
- "Roles.empty": "您还没有任何角色。",
59
- "Roles.empty.search": "没有与搜索相匹配的角色。",
60
- "Settings.roles.deleted": "角色已被删除",
61
- "Settings.roles.edited": "角色编辑完成",
62
- "Settings.section-label": "用户及权限插件",
63
- "components.Input.error.validation.email": "这是一个无效的电子邮件",
64
- "components.Input.error.validation.json": "这不符合JSON格式",
65
- "components.Input.error.validation.max": "值过高。",
66
- "components.Input.error.validation.maxLength": "值过长。",
67
- "components.Input.error.validation.min": "值太低。",
68
- "components.Input.error.validation.minLength": "值太短。",
69
- "components.Input.error.validation.minSupMax": "不能超过上限",
70
- "components.Input.error.validation.regex": "该值不符合正则表达式。",
71
- "components.Input.error.validation.required": "该值为必填项。",
72
- "components.Input.error.validation.unique": "该值已被使用。",
73
- "notification.success.submit": "设置已被更新",
74
- "page.title": "设置 - 角色",
75
- "plugin.description.long": "使用基于 JWT 的完整身份验证过程来保护 API。这个插件还有一个 ACL 策略,允许你管理用户组之间的权限。",
76
- "plugin.description.short": "使用基于 JWT 的完整身份验证过程保护 API",
77
- "plugin.name": "角色及权限",
78
- "popUpWarning.button.cancel": "取消",
79
- "popUpWarning.button.confirm": "确认",
80
- "popUpWarning.title": "请确认",
81
- "popUpWarning.warning.cancel": "你确定你要取消你的修改?"
2
+ "BoundRoute.title": "绑定路由到",
3
+ "EditForm.inputSelect.description.role": "新验证身份的用户将被赋予所选角色。",
4
+ "EditForm.inputSelect.label.role": "认证用户的默认角色",
5
+ "EditForm.inputToggle.description.email": "不允许用户使用不同的认证提供商但相同的电子邮件地址来创建多个账户。",
6
+ "EditForm.inputToggle.description.email-confirmation": "启用(开)后,新注册的用户会收到一封确认电子邮件。",
7
+ "EditForm.inputToggle.description.email-confirmation-redirection": "确认您的电子邮件后,选择将您重定向到的位置。",
8
+ "EditForm.inputToggle.description.email-reset-password": "应用程序的重置密码页面的网址",
9
+ "EditForm.inputToggle.description.sign-up": "当禁用(关)时,注册过程将被禁止。任何人无论使用任何的供应商都不可以订阅。",
10
+ "EditForm.inputToggle.label.email": "每个电子邮件地址对应一个账户",
11
+ "EditForm.inputToggle.label.email-confirmation": "启用电子邮件确认",
12
+ "EditForm.inputToggle.label.email-confirmation-redirection": "重定向网址",
13
+ "EditForm.inputToggle.label.email-reset-password": "重置密码页面网址",
14
+ "EditForm.inputToggle.label.sign-up": "启用注册",
15
+ "EditForm.inputToggle.placeholder.email-confirmation-redirection": "例如: https://yourfrontend.com/email-confirmation-redirection",
16
+ "EditForm.inputToggle.placeholder.email-reset-password": "例如: https://yourfrontend.com/reset-password",
17
+ "EditPage.form.roles": "角色详情",
18
+ "Email.template.data.loaded": "电子邮件模板已加载",
19
+ "Email.template.email_confirmation": "邮箱地址确认",
20
+ "Email.template.form.edit.label": "编辑模板",
21
+ "Email.template.table.action.label": "操作",
22
+ "Email.template.table.icon.label": "图标",
23
+ "Email.template.table.name.label": "名称",
24
+ "Form.advancedSettings.data.loaded": "高级设置数据已加载",
25
+ "HeaderNav.link.advancedSettings": "高级设置",
26
+ "HeaderNav.link.emailTemplates": "电子邮件模板",
27
+ "HeaderNav.link.providers": "提供商",
28
+ "Plugin.permissions.plugins.description": "定义 {name} 插件所有允许的操作。",
29
+ "Plugins.header.description": "下面只列出路由绑定的操作。",
30
+ "Plugins.header.title": "权限",
31
+ "Policies.header.hint": "选择应用程序或插件对应的操作,然后点击齿轮图标显示绑定的路由",
32
+ "Policies.header.title": "高级设置",
33
+ "PopUpForm.Email.email_templates.inputDescription": "如果你不确定如何使用变量, {link}",
34
+ "PopUpForm.Email.link.documentation": "查看我们的文档",
35
+ "PopUpForm.Email.options.from.email.label": "发件人地址",
36
+ "PopUpForm.Email.options.from.email.placeholder": "kai@doe.com",
37
+ "PopUpForm.Email.options.from.name.label": "发件人名称",
38
+ "PopUpForm.Email.options.from.name.placeholder": "Kai Doe",
39
+ "PopUpForm.Email.options.message.label": "消息",
40
+ "PopUpForm.Email.options.object.label": "主题",
41
+ "PopUpForm.Email.options.object.placeholder": "请为%APP_NAME%确认邮箱地址",
42
+ "PopUpForm.Email.options.response_email.label": "回复邮件",
43
+ "PopUpForm.Email.options.response_email.placeholder": "kai@doe.com",
44
+ "PopUpForm.Providers.enabled.description": "如果禁用,用户将无法使用此供应商。",
45
+ "PopUpForm.Providers.enabled.label": "启用",
46
+ "PopUpForm.Providers.key.label": "客户端 ID",
47
+ "PopUpForm.Providers.key.placeholder": "文本",
48
+ "PopUpForm.Providers.redirectURL.front-end.label": "重定向网址",
49
+ "PopUpForm.Providers.redirectURL.label": "添加到{provider}应用配置的跳转网址",
50
+ "PopUpForm.Providers.secret.label": "客户端秘钥",
51
+ "PopUpForm.Providers.secret.placeholder": "文本",
52
+ "PopUpForm.Providers.subdomain.label": "主机URI(子域名)",
53
+ "PopUpForm.Providers.subdomain.placeholder": "my.subdomain.com",
54
+ "PopUpForm.header.edit.email-templates": "编辑电子邮件模版",
55
+ "PopUpForm.header.edit.providers": "编辑提供商",
56
+ "Providers.data.loaded": "提供商已加载",
57
+ "Providers.status": "状态",
58
+ "Roles.empty": "您还没有任何角色。",
59
+ "Roles.empty.search": "没有与搜索相匹配的角色。",
60
+ "Settings.roles.deleted": "角色已被删除",
61
+ "Settings.roles.edited": "角色编辑完成",
62
+ "Settings.section-label": "用户及权限插件",
63
+ "components.Input.error.validation.email": "这是一个无效的电子邮件",
64
+ "components.Input.error.validation.json": "这不符合JSON格式",
65
+ "components.Input.error.validation.max": "值过高。",
66
+ "components.Input.error.validation.maxLength": "值过长。",
67
+ "components.Input.error.validation.min": "值太低。",
68
+ "components.Input.error.validation.minLength": "值太短。",
69
+ "components.Input.error.validation.minSupMax": "不能超过上限",
70
+ "components.Input.error.validation.regex": "该值不符合正则表达式。",
71
+ "components.Input.error.validation.required": "该值为必填项。",
72
+ "components.Input.error.validation.unique": "该值已被使用。",
73
+ "notification.success.submit": "设置已被更新",
74
+ "page.title": "设置 - 角色",
75
+ "plugin.description.long": "使用基于 JWT 的完整身份验证过程来保护 API。这个插件还有一个 ACL 策略,允许你管理用户组之间的权限。",
76
+ "plugin.description.short": "使用基于 JWT 的完整身份验证过程保护 API",
77
+ "plugin.name": "用户及权限插件",
78
+ "popUpWarning.button.cancel": "取消",
79
+ "popUpWarning.button.confirm": "确认",
80
+ "popUpWarning.title": "请确认",
81
+ "popUpWarning.warning.cancel": "你确定你要取消你的修改?"
82
82
  }
@@ -1,4 +1,3 @@
1
1
  export { default as cleanPermissions } from './cleanPermissions';
2
2
  export { default as formatPolicies } from './formatPolicies';
3
- export { default as getRequestURL } from './getRequestURL';
4
3
  export { default as getTrad } from './getTrad';
@@ -93,7 +93,7 @@ paths:
93
93
  required: true
94
94
  responses:
95
95
  200:
96
- description: Successfull registration
96
+ description: Successful registration
97
97
  content:
98
98
  application/json:
99
99
  schema:
@@ -3,5 +3,5 @@
3
3
  module.exports = {
4
4
  preset: '../../../jest-preset.front.js',
5
5
  displayName: 'Users & Permissions plugin',
6
- setupFilesAfterEnv: ['./tests/setup.js'],
6
+ setupFilesAfterEnv: ['./admin/src/tests/setup.js'],
7
7
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@strapi/plugin-users-permissions",
3
- "version": "0.0.0-next.c593a2735b35c69d2c421003b6f80adf72fcf16e",
3
+ "version": "0.0.0-next.ce51df0e18404afc8a1aa7f504c1006a7a221459",
4
4
  "description": "Protect your API with a full-authentication process based on JWT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -29,26 +29,26 @@
29
29
  "lint": "run -T eslint ."
30
30
  },
31
31
  "dependencies": {
32
- "@strapi/design-system": "1.8.0",
33
- "@strapi/helper-plugin": "0.0.0-next.c593a2735b35c69d2c421003b6f80adf72fcf16e",
34
- "@strapi/icons": "1.8.0",
35
- "@strapi/utils": "0.0.0-next.c593a2735b35c69d2c421003b6f80adf72fcf16e",
32
+ "@strapi/design-system": "1.9.0",
33
+ "@strapi/helper-plugin": "0.0.0-next.ce51df0e18404afc8a1aa7f504c1006a7a221459",
34
+ "@strapi/icons": "1.9.0",
35
+ "@strapi/utils": "0.0.0-next.ce51df0e18404afc8a1aa7f504c1006a7a221459",
36
36
  "bcryptjs": "2.4.3",
37
37
  "formik": "2.4.0",
38
38
  "grant-koa": "5.4.8",
39
39
  "immer": "9.0.19",
40
40
  "jsonwebtoken": "9.0.0",
41
41
  "jwk-to-pem": "2.0.5",
42
- "koa": "^2.13.4",
42
+ "koa": "2.13.4",
43
43
  "koa2-ratelimit": "^1.1.2",
44
44
  "lodash": "4.17.21",
45
45
  "prop-types": "^15.8.1",
46
46
  "purest": "4.0.2",
47
47
  "react-intl": "6.4.1",
48
48
  "react-query": "3.39.3",
49
- "react-redux": "8.0.5",
49
+ "react-redux": "8.1.1",
50
50
  "url-join": "4.0.1",
51
- "yup": "^0.32.9"
51
+ "yup": "0.32.9"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@testing-library/dom": "9.2.0",
@@ -67,7 +67,7 @@
67
67
  "styled-components": "5.3.3"
68
68
  },
69
69
  "engines": {
70
- "node": ">=14.19.1 <=18.x.x",
70
+ "node": ">=16.0.0 <=20.x.x",
71
71
  "npm": ">=6.0.0"
72
72
  },
73
73
  "strapi": {
@@ -77,5 +77,5 @@
77
77
  "required": true,
78
78
  "kind": "plugin"
79
79
  },
80
- "gitHead": "c593a2735b35c69d2c421003b6f80adf72fcf16e"
80
+ "gitHead": "ce51df0e18404afc8a1aa7f504c1006a7a221459"
81
81
  }
@@ -10,10 +10,12 @@
10
10
  const crypto = require('crypto');
11
11
  const _ = require('lodash');
12
12
  const urljoin = require('url-join');
13
+ const { isArray } = require('lodash/fp');
13
14
  const { getService } = require('../utils');
14
15
  const getGrantConfig = require('./grant-config');
15
16
 
16
17
  const usersPermissionsActions = require('./users-permissions-actions');
18
+ const userSchema = require('../content-types/user');
17
19
 
18
20
  const initGrant = async (pluginStore) => {
19
21
  const apiPrefix = strapi.config.get('api.rest.prefix');
@@ -97,6 +99,26 @@ const initAdvancedOptions = async (pluginStore) => {
97
99
  }
98
100
  };
99
101
 
102
+ const userSchemaAdditions = () => {
103
+ const defaultSchema = Object.keys(userSchema.attributes);
104
+ const currentSchema = Object.keys(
105
+ strapi.contentTypes['plugin::users-permissions.user'].attributes
106
+ );
107
+
108
+ // Some dynamic fields may not have been initialized yet, so we need to ignore them
109
+ // TODO: we should have a global method for finding these
110
+ const ignoreDiffs = [
111
+ 'createdBy',
112
+ 'createdAt',
113
+ 'updatedBy',
114
+ 'updatedAt',
115
+ 'publishedAt',
116
+ 'strapi_reviewWorkflows_stage',
117
+ ];
118
+
119
+ return currentSchema.filter((key) => !(ignoreDiffs.includes(key) || defaultSchema.includes(key)));
120
+ };
121
+
100
122
  module.exports = async ({ strapi }) => {
101
123
  const pluginStore = strapi.store({ type: 'plugin', name: 'users-permissions' });
102
124
 
@@ -130,4 +152,17 @@ For security reasons, prefer storing the secret in an environment variable and r
130
152
  );
131
153
  }
132
154
  }
155
+
156
+ // TODO v5: Remove this block of code and default allowedFields to empty array
157
+ if (!isArray(strapi.config.get('plugin.users-permissions.register.allowedFields'))) {
158
+ const modifications = userSchemaAdditions();
159
+ if (modifications.length > 0) {
160
+ // if there is a potential vulnerability, show a warning
161
+ strapi.log.warn(
162
+ `Users-permissions registration has defaulted to accepting the following additional user fields during registration: ${modifications.join(
163
+ ','
164
+ )}`
165
+ );
166
+ }
167
+ }
133
168
  };
@@ -9,7 +9,11 @@
9
9
  /* eslint-disable no-useless-escape */
10
10
  const crypto = require('crypto');
11
11
  const _ = require('lodash');
12
+ const { concat, compact, isArray } = require('lodash/fp');
12
13
  const utils = require('@strapi/utils');
14
+ const {
15
+ contentTypes: { getNonWritableAttributes },
16
+ } = require('@strapi/utils');
13
17
  const { getService } = require('../utils');
14
18
  const {
15
19
  validateCallbackBody,
@@ -273,20 +277,49 @@ module.exports = {
273
277
  throw new ApplicationError('Register action is currently disabled');
274
278
  }
275
279
 
280
+ const { register } = strapi.config.get('plugin.users-permissions');
281
+ const alwaysAllowedKeys = ['username', 'password', 'email'];
282
+ const userModel = strapi.contentTypes['plugin::users-permissions.user'];
283
+ const { attributes } = userModel;
284
+
285
+ const nonWritable = getNonWritableAttributes(userModel);
286
+
287
+ const allowedKeys = compact(
288
+ concat(
289
+ alwaysAllowedKeys,
290
+ isArray(register?.allowedFields)
291
+ ? // Note that we do not filter allowedFields in case a user explicitly chooses to allow a private or otherwise omitted field on registration
292
+ register.allowedFields // if null or undefined, compact will remove it
293
+ : // to prevent breaking changes, if allowedFields is not set in config, we only remove private and known dangerous user schema fields
294
+ // TODO V5: allowedFields defaults to [] when undefined and remove this case
295
+ Object.keys(attributes).filter(
296
+ (key) =>
297
+ !nonWritable.includes(key) &&
298
+ !attributes[key].private &&
299
+ ![
300
+ // many of these are included in nonWritable, but we'll list them again to be safe and since we're removing this code in v5 anyway
301
+ // Strapi user schema fields
302
+ 'confirmed',
303
+ 'blocked',
304
+ 'confirmationToken',
305
+ 'resetPasswordToken',
306
+ 'provider',
307
+ 'id',
308
+ 'role',
309
+ // other Strapi fields that might be added
310
+ 'createdAt',
311
+ 'updatedAt',
312
+ 'createdBy',
313
+ 'updatedBy',
314
+ 'publishedAt', // d&p
315
+ 'strapi_reviewWorkflows_stage', // review workflows
316
+ ].includes(key)
317
+ )
318
+ )
319
+ );
320
+
276
321
  const params = {
277
- ..._.omit(ctx.request.body, [
278
- 'confirmed',
279
- 'blocked',
280
- 'confirmationToken',
281
- 'resetPasswordToken',
282
- 'provider',
283
- 'id',
284
- 'createdAt',
285
- 'updatedAt',
286
- 'createdBy',
287
- 'updatedBy',
288
- 'role',
289
- ]),
322
+ ..._.pick(ctx.request.body, allowedKeys),
290
323
  provider: 'local',
291
324
  };
292
325
 
@@ -11,7 +11,7 @@ const utils = require('@strapi/utils');
11
11
  const { getService } = require('../utils');
12
12
  const { validateCreateUserBody, validateUpdateUserBody } = require('./validation/user');
13
13
 
14
- const { sanitize } = utils;
14
+ const { sanitize, validate } = utils;
15
15
  const { ApplicationError, ValidationError, NotFoundError } = utils.errors;
16
16
 
17
17
  const sanitizeOutput = async (user, ctx) => {
@@ -21,6 +21,13 @@ const sanitizeOutput = async (user, ctx) => {
21
21
  return sanitize.contentAPI.output(user, schema, { auth });
22
22
  };
23
23
 
24
+ const validateQuery = async (query, ctx) => {
25
+ const schema = strapi.getModel('plugin::users-permissions.user');
26
+ const { auth } = ctx.state;
27
+
28
+ return validate.contentAPI.query(query, schema, { auth });
29
+ };
30
+
24
31
  const sanitizeQuery = async (query, ctx) => {
25
32
  const schema = strapi.getModel('plugin::users-permissions.user');
26
33
  const { auth } = ctx.state;
@@ -143,6 +150,7 @@ module.exports = {
143
150
  * @return {Object|Array}
144
151
  */
145
152
  async find(ctx) {
153
+ await validateQuery(ctx.query, ctx);
146
154
  const sanitizedQuery = await sanitizeQuery(ctx.query, ctx);
147
155
  const users = await getService('user').fetchAll(sanitizedQuery);
148
156
 
@@ -155,6 +163,7 @@ module.exports = {
155
163
  */
156
164
  async findOne(ctx) {
157
165
  const { id } = ctx.params;
166
+ await validateQuery(ctx.query, ctx);
158
167
  const sanitizedQuery = await sanitizeQuery(ctx.query, ctx);
159
168
 
160
169
  let data = await getService('user').fetch(id, sanitizedQuery);
@@ -171,6 +180,7 @@ module.exports = {
171
180
  * @return {Number}
172
181
  */
173
182
  async count(ctx) {
183
+ await validateQuery(ctx.query, ctx);
174
184
  const sanitizedQuery = await sanitizeQuery(ctx.query, ctx);
175
185
 
176
186
  ctx.body = await getService('user').count(sanitizedQuery);
@@ -201,6 +211,7 @@ module.exports = {
201
211
  return ctx.unauthorized();
202
212
  }
203
213
 
214
+ await validateQuery(query, ctx);
204
215
  const sanitizedQuery = await sanitizeQuery(query, ctx);
205
216
  const user = await getService('user').fetch(authUser.id, sanitizedQuery);
206
217