@strapi/plugin-users-permissions 0.0.0-next.e41415e8ff5f565ff959667d5c5ba4f20bee013c → 0.0.0-next.e9b6852d1c05518ff6e37d599321f7aa7aa0683b

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 (196) hide show
  1. package/.eslintignore +1 -2
  2. package/.eslintrc +17 -0
  3. package/LICENSE +18 -3
  4. package/admin/src/components/BoundRoute/{index.js → index.jsx} +5 -3
  5. package/admin/src/components/FormModal/Input/{index.js → index.jsx} +3 -2
  6. package/admin/src/components/FormModal/{index.js → index.jsx} +13 -10
  7. package/admin/src/components/Permissions/PermissionRow/{SubCategory.js → SubCategory.jsx} +26 -5
  8. package/admin/src/components/Permissions/PermissionRow/{index.js → index.jsx} +4 -2
  9. package/admin/src/components/Permissions/{index.js → index.jsx} +6 -5
  10. package/admin/src/components/Policies/{index.js → index.jsx} +3 -2
  11. package/admin/src/components/UsersPermissions/{index.js → index.jsx} +8 -5
  12. package/admin/src/{permissions.js → constants.js} +1 -3
  13. package/admin/src/contexts/UsersPermissionsContext/{index.js → index.jsx} +1 -0
  14. package/admin/src/index.js +19 -28
  15. package/admin/src/pages/AdvancedSettings/{index.js → index.jsx} +68 -52
  16. package/admin/src/pages/AdvancedSettings/utils/schema.js +1 -1
  17. package/admin/src/pages/EmailTemplates/components/{EmailForm.js → EmailForm.jsx} +13 -12
  18. package/admin/src/pages/EmailTemplates/components/{EmailTable.js → EmailTable.jsx} +9 -7
  19. package/admin/src/pages/EmailTemplates/{index.js → index.jsx} +77 -63
  20. package/admin/src/pages/EmailTemplates/utils/schema.js +1 -1
  21. package/admin/src/pages/Providers/{index.js → index.jsx} +91 -86
  22. package/admin/src/pages/Providers/utils/forms.js +1 -1
  23. package/admin/src/pages/Roles/{CreatePage/utils/schema.js → constants.js} +2 -4
  24. package/admin/src/pages/Roles/hooks/usePlugins.js +78 -0
  25. package/admin/src/pages/Roles/index.jsx +33 -0
  26. package/admin/src/pages/Roles/pages/CreatePage.jsx +199 -0
  27. package/admin/src/pages/Roles/pages/EditPage.jsx +220 -0
  28. package/admin/src/pages/Roles/{ListPage/components/TableBody.js → pages/ListPage/components/TableBody.jsx} +46 -15
  29. package/admin/src/pages/Roles/{ListPage/index.js → pages/ListPage/index.jsx} +48 -47
  30. package/admin/src/pages/Roles/{ListPage → pages/ListPage}/utils/api.js +3 -4
  31. package/admin/src/translations/zh-Hans.json +80 -80
  32. package/admin/src/utils/index.js +1 -2
  33. package/dist/_chunks/ar-MvD8Ghac.mjs +44 -0
  34. package/dist/_chunks/ar-MvD8Ghac.mjs.map +1 -0
  35. package/dist/_chunks/ar-t5qTFaAD.js +44 -0
  36. package/dist/_chunks/ar-t5qTFaAD.js.map +1 -0
  37. package/dist/_chunks/cs-BMuXwxA1.mjs +50 -0
  38. package/dist/_chunks/cs-BMuXwxA1.mjs.map +1 -0
  39. package/dist/_chunks/cs-I8N4u-Sd.js +50 -0
  40. package/dist/_chunks/cs-I8N4u-Sd.js.map +1 -0
  41. package/dist/_chunks/de-YTjtq89K.js +62 -0
  42. package/dist/_chunks/de-YTjtq89K.js.map +1 -0
  43. package/dist/_chunks/de-zs2qqc0W.mjs +62 -0
  44. package/dist/_chunks/de-zs2qqc0W.mjs.map +1 -0
  45. package/dist/_chunks/dk-HctVBMsG.mjs +86 -0
  46. package/dist/_chunks/dk-HctVBMsG.mjs.map +1 -0
  47. package/dist/_chunks/dk-TF-dWjzl.js +86 -0
  48. package/dist/_chunks/dk-TF-dWjzl.js.map +1 -0
  49. package/dist/_chunks/en-CE3wEy_c.mjs +86 -0
  50. package/dist/_chunks/en-CE3wEy_c.mjs.map +1 -0
  51. package/dist/_chunks/en-m608rMZx.js +86 -0
  52. package/dist/_chunks/en-m608rMZx.js.map +1 -0
  53. package/dist/_chunks/es-9381tih_.mjs +86 -0
  54. package/dist/_chunks/es-9381tih_.mjs.map +1 -0
  55. package/dist/_chunks/es-XBQsB8_9.js +86 -0
  56. package/dist/_chunks/es-XBQsB8_9.js.map +1 -0
  57. package/dist/_chunks/fr-6cz3U-IF.js +50 -0
  58. package/dist/_chunks/fr-6cz3U-IF.js.map +1 -0
  59. package/dist/_chunks/fr-CMSc77If.mjs +50 -0
  60. package/dist/_chunks/fr-CMSc77If.mjs.map +1 -0
  61. package/dist/_chunks/id-RJ934rq-.js +62 -0
  62. package/dist/_chunks/id-RJ934rq-.js.map +1 -0
  63. package/dist/_chunks/id-SDuyIkZa.mjs +62 -0
  64. package/dist/_chunks/id-SDuyIkZa.mjs.map +1 -0
  65. package/dist/_chunks/index--NjZI15Z.mjs +301 -0
  66. package/dist/_chunks/index--NjZI15Z.mjs.map +1 -0
  67. package/dist/_chunks/index-EFtzpYTe.mjs +385 -0
  68. package/dist/_chunks/index-EFtzpYTe.mjs.map +1 -0
  69. package/dist/_chunks/index-M6VUX9Xe.mjs +250 -0
  70. package/dist/_chunks/index-M6VUX9Xe.mjs.map +1 -0
  71. package/dist/_chunks/index-PIkWT4fR.js +1191 -0
  72. package/dist/_chunks/index-PIkWT4fR.js.map +1 -0
  73. package/dist/_chunks/index-RINRWbs5.mjs +615 -0
  74. package/dist/_chunks/index-RINRWbs5.mjs.map +1 -0
  75. package/dist/_chunks/index-V3CiTkmR.mjs +1159 -0
  76. package/dist/_chunks/index-V3CiTkmR.mjs.map +1 -0
  77. package/dist/_chunks/index-d2k7RBd6.js +407 -0
  78. package/dist/_chunks/index-d2k7RBd6.js.map +1 -0
  79. package/dist/_chunks/index-f7pS9YU1.js +249 -0
  80. package/dist/_chunks/index-f7pS9YU1.js.map +1 -0
  81. package/dist/_chunks/index-j8_HHqx2.js +638 -0
  82. package/dist/_chunks/index-j8_HHqx2.js.map +1 -0
  83. package/dist/_chunks/index-zhL5rDjK.js +320 -0
  84. package/dist/_chunks/index-zhL5rDjK.js.map +1 -0
  85. package/dist/_chunks/it-YhZOlM2X.js +62 -0
  86. package/dist/_chunks/it-YhZOlM2X.js.map +1 -0
  87. package/dist/_chunks/it-bvH7DgQo.mjs +62 -0
  88. package/dist/_chunks/it-bvH7DgQo.mjs.map +1 -0
  89. package/dist/_chunks/ja-o_-JPvQv.mjs +48 -0
  90. package/dist/_chunks/ja-o_-JPvQv.mjs.map +1 -0
  91. package/dist/_chunks/ja-xssHUXFv.js +48 -0
  92. package/dist/_chunks/ja-xssHUXFv.js.map +1 -0
  93. package/dist/_chunks/ko-C3mHUSJa.js +86 -0
  94. package/dist/_chunks/ko-C3mHUSJa.js.map +1 -0
  95. package/dist/_chunks/ko-XJbPSez_.mjs +86 -0
  96. package/dist/_chunks/ko-XJbPSez_.mjs.map +1 -0
  97. package/dist/_chunks/ms-II5Ea73J.mjs +49 -0
  98. package/dist/_chunks/ms-II5Ea73J.mjs.map +1 -0
  99. package/dist/_chunks/ms-d0hfg65Z.js +49 -0
  100. package/dist/_chunks/ms-d0hfg65Z.js.map +1 -0
  101. package/dist/_chunks/nl-TA7TfK_5.js +48 -0
  102. package/dist/_chunks/nl-TA7TfK_5.js.map +1 -0
  103. package/dist/_chunks/nl-vEy6TN0K.mjs +48 -0
  104. package/dist/_chunks/nl-vEy6TN0K.mjs.map +1 -0
  105. package/dist/_chunks/pl-0pUL9hdA.js +86 -0
  106. package/dist/_chunks/pl-0pUL9hdA.js.map +1 -0
  107. package/dist/_chunks/pl-2VowaFGt.mjs +86 -0
  108. package/dist/_chunks/pl-2VowaFGt.mjs.map +1 -0
  109. package/dist/_chunks/pt-BR-WNOhafR4.js +44 -0
  110. package/dist/_chunks/pt-BR-WNOhafR4.js.map +1 -0
  111. package/dist/_chunks/pt-BR-sS1Xp3Jt.mjs +44 -0
  112. package/dist/_chunks/pt-BR-sS1Xp3Jt.mjs.map +1 -0
  113. package/dist/_chunks/pt-Rf9W51IO.mjs +48 -0
  114. package/dist/_chunks/pt-Rf9W51IO.mjs.map +1 -0
  115. package/dist/_chunks/pt-guNR9Gax.js +48 -0
  116. package/dist/_chunks/pt-guNR9Gax.js.map +1 -0
  117. package/dist/_chunks/ru-X3BMXDds.js +86 -0
  118. package/dist/_chunks/ru-X3BMXDds.js.map +1 -0
  119. package/dist/_chunks/ru-qKHnd5or.mjs +86 -0
  120. package/dist/_chunks/ru-qKHnd5or.mjs.map +1 -0
  121. package/dist/_chunks/sk-NWPw1oTN.js +50 -0
  122. package/dist/_chunks/sk-NWPw1oTN.js.map +1 -0
  123. package/dist/_chunks/sk-_Ryr-eTT.mjs +50 -0
  124. package/dist/_chunks/sk-_Ryr-eTT.mjs.map +1 -0
  125. package/dist/_chunks/sv-76NnbB__.js +86 -0
  126. package/dist/_chunks/sv-76NnbB__.js.map +1 -0
  127. package/dist/_chunks/sv-BqzScFXS.mjs +86 -0
  128. package/dist/_chunks/sv-BqzScFXS.mjs.map +1 -0
  129. package/dist/_chunks/th-WsknMEpq.mjs +60 -0
  130. package/dist/_chunks/th-WsknMEpq.mjs.map +1 -0
  131. package/dist/_chunks/th-cbppX21D.js +60 -0
  132. package/dist/_chunks/th-cbppX21D.js.map +1 -0
  133. package/dist/_chunks/tr-6mm_Fmz7.js +85 -0
  134. package/dist/_chunks/tr-6mm_Fmz7.js.map +1 -0
  135. package/dist/_chunks/tr-_DB1F1GW.mjs +85 -0
  136. package/dist/_chunks/tr-_DB1F1GW.mjs.map +1 -0
  137. package/dist/_chunks/uk-sI2I1ogF.js +49 -0
  138. package/dist/_chunks/uk-sI2I1ogF.js.map +1 -0
  139. package/dist/_chunks/uk-yxMSQAwI.mjs +49 -0
  140. package/dist/_chunks/uk-yxMSQAwI.mjs.map +1 -0
  141. package/dist/_chunks/vi-A3zJxaiI.js +50 -0
  142. package/dist/_chunks/vi-A3zJxaiI.js.map +1 -0
  143. package/dist/_chunks/vi-xY0zCW3d.mjs +50 -0
  144. package/dist/_chunks/vi-xY0zCW3d.mjs.map +1 -0
  145. package/dist/_chunks/zh-72SpmFXa.js +86 -0
  146. package/dist/_chunks/zh-72SpmFXa.js.map +1 -0
  147. package/dist/_chunks/zh-Hans-ArWWtyP4.js +86 -0
  148. package/dist/_chunks/zh-Hans-ArWWtyP4.js.map +1 -0
  149. package/dist/_chunks/zh-Hans-E84cu4kP.mjs +86 -0
  150. package/dist/_chunks/zh-Hans-E84cu4kP.mjs.map +1 -0
  151. package/dist/_chunks/zh-OFeldzbX.mjs +86 -0
  152. package/dist/_chunks/zh-OFeldzbX.mjs.map +1 -0
  153. package/dist/admin/index.js +5 -0
  154. package/dist/admin/index.js.map +1 -0
  155. package/dist/admin/index.mjs +6 -0
  156. package/dist/admin/index.mjs.map +1 -0
  157. package/documentation/content-api.yaml +1 -1
  158. package/jest.config.front.js +1 -0
  159. package/package.json +37 -18
  160. package/packup.config.ts +22 -0
  161. package/server/bootstrap/grant-config.js +9 -0
  162. package/server/bootstrap/index.js +36 -0
  163. package/server/bootstrap/users-permissions-actions.js +6 -0
  164. package/server/config.js +29 -0
  165. package/server/controllers/auth.js +73 -18
  166. package/server/controllers/user.js +12 -1
  167. package/server/middlewares/rateLimit.js +41 -21
  168. package/server/services/providers-registry.js +21 -2
  169. package/server/strategies/users-permissions.js +1 -8
  170. package/.eslintrc.js +0 -14
  171. package/admin/src/hooks/index.js +0 -5
  172. package/admin/src/hooks/useFetchRole/index.js +0 -64
  173. package/admin/src/hooks/useFetchRole/reducer.js +0 -31
  174. package/admin/src/hooks/useForm/index.js +0 -67
  175. package/admin/src/hooks/useForm/reducer.js +0 -40
  176. package/admin/src/hooks/usePlugins/index.js +0 -67
  177. package/admin/src/hooks/usePlugins/init.js +0 -5
  178. package/admin/src/hooks/usePlugins/reducer.js +0 -34
  179. package/admin/src/hooks/useRolesList/index.js +0 -62
  180. package/admin/src/hooks/useRolesList/init.js +0 -5
  181. package/admin/src/hooks/useRolesList/reducer.js +0 -31
  182. package/admin/src/pages/AdvancedSettings/utils/api.js +0 -17
  183. package/admin/src/pages/EmailTemplates/utils/api.js +0 -17
  184. package/admin/src/pages/Providers/reducer.js +0 -54
  185. package/admin/src/pages/Providers/utils/api.js +0 -25
  186. package/admin/src/pages/Providers/utils/createProvidersArray.js +0 -21
  187. package/admin/src/pages/Roles/CreatePage/index.js +0 -182
  188. package/admin/src/pages/Roles/EditPage/index.js +0 -194
  189. package/admin/src/pages/Roles/EditPage/utils/schema.js +0 -9
  190. package/admin/src/pages/Roles/ProtectedCreatePage/index.js +0 -12
  191. package/admin/src/pages/Roles/ProtectedEditPage/index.js +0 -12
  192. package/admin/src/pages/Roles/ProtectedListPage/index.js +0 -15
  193. package/admin/src/pages/Roles/index.js +0 -27
  194. package/admin/src/utils/getRequestURL.js +0 -5
  195. package/strapi-admin.js +0 -3
  196. package/admin/src/components/Permissions/PermissionRow/{CheckboxWrapper.js → CheckboxWrapper.jsx} +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@strapi/plugin-users-permissions",
