@strapi/utils 4.0.0-next.9 → 4.0.3
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/lib/build-query.js +1 -1
- package/lib/config.js +6 -3
- package/lib/content-types.js +21 -9
- package/lib/convert-query-params.js +48 -29
- package/lib/errors.js +82 -0
- package/lib/format-yup-error.js +20 -0
- package/lib/hooks.js +5 -3
- package/lib/index.js +14 -7
- package/lib/pagination.js +100 -0
- package/lib/parse-multipart.js +3 -3
- package/lib/pipe-async.js +11 -0
- package/lib/policy.js +66 -51
- package/lib/print-value.js +51 -0
- package/lib/sanitize/index.js +51 -0
- package/lib/sanitize/sanitizers.js +26 -0
- package/lib/sanitize/visitors/allowed-fields.js +92 -0
- package/lib/sanitize/visitors/index.js +9 -0
- package/lib/sanitize/visitors/remove-password.js +7 -0
- package/lib/sanitize/visitors/remove-private.js +11 -0
- package/lib/sanitize/visitors/remove-restricted-relations.js +67 -0
- package/lib/sanitize/visitors/restricted-fields.js +31 -0
- package/lib/traverse-entity.js +100 -0
- package/lib/validators.js +45 -16
- package/package.json +25 -25
- package/lib/sanitize-entity.js +0 -172
package/lib/validators.js
CHANGED
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
const yup = require('yup');
|
|
4
4
|
const _ = require('lodash');
|
|
5
|
+
const { defaults } = require('lodash/fp');
|
|
5
6
|
const utils = require('./string-formatting');
|
|
7
|
+
const { YupValidationError } = require('./errors');
|
|
8
|
+
const printValue = require('./print-value');
|
|
6
9
|
|
|
7
10
|
const MixedSchemaType = yup.mixed;
|
|
8
11
|
|
|
@@ -56,27 +59,53 @@ class StrapiIDSchema extends MixedSchemaType {
|
|
|
56
59
|
|
|
57
60
|
yup.strapiID = () => new StrapiIDSchema();
|
|
58
61
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
if (!validationError.inner) {
|
|
65
|
-
throw new Error('invalid.input');
|
|
66
|
-
}
|
|
62
|
+
const handleYupError = (error, errorMessage) => {
|
|
63
|
+
throw new YupValidationError(error, errorMessage);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const defaultValidationParam = { strict: true, abortEarly: false };
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
const validateYupSchema = (schema, options = {}) => async (body, errorMessage) => {
|
|
69
|
+
try {
|
|
70
|
+
const optionsWithDefaults = defaults(defaultValidationParam, options);
|
|
71
|
+
return await schema.validate(body, optionsWithDefaults);
|
|
72
|
+
} catch (e) {
|
|
73
|
+
handleYupError(e, errorMessage);
|
|
71
74
|
}
|
|
75
|
+
};
|
|
72
76
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
+
const validateYupSchemaSync = (schema, options = {}) => (body, errorMessage) => {
|
|
78
|
+
try {
|
|
79
|
+
const optionsWithDefaults = defaults(defaultValidationParam, options);
|
|
80
|
+
return schema.validateSync(body, optionsWithDefaults);
|
|
81
|
+
} catch (e) {
|
|
82
|
+
handleYupError(e, errorMessage);
|
|
83
|
+
}
|
|
77
84
|
};
|
|
78
85
|
|
|
86
|
+
// Temporary fix of this issue : https://github.com/jquense/yup/issues/616
|
|
87
|
+
yup.setLocale({
|
|
88
|
+
mixed: {
|
|
89
|
+
notType({ path, type, value, originalValue }) {
|
|
90
|
+
let isCast = originalValue != null && originalValue !== value;
|
|
91
|
+
let msg =
|
|
92
|
+
`${path} must be a \`${type}\` type, ` +
|
|
93
|
+
`but the final value was: \`${printValue(value, true)}\`` +
|
|
94
|
+
(isCast ? ` (cast from the value \`${printValue(originalValue, true)}\`).` : '.');
|
|
95
|
+
|
|
96
|
+
/* Remove comment that is not supposed to be seen by the enduser
|
|
97
|
+
if (value === null) {
|
|
98
|
+
msg += `\n If "null" is intended as an empty value be sure to mark the schema as \`.nullable()\``;
|
|
99
|
+
}
|
|
100
|
+
*/
|
|
101
|
+
return msg;
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
|
|
79
106
|
module.exports = {
|
|
80
107
|
yup,
|
|
81
|
-
|
|
108
|
+
handleYupError,
|
|
109
|
+
validateYupSchema,
|
|
110
|
+
validateYupSchemaSync,
|
|
82
111
|
};
|
package/package.json
CHANGED
|
@@ -1,49 +1,49 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@strapi/utils",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.3",
|
|
4
4
|
"description": "Shared utilities for the Strapi packages",
|
|
5
|
-
"homepage": "https://strapi.io",
|
|
6
5
|
"keywords": [
|
|
7
6
|
"strapi",
|
|
8
|
-
"
|
|
9
|
-
"utils",
|
|
10
|
-
"winston"
|
|
7
|
+
"utils"
|
|
11
8
|
],
|
|
12
|
-
"
|
|
13
|
-
|
|
9
|
+
"homepage": "https://strapi.io",
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/strapi/strapi/issues"
|
|
14
12
|
},
|
|
15
|
-
"
|
|
16
|
-
|
|
17
|
-
"
|
|
18
|
-
"date-fns": "^2.19.0",
|
|
19
|
-
"lodash": "4.17.21",
|
|
20
|
-
"pino": "^4.7.1",
|
|
21
|
-
"pluralize": "^8.0.0",
|
|
22
|
-
"yup": "^0.32.9"
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git://github.com/strapi/strapi.git"
|
|
23
16
|
},
|
|
17
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
24
18
|
"author": {
|
|
19
|
+
"name": "Strapi Solutions SAS",
|
|
25
20
|
"email": "hi@strapi.io",
|
|
26
|
-
"name": "Strapi team",
|
|
27
21
|
"url": "https://strapi.io"
|
|
28
22
|
},
|
|
29
23
|
"maintainers": [
|
|
30
24
|
{
|
|
31
|
-
"name": "Strapi
|
|
25
|
+
"name": "Strapi Solutions SAS",
|
|
32
26
|
"email": "hi@strapi.io",
|
|
33
27
|
"url": "https://strapi.io"
|
|
34
28
|
}
|
|
35
29
|
],
|
|
36
|
-
"
|
|
37
|
-
|
|
38
|
-
"
|
|
30
|
+
"main": "./lib",
|
|
31
|
+
"directories": {
|
|
32
|
+
"lib": "./lib"
|
|
39
33
|
},
|
|
40
|
-
"
|
|
41
|
-
"
|
|
34
|
+
"scripts": {
|
|
35
|
+
"test:unit": "jest --verbose"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@sindresorhus/slugify": "1.1.0",
|
|
39
|
+
"date-fns": "2.24.0",
|
|
40
|
+
"http-errors": "1.8.0",
|
|
41
|
+
"lodash": "4.17.21",
|
|
42
|
+
"yup": "0.32.9"
|
|
42
43
|
},
|
|
43
44
|
"engines": {
|
|
44
|
-
"node": ">=12.
|
|
45
|
+
"node": ">=12.22.0 <=16.x.x",
|
|
45
46
|
"npm": ">=6.0.0"
|
|
46
47
|
},
|
|
47
|
-
"
|
|
48
|
-
"gitHead": "231263a3535658bab1e9492c6aaaed8692d62a53"
|
|
48
|
+
"gitHead": "48893ae3fc951b618fd8c4fdc6970e623d2c92db"
|
|
49
49
|
}
|
package/lib/sanitize-entity.js
DELETED
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const _ = require('lodash');
|
|
4
|
-
const {
|
|
5
|
-
constants,
|
|
6
|
-
isPrivateAttribute,
|
|
7
|
-
getNonWritableAttributes,
|
|
8
|
-
getNonVisibleAttributes,
|
|
9
|
-
getWritableAttributes,
|
|
10
|
-
} = require('./content-types');
|
|
11
|
-
|
|
12
|
-
const { ID_ATTRIBUTE, CREATED_AT_ATTRIBUTE, UPDATED_AT_ATTRIBUTE } = constants;
|
|
13
|
-
|
|
14
|
-
const sanitizeEntity = (dataSource, options) => {
|
|
15
|
-
const { model, withPrivate = false, isOutput = true, includeFields = null } = options;
|
|
16
|
-
|
|
17
|
-
if (typeof dataSource !== 'object' || _.isNil(dataSource)) {
|
|
18
|
-
return dataSource;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const data = parseOriginalData(dataSource);
|
|
22
|
-
|
|
23
|
-
if (typeof data !== 'object' || _.isNil(data)) {
|
|
24
|
-
return data;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
if (_.isArray(data)) {
|
|
28
|
-
return data.map(entity => sanitizeEntity(entity, options));
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (_.isNil(model)) {
|
|
32
|
-
if (isOutput) {
|
|
33
|
-
return null;
|
|
34
|
-
} else {
|
|
35
|
-
return data;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const { attributes } = model;
|
|
40
|
-
const allowedFields = getAllowedFields({ includeFields, model, isOutput });
|
|
41
|
-
|
|
42
|
-
const reducerFn = (acc, value, key) => {
|
|
43
|
-
const attribute = attributes[key];
|
|
44
|
-
const allowedFieldsHasKey = allowedFields.includes(key);
|
|
45
|
-
|
|
46
|
-
if (shouldRemoveAttribute(model, key, attribute, { withPrivate, isOutput })) {
|
|
47
|
-
return acc;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Relations
|
|
51
|
-
const relation = attribute && (attribute.model || attribute.collection || attribute.component);
|
|
52
|
-
if (relation) {
|
|
53
|
-
if (_.isNil(value)) {
|
|
54
|
-
return { ...acc, [key]: value };
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const [nextFields, isAllowed] = includeFields
|
|
58
|
-
? getNextFields(allowedFields, key, { allowedFieldsHasKey })
|
|
59
|
-
: [null, true];
|
|
60
|
-
|
|
61
|
-
if (!isAllowed) {
|
|
62
|
-
return acc;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const baseOptions = {
|
|
66
|
-
withPrivate,
|
|
67
|
-
isOutput,
|
|
68
|
-
includeFields: nextFields,
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
let sanitizeFn;
|
|
72
|
-
if (relation === '*') {
|
|
73
|
-
sanitizeFn = entity => {
|
|
74
|
-
if (_.isNil(entity) || !_.has(entity, '__contentType')) {
|
|
75
|
-
return entity;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return sanitizeEntity(entity, {
|
|
79
|
-
model: strapi.getModel(entity.__contentType),
|
|
80
|
-
...baseOptions,
|
|
81
|
-
});
|
|
82
|
-
};
|
|
83
|
-
} else {
|
|
84
|
-
sanitizeFn = entity =>
|
|
85
|
-
sanitizeEntity(entity, {
|
|
86
|
-
model: strapi.getModel(relation, attribute.plugin),
|
|
87
|
-
...baseOptions,
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const nextVal = Array.isArray(value) ? value.map(sanitizeFn) : sanitizeFn(value);
|
|
92
|
-
|
|
93
|
-
return { ...acc, [key]: nextVal };
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const isAllowedField = !includeFields || allowedFieldsHasKey;
|
|
97
|
-
|
|
98
|
-
// Dynamic zones
|
|
99
|
-
if (attribute && attribute.type === 'dynamiczone' && value !== null && isAllowedField) {
|
|
100
|
-
const nextVal = value.map(elem =>
|
|
101
|
-
sanitizeEntity(elem, {
|
|
102
|
-
model: strapi.getModel(elem.__component),
|
|
103
|
-
withPrivate,
|
|
104
|
-
isOutput,
|
|
105
|
-
})
|
|
106
|
-
);
|
|
107
|
-
return { ...acc, [key]: nextVal };
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Other fields
|
|
111
|
-
if (isAllowedField) {
|
|
112
|
-
return { ...acc, [key]: value };
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return acc;
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
return _.reduce(data, reducerFn, {});
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
const parseOriginalData = data => (_.isFunction(data.toJSON) ? data.toJSON() : data);
|
|
122
|
-
|
|
123
|
-
const COMPONENT_FIELDS = ['__component'];
|
|
124
|
-
const STATIC_FIELDS = [ID_ATTRIBUTE];
|
|
125
|
-
|
|
126
|
-
const getAllowedFields = ({ includeFields, model, isOutput }) => {
|
|
127
|
-
const nonWritableAttributes = getNonWritableAttributes(model);
|
|
128
|
-
const nonVisibleAttributes = getNonVisibleAttributes(model);
|
|
129
|
-
|
|
130
|
-
const writableAttributes = getWritableAttributes(model);
|
|
131
|
-
|
|
132
|
-
const nonVisibleWritableAttributes = _.intersection(writableAttributes, nonVisibleAttributes);
|
|
133
|
-
|
|
134
|
-
return _.concat(
|
|
135
|
-
includeFields || [],
|
|
136
|
-
...(isOutput
|
|
137
|
-
? [
|
|
138
|
-
STATIC_FIELDS,
|
|
139
|
-
CREATED_AT_ATTRIBUTE,
|
|
140
|
-
UPDATED_AT_ATTRIBUTE,
|
|
141
|
-
COMPONENT_FIELDS,
|
|
142
|
-
...nonWritableAttributes,
|
|
143
|
-
...nonVisibleAttributes,
|
|
144
|
-
]
|
|
145
|
-
: [STATIC_FIELDS, COMPONENT_FIELDS, ...nonVisibleWritableAttributes])
|
|
146
|
-
);
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
const getNextFields = (fields, key, { allowedFieldsHasKey }) => {
|
|
150
|
-
const searchStr = `${key}.`;
|
|
151
|
-
|
|
152
|
-
const transformedFields = (fields || [])
|
|
153
|
-
.filter(field => field.startsWith(searchStr))
|
|
154
|
-
.map(field => field.replace(searchStr, ''));
|
|
155
|
-
|
|
156
|
-
const isAllowed = allowedFieldsHasKey || transformedFields.length > 0;
|
|
157
|
-
const nextFields = allowedFieldsHasKey ? null : transformedFields;
|
|
158
|
-
|
|
159
|
-
return [nextFields, isAllowed];
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
const shouldRemoveAttribute = (model, key, attribute = {}, { withPrivate, isOutput }) => {
|
|
163
|
-
const isPassword = attribute.type === 'password';
|
|
164
|
-
const isPrivate = isPrivateAttribute(model, key);
|
|
165
|
-
|
|
166
|
-
const shouldRemovePassword = isOutput;
|
|
167
|
-
const shouldRemovePrivate = !withPrivate && isOutput;
|
|
168
|
-
|
|
169
|
-
return !!((isPassword && shouldRemovePassword) || (isPrivate && shouldRemovePrivate));
|
|
170
|
-
};
|
|
171
|
-
|
|
172
|
-
module.exports = sanitizeEntity;
|