@strapi/plugin-users-permissions 4.12.6 → 4.13.0-alpha.0
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.
- package/package.json +4 -4
- package/server/bootstrap/index.js +35 -0
- package/server/controllers/auth.js +46 -13
- package/server/controllers/user.js +12 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@strapi/plugin-users-permissions",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.13.0-alpha.0",
|
|
4
4
|
"description": "Protect your API with a full-authentication process based on JWT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@strapi/design-system": "1.9.0",
|
|
33
|
-
"@strapi/helper-plugin": "4.
|
|
33
|
+
"@strapi/helper-plugin": "4.13.0-alpha.0",
|
|
34
34
|
"@strapi/icons": "1.9.0",
|
|
35
|
-
"@strapi/utils": "4.
|
|
35
|
+
"@strapi/utils": "4.13.0-alpha.0",
|
|
36
36
|
"bcryptjs": "2.4.3",
|
|
37
37
|
"formik": "2.4.0",
|
|
38
38
|
"grant-koa": "5.4.8",
|
|
@@ -77,5 +77,5 @@
|
|
|
77
77
|
"required": true,
|
|
78
78
|
"kind": "plugin"
|
|
79
79
|
},
|
|
80
|
-
"gitHead": "
|
|
80
|
+
"gitHead": "41844c2867621a1f47dd6ae6ac83283aa54b22f8"
|
|
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
|
-
..._.
|
|
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
|
|