3
- "version": "0.0.0-next.e41415e8ff5f565ff959667d5c5ba4f20bee013c",
3
+ "version": "0.0.0-next.e9b6852d1c05518ff6e37d599321f7aa7aa0683b",
4
4
  "description": "Protect your API with a full-authentication process based on JWT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -19,56 +19,75 @@
19
19
  "url": "https://strapi.io"
20
20
  }
21
21
  ],
22
+ "exports": {
23
+ "./strapi-admin": {
24
+ "source": "./admin/src/index.js",
25
+ "import": "./dist/admin/index.mjs",
26
+ "require": "./dist/admin/index.js",
27
+ "default": "./dist/admin/index.js"
28
+ },
29
+ "./strapi-server": {
30
+ "source": "./strapi-server.js",
31
+ "require": "./strapi-server.js",
32
+ "default": "./strapi-server.js"
33
+ },
34
+ "./package.json": "./package.json"
35
+ },
22
36
  "scripts": {
23
- "test:unit": "run -T jest",
24
- "test:unit:watch": "run -T jest --watch",
37
+ "build": "pack-up build",
38
+ "clean": "run -T rimraf dist",
39
+ "lint": "run -T eslint .",
25
40
  "test:front": "run -T cross-env IS_EE=true jest --config ./jest.config.front.js",
26
- "test:front:watch": "run -T cross-env IS_EE=true jest --config ./jest.config.front.js --watchAll",
27
41
  "test:front:ce": "run -T cross-env IS_EE=false jest --config ./jest.config.front.js",
42
+ "test:front:watch": "run -T cross-env IS_EE=true jest --config ./jest.config.front.js --watchAll",
28
43
  "test:front:watch:ce": "run -T cross-env IS_EE=false jest --config ./jest.config.front.js --watchAll",
29
- "lint": "run -T eslint ."
44
+ "test:unit": "run -T jest",
45
+ "test:unit:watch": "run -T jest --watch",
46
+ "watch": "pack-up watch"
30
47
  },
31
48
  "dependencies": {
32
- "@strapi/design-system": "1.8.0",
33
- "@strapi/helper-plugin": "0.0.0-next.e41415e8ff5f565ff959667d5c5ba4f20bee013c",
34
- "@strapi/icons": "1.8.0",
35
- "@strapi/utils": "0.0.0-next.e41415e8ff5f565ff959667d5c5ba4f20bee013c",
49
+ "@strapi/design-system": "1.19.0",
50
+ "@strapi/helper-plugin": "0.0.0-next.e9b6852d1c05518ff6e37d599321f7aa7aa0683b",
51
+ "@strapi/icons": "1.19.0",
52
+ "@strapi/utils": "0.0.0-next.e9b6852d1c05518ff6e37d599321f7aa7aa0683b",
36
53
  "bcryptjs": "2.4.3",
37
54
  "formik": "2.4.0",
38
55
  "grant-koa": "5.4.8",
39
56
  "immer": "9.0.19",
40
57
  "jsonwebtoken": "9.0.0",
41
58
  "jwk-to-pem": "2.0.5",
42
- "koa": "^2.13.4",
59
+ "koa": "2.13.4",
43
60
  "koa2-ratelimit": "^1.1.2",
44
61
  "lodash": "4.17.21",
45
62
  "prop-types": "^15.8.1",
46
63
  "purest": "4.0.2",
47
64
  "react-intl": "6.4.1",
48
65
  "react-query": "3.39.3",
49
- "react-redux": "8.0.5",
66
+ "react-redux": "8.1.1",
50
67
  "url-join": "4.0.1",
51
- "yup": "^0.32.9"
68
+ "yup": "0.32.9"
52
69
  },
53
70
  "devDependencies": {
71
+ "@strapi/pack-up": "4.23.0",
72
+ "@strapi/strapi": "0.0.0-next.e9b6852d1c05518ff6e37d599321f7aa7aa0683b",
54
73
  "@testing-library/dom": "9.2.0",
55
74
  "@testing-library/react": "14.0.0",
56
75
  "@testing-library/user-event": "14.4.3",
57
- "history": "^4.9.0",
58
- "msw": "1.2.1",
76
+ "msw": "1.3.0",
59
77
  "react": "^18.2.0",
60
78
  "react-dom": "^18.2.0",
61
79
  "react-router-dom": "5.3.4",
62
80
  "styled-components": "5.3.3"
63
81
  },
64
82
  "peerDependencies": {
83
+ "@strapi/strapi": "^4.0.0",
65
84
  "react": "^17.0.0 || ^18.0.0",
66
85
  "react-dom": "^17.0.0 || ^18.0.0",
67
- "react-router-dom": "5.3.4",
68
- "styled-components": "5.3.3"
86
+ "react-router-dom": "^5.2.0",
87
+ "styled-components": "^5.2.1"
69
88
  },
70
89
  "engines": {
71
- "node": ">=14.19.1 <=18.x.x",
90
+ "node": ">=18.0.0 <=20.x.x",
72
91
  "npm": ">=6.0.0"
73
92
  },
74
93
  "strapi": {
@@ -78,5 +97,5 @@
78
97
  "required": true,
79
98
  "kind": "plugin"
80
99
  },
81
- "gitHead": "e41415e8ff5f565ff959667d5c5ba4f20bee013c"
100
+ "gitHead": "e9b6852d1c05518ff6e37d599321f7aa7aa0683b"
82
101
  }
@@ -0,0 +1,22 @@
1
+ import { Config, defineConfig } from '@strapi/pack-up';
2
+ import { transformWithEsbuild } from 'vite';
3
+
4
+ const config: Config = defineConfig({
5
+ bundles: [
6
+ {
7
+ source: './admin/src/index.js',
8
+ import: './dist/admin/index.mjs',
9
+ require: './dist/admin/index.js',
10
+ runtime: 'web',
11
+ },
12
+ ],
13
+ dist: './dist',
14
+ /**
15
+ * Because we're exporting a server & client package
16
+ * which have different runtimes we want to ignore
17
+ * what they look like in the package.json
18
+ */
19
+ exports: {},
20
+ });
21
+
22
+ export default config;
@@ -128,4 +128,13 @@ module.exports = (baseURL) => ({
128
128
  callback: `${baseURL}/patreon/callback`,
129
129
  scope: ['identity', 'identity[email]'],
130
130
  },
131
+ keycloak: {
132
+ enabled: false,
133
+ icon: '',
134
+ key: '',
135
+ secret: '',
136
+ subdomain: 'myKeycloakProvider.com/realms/myrealm',
137
+ callback: `${baseURL}/keycloak/callback`,
138
+ scope: ['openid', 'email', 'profile'],
139
+ },
131
140
  });
@@ -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,27 @@ 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_stage',
117
+ 'strapi_assignee',
118
+ ];
119
+
120
+ return currentSchema.filter((key) => !(ignoreDiffs.includes(key) || defaultSchema.includes(key)));
121
+ };
122
+
100
123
  module.exports = async ({ strapi }) => {
101
124
  const pluginStore = strapi.store({ type: 'plugin', name: 'users-permissions' });
102
125
 
@@ -130,4 +153,17 @@ For security reasons, prefer storing the secret in an environment variable and r
130
153
  );
131
154
  }
132
155
  }
156
+
157
+ // TODO v5: Remove this block of code and default allowedFields to empty array
158
+ if (!isArray(strapi.config.get('plugin.users-permissions.register.allowedFields'))) {
159
+ const modifications = userSchemaAdditions();
160
+ if (modifications.length > 0) {
161
+ // if there is a potential vulnerability, show a warning
162
+ strapi.log.warn(
163
+ `Users-permissions registration has defaulted to accepting the following additional user fields during registration: ${modifications.join(
164
+ ','
165
+ )}`
166
+ );
167
+ }
168
+ }
133
169
  };
@@ -16,6 +16,12 @@ module.exports = {
16
16
  uid: 'roles.read',
17
17
  subCategory: 'roles',
18
18
  pluginName: 'users-permissions',
19
+ aliases: [
20
+ {
21
+ actionId: 'plugin::content-manager.explorer.read',
22
+ subjects: ['plugin::users-permissions.role'],
23
+ },
24
+ ],
19
25
  },
20
26
  {
21
27
  section: 'plugins',
package/server/config.js CHANGED
@@ -18,6 +18,35 @@ module.exports = {
18
18
  },
19
19
  },
20
20
  },
21
+ callback: {
22
+ validate(callback, provider) {
23
+ let uCallback;
24
+ let uProviderCallback;
25
+
26
+ try {
27
+ uCallback = new URL(callback);
28
+ uProviderCallback = new URL(provider.callback);
29
+ } catch {
30
+ throw new Error('The callback is not a valid URL');
31
+ }
32
+
33
+ // Make sure the different origin matches
34
+ if (uCallback.origin !== uProviderCallback.origin) {
35
+ throw new Error(
36
+ `Forbidden callback provided: origins don't match. Please verify your config.`
37
+ );
38
+ }
39
+
40
+ // Make sure the different pathname matches
41
+ if (uCallback.pathname !== uProviderCallback.pathname) {
42
+ throw new Error(
43
+ `Forbidden callback provided: pathname don't match. Please verify your config.`
44
+ );
45
+ }
46
+
47
+ // NOTE: We're not checking the search parameters on purpose to allow passing different states
48
+ },
49
+ },
21
50
  }),
22
51
  validator() {},
23
52
  };
@@ -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,
@@ -22,7 +26,7 @@ const {
22
26
  } = require('./validation/auth');
23
27
 
24
28
  const { getAbsoluteAdminUrl, getAbsoluteServerUrl, sanitize } = utils;
25
- const { ApplicationError, ValidationError } = utils.errors;
29
+ const { ApplicationError, ValidationError, ForbiddenError } = utils.errors;
26
30
 
27
31
  const sanitizeUser = (user, ctx) => {
28
32
  const { auth } = ctx.state;
@@ -96,6 +100,10 @@ module.exports = {
96
100
  try {
97
101
  const user = await getService('providers').connect(provider, ctx.query);
98
102
 
103
+ if (user.blocked) {
104
+ throw new ForbiddenError('Your account has been blocked by an administrator');
105
+ }
106
+
99
107
  return ctx.send({
100
108
  jwt: getService('jwt').issue({ id: user.id }),
101
109
  user: await sanitizeUser(user, ctx),
@@ -193,10 +201,28 @@ module.exports = {
193
201
  }
194
202
 
195
203
  // Ability to pass OAuth callback dynamically
196
- grantConfig[provider].callback =
197
- _.get(ctx, 'query.callback') ||
198
- _.get(ctx, 'session.grant.dynamic.callback') ||
199
- grantConfig[provider].callback;
204
+ const queryCustomCallback = _.get(ctx, 'query.callback');
205
+ const dynamicSessionCallback = _.get(ctx, 'session.grant.dynamic.callback');
206
+
207
+ const customCallback = queryCustomCallback ?? dynamicSessionCallback;
208
+
209
+ // The custom callback is validated to make sure it's not redirecting to an unwanted actor.
210
+ if (customCallback !== undefined) {
211
+ try {
212
+ // We're extracting the callback validator from the plugin config since it can be user-customized
213
+ const { validate: validateCallback } = strapi
214
+ .plugin('users-permissions')
215
+ .config('callback');
216
+
217
+ await validateCallback(customCallback, grantConfig[provider]);
218
+
219
+ grantConfig[provider].callback = customCallback;
220
+ } catch (e) {
221
+ throw new ValidationError('Invalid callback URL provided', { callback: customCallback });
222
+ }
223
+ }
224
+
225
+ // Build a valid redirect URI for the current provider
200
226
  grantConfig[provider].redirect_uri = getService('providers').buildRedirectUri(provider);
201
227
 
202
228
  return grant(grantConfig)(ctx, next);
@@ -273,20 +299,49 @@ module.exports = {
273
299
  throw new ApplicationError('Register action is currently disabled');
274
300
  }
275
301
 
302
+ const { register } = strapi.config.get('plugin.users-permissions');
303
+ const alwaysAllowedKeys = ['username', 'password', 'email'];
304
+ const userModel = strapi.contentTypes['plugin::users-permissions.user'];
305
+ const { attributes } = userModel;
306
+
307
+ const nonWritable = getNonWritableAttributes(userModel);
308
+
309
+ const allowedKeys = compact(
310
+ concat(
311
+ alwaysAllowedKeys,
312
+ isArray(register?.allowedFields)
313
+ ? // Note that we do not filter allowedFields in case a user explicitly chooses to allow a private or otherwise omitted field on registration
314
+ register.allowedFields // if null or undefined, compact will remove it
315
+ : // to prevent breaking changes, if allowedFields is not set in config, we only remove private and known dangerous user schema fields
316
+ // TODO V5: allowedFields defaults to [] when undefined and remove this case
317
+ Object.keys(attributes).filter(
318
+ (key) =>
319
+ !nonWritable.includes(key) &&
320
+ !attributes[key].private &&
321
+ ![
322
+ // 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
323
+ // Strapi user schema fields
324
+ 'confirmed',
325
+ 'blocked',
326
+ 'confirmationToken',
327
+ 'resetPasswordToken',
328
+ 'provider',
329
+ 'id',
330
+ 'role',
331
+ // other Strapi fields that might be added
332
+ 'createdAt',
333
+ 'updatedAt',
334
+ 'createdBy',
335
+ 'updatedBy',
336
+ 'publishedAt', // d&p
337
+ 'strapi_reviewWorkflows_stage', // review workflows
338
+ ].includes(key)
339
+ )
340
+ )
341
+ );
342
+
276
343
  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
- ]),
344
+ ..._.pick(ctx.request.body, allowedKeys),
290
345
  provider: 'local',
291
346
  };
292
347
 
@@ -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
 
@@ -1,27 +1,47 @@
1
1
  'use strict';
2
2
 
3
+ const path = require('path');
4
+ const utils = require('@strapi/utils');
5
+ const { isString, has, toLower } = require('lodash/fp');
6
+
7
+ const { RateLimitError } = utils.errors;
8
+
3
9
  module.exports =
4
10
  (config, { strapi }) =>
5
11
  async (ctx, next) => {
6
- const ratelimit = require('koa2-ratelimit').RateLimit;
7
-
8
- const message = [
9
- {
10
- messages: [
11
- {
12
- id: 'Auth.form.error.ratelimit',
13
- message: 'Too many attempts, please try again in a minute.',
14
- },
15
- ],
16
- },
17
- ];
18
-
19
- return ratelimit.middleware({
20
- interval: 1 * 60 * 1000,
21
- max: 5,
22
- prefixKey: `${ctx.request.path}:${ctx.request.ip}`,
23
- message,
24
- ...strapi.config.get('plugin.users-permissions.ratelimit'),
25
- ...config,
26
- })(ctx, next);
12
+ let rateLimitConfig = strapi.config.get('plugin.users-permissions.ratelimit');
13
+
14
+ if (!rateLimitConfig) {
15
+ rateLimitConfig = {
16
+ enabled: true,
17
+ };
18
+ }
19
+
20
+ if (!has('enabled', rateLimitConfig)) {
21
+ rateLimitConfig.enabled = true;
22
+ }
23
+
24
+ if (rateLimitConfig.enabled === true) {
25
+ const rateLimit = require('koa2-ratelimit').RateLimit;
26
+
27
+ const userIdentifier = toLower(ctx.request.body.email) || 'unknownIdentifier';
28
+ const requestPath = isString(ctx.request.path)
29
+ ? toLower(path.normalize(ctx.request.path))
30
+ : 'invalidPath';
31
+
32
+ const loadConfig = {
33
+ interval: { min: 5 },
34
+ max: 5,
35
+ prefixKey: `${userIdentifier}:${requestPath}:${ctx.request.ip}`,
36
+ handler() {
37
+ throw new RateLimitError();
38
+ },
39
+ ...rateLimitConfig,
40
+ ...config,
41
+ };
42
+
43
+ return rateLimit.middleware(loadConfig)(ctx, next);
44
+ }
45
+
46
+ return next();
27
47
  };
@@ -54,8 +54,11 @@ const getInitialProviders = ({ purest }) => ({
54
54
  .auth(accessToken)
55
55
  .request()
56
56
  .then(({ body }) => {
57
- // Combine username and discriminator because discord username is not unique
58
- const username = `${body.username}#${body.discriminator}`;
57
+ // Combine username and discriminator (if discriminator exists and not equal to 0)
58
+ const username =
59
+ body.discriminator && body.discriminator !== '0'
60
+ ? `${body.username}#${body.discriminator}`
61
+ : body.username;
59
62
  return {
60
63
  username,
61
64
  email: body.email,
@@ -311,6 +314,7 @@ const getInitialProviders = ({ purest }) => ({
311
314
  origin: 'https://www.patreon.com',
312
315
  path: 'api/oauth2/{path}',
313
316
  headers: {
317
+ 'user-agent': 'strapi',
314
318
  authorization: 'Bearer {auth}',
315
319
  },
316
320
  },
@@ -331,6 +335,21 @@ const getInitialProviders = ({ purest }) => ({
331
335
  };
332
336
  });
333
337
  },
338
+ async keycloak({ accessToken, providers }) {
339
+ const keycloak = purest({ provider: 'keycloak' });
340
+
341
+ return keycloak
342
+ .subdomain(providers.keycloak.subdomain)
343
+ .get('protocol/openid-connect/userinfo')
344
+ .auth(accessToken)
345
+ .request()
346
+ .then(({ body }) => {
347
+ return {
348
+ username: body.preferred_username,
349
+ email: body.email,
350
+ };
351
+ });
352
+ },
334
353
  });
335
354
 
336
355
  module.exports = () => {
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { castArray, map, every, pipe, isEmpty } = require('lodash/fp');
3
+ const { castArray, map, every, pipe } = require('lodash/fp');
4
4
  const { ForbiddenError, UnauthorizedError } = require('@strapi/utils').errors;
5
5
 
6
6
  const { getService } = require('../utils');
@@ -80,13 +80,6 @@ const authenticate = async (ctx) => {
80
80
  const verify = async (auth, config) => {
81
81
  const { credentials: user, ability } = auth;
82
82
 
83
- strapi.telemetry.send('didReceiveAPIRequest', {
84
- eventProperties: {
85
- authenticationMethod: auth?.strategy?.name,
86
- isAuthenticated: !isEmpty(user),
87
- },
88
- });
89
-
90
83
  if (!config.scope) {
91
84
  if (!user) {
92
85
  // A non authenticated user cannot access routes that do not have a scope
package/.eslintrc.js DELETED
@@ -1,14 +0,0 @@
1
- module.exports = {
2
- root: true,
3
- overrides: [
4
- {
5
- files: ['admin/**/*'],
6
- extends: ['custom/front'],
7
- },
8
- {
9
- files: ['**/*'],
10
- excludedFiles: ['admin/**/*'],
11
- extends: ['custom/back'],
12
- },
13
- ],
14
- };
@@ -1,5 +0,0 @@
1
- // eslint-disable-next-line import/prefer-default-export
2
- export { default as useForm } from './useForm';
3
- export { default as useRolesList } from './useRolesList';
4
- export { default as usePlugins } from './usePlugins';
5
- export { default as useFetchRole } from './useFetchRole';
@@ -1,64 +0,0 @@
1
- import { useCallback, useReducer, useEffect, useRef } from 'react';
2
- import { useNotification, useFetchClient } from '@strapi/helper-plugin';
3
- import reducer, { initialState } from './reducer';
4
- import pluginId from '../../pluginId';
5
-
6
- const useFetchRole = (id) => {
7
- const [state, dispatch] = useReducer(reducer, initialState);
8
- const toggleNotification = useNotification();
9
- const isMounted = useRef(null);
10
- const { get } = useFetchClient();
11
-
12
- useEffect(() => {
13
- isMounted.current = true;
14
-
15
- if (id) {
16
- fetchRole(id);
17
- } else {
18
- dispatch({
19
- type: 'GET_DATA_SUCCEEDED',
20
- role: {},
21
- });
22
- }
23
-
24
- return () => (isMounted.current = false);
25
- // eslint-disable-next-line react-hooks/exhaustive-deps
26
- }, [id]);
27
-
28
- const fetchRole = async (roleId) => {
29
- try {
30
- const {
31
- data: { role },
32
- } = await get(`/${pluginId}/roles/${roleId}`);
33
-
34
- // Prevent updating state on an unmounted component
35
- if (isMounted.current) {
36
- dispatch({
37
- type: 'GET_DATA_SUCCEEDED',
38
- role,
39
- });
40
- }
41
- } catch (err) {
42
- console.error(err);
43
-
44
- dispatch({
45
- type: 'GET_DATA_ERROR',
46
- });
47
- toggleNotification({
48
- type: 'warning',
49
- message: { id: 'notification.error' },
50
- });
51
- }
52
- };
53
-
54
- const handleSubmitSucceeded = useCallback((data) => {
55
- dispatch({
56
- type: 'ON_SUBMIT_SUCCEEDED',
57
- ...data,
58
- });
59
- }, []);
60
-
61
- return { ...state, onSubmitSucceeded: handleSubmitSucceeded };
62
- };
63
-
64
- export default useFetchRole;