@strapi/strapi 4.0.0-beta.2 → 4.0.0-beta.20
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/README.md +12 -12
- package/bin/strapi.js +6 -1
- package/lib/Strapi.js +33 -21
- package/lib/commands/build.js +1 -1
- package/lib/commands/content-types/list.js +3 -5
- package/lib/commands/develop.js +8 -10
- package/lib/commands/generate-template.js +4 -5
- package/lib/commands/hooks/list.js +3 -5
- package/lib/commands/middlewares/list.js +3 -5
- package/lib/commands/new.js +3 -1
- package/lib/commands/policies/list.js +3 -5
- package/lib/commands/services/list.js +22 -0
- package/lib/commands/watchAdmin.js +4 -4
- package/lib/core/app-configuration/index.js +5 -10
- package/lib/core/bootstrap.js +2 -2
- package/lib/core/domain/module/index.js +3 -1
- package/lib/core/domain/module/validation.js +1 -4
- package/lib/core/loaders/admin.js +2 -2
- package/lib/core/loaders/apis.js +3 -1
- package/lib/core/loaders/plugins/get-enabled-plugins.js +25 -9
- package/lib/core/loaders/plugins/index.js +21 -7
- package/lib/core/loaders/src-index.js +1 -0
- package/lib/core/registries/apis.js +2 -16
- package/lib/core/registries/content-types.js +50 -6
- package/lib/core/registries/controllers.d.ts +7 -0
- package/lib/core/registries/controllers.js +74 -3
- package/lib/core/registries/hooks.d.ts +20 -0
- package/lib/core/registries/hooks.js +57 -7
- package/lib/core/registries/middlewares.d.ts +5 -0
- package/lib/core/registries/middlewares.js +61 -2
- package/lib/core/registries/policies.d.ts +9 -0
- package/lib/core/registries/policies.js +57 -6
- package/lib/core/registries/services.d.ts +7 -0
- package/lib/core/registries/services.js +67 -11
- package/lib/core-api/controller/collection-type.js +38 -11
- package/lib/core-api/controller/index.d.ts +25 -0
- package/lib/core-api/controller/index.js +30 -11
- package/lib/core-api/controller/single-type.js +26 -7
- package/lib/core-api/routes/index.js +71 -0
- package/lib/core-api/service/collection-type.js +8 -12
- package/lib/core-api/service/index.d.ts +21 -0
- package/lib/core-api/service/index.js +9 -19
- package/lib/core-api/service/pagination.js +7 -2
- package/lib/core-api/service/single-type.js +12 -11
- package/lib/factories.d.ts +48 -0
- package/lib/factories.js +84 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +5 -1
- package/lib/middlewares/body.js +33 -0
- package/lib/middlewares/compression.js +1 -1
- package/lib/middlewares/cors.js +3 -2
- package/lib/middlewares/errors.js +24 -119
- package/lib/middlewares/favicon.js +3 -3
- package/lib/middlewares/index.d.ts +2 -1
- package/lib/middlewares/index.js +4 -2
- package/lib/middlewares/ip.js +1 -1
- package/lib/middlewares/logger.js +1 -1
- package/lib/middlewares/powered-by.js +4 -2
- package/lib/middlewares/public/index.js +4 -7
- package/lib/middlewares/query.js +46 -0
- package/lib/middlewares/responses.js +2 -2
- package/lib/middlewares/security.js +29 -3
- package/lib/middlewares/session/index.js +1 -1
- package/lib/services/auth/index.js +1 -6
- package/lib/services/entity-service/attributes/index.js +31 -0
- package/lib/services/entity-service/attributes/transforms.js +20 -0
- package/lib/services/entity-service/components.js +2 -3
- package/lib/services/entity-service/index.d.ts +1 -1
- package/lib/services/entity-service/index.js +83 -27
- package/lib/services/entity-service/params.js +37 -87
- package/lib/services/entity-validator/index.js +76 -43
- package/lib/services/entity-validator/validators.js +129 -43
- package/lib/services/errors.js +77 -0
- package/lib/services/metrics/index.js +37 -35
- package/lib/services/server/compose-endpoint.js +39 -11
- package/lib/services/server/content-api.js +1 -1
- package/lib/services/server/index.js +4 -9
- package/lib/services/server/koa.js +64 -0
- package/lib/services/server/middleware.js +8 -1
- package/lib/services/server/policy.js +8 -10
- package/lib/services/server/register-middlewares.js +7 -2
- package/lib/services/server/register-routes.js +1 -3
- package/lib/services/server/routing.js +13 -0
- package/lib/utils/get-dirs.js +1 -0
- package/lib/utils/signals.js +24 -0
- package/package.json +22 -17
- package/lib/core/app-configuration/load-functions.js +0 -28
- package/lib/core-api/index.js +0 -39
- package/lib/middlewares/request.js +0 -74
|
@@ -4,72 +4,155 @@ const _ = require('lodash');
|
|
|
4
4
|
|
|
5
5
|
const { yup } = require('@strapi/utils');
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* @type {import('yup').StringSchema} StringSchema
|
|
9
|
+
* @type {import('yup').NumberSchema} NumberSchema
|
|
10
|
+
* @type {import('yup').AnySchema} AnySchema
|
|
11
|
+
*/
|
|
12
|
+
|
|
7
13
|
/**
|
|
8
14
|
* Utility function to compose validators
|
|
9
15
|
*/
|
|
10
|
-
const composeValidators = (...fns) => (
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
16
|
+
const composeValidators = (...fns) => (...args) => {
|
|
17
|
+
let validator = yup.mixed();
|
|
18
|
+
|
|
19
|
+
// if we receive a schema then use it as base schema for nested composition
|
|
20
|
+
if (yup.isSchema(args[0])) {
|
|
21
|
+
validator = args[0];
|
|
22
|
+
args = args.slice(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return fns.reduce((validator, fn) => fn(validator, ...args), validator);
|
|
14
26
|
};
|
|
15
27
|
|
|
16
28
|
/* Validator utils */
|
|
17
29
|
|
|
18
30
|
/**
|
|
19
31
|
* Adds minLength validator
|
|
20
|
-
* @param {
|
|
21
|
-
* @param {Object}
|
|
32
|
+
* @param {StringSchema} validator yup validator
|
|
33
|
+
* @param {Object} metas
|
|
34
|
+
* @param {{ minLength: Number }} metas.attr model attribute
|
|
35
|
+
* @param {Object} options
|
|
36
|
+
* @param {boolean} options.isDraft
|
|
37
|
+
*
|
|
38
|
+
* @returns {StringSchema}
|
|
22
39
|
*/
|
|
23
|
-
const addMinLengthValidator = ({
|
|
24
|
-
_.isInteger(minLength) && !isDraft ? validator.min(minLength) : validator;
|
|
40
|
+
const addMinLengthValidator = (validator, { attr }, { isDraft }) =>
|
|
41
|
+
_.isInteger(attr.minLength) && !isDraft ? validator.min(attr.minLength) : validator;
|
|
25
42
|
|
|
26
43
|
/**
|
|
27
44
|
* Adds maxLength validator
|
|
28
|
-
* @param {
|
|
29
|
-
* @param {Object}
|
|
45
|
+
* @param {StringSchema} validator yup validator
|
|
46
|
+
* @param {Object} metas
|
|
47
|
+
* @param {{ maxLength: Number }} metas.attr model attribute
|
|
48
|
+
*
|
|
49
|
+
* @returns {StringSchema}
|
|
30
50
|
*/
|
|
31
|
-
const addMaxLengthValidator = ({
|
|
32
|
-
_.isInteger(maxLength) ? validator.max(maxLength) : validator;
|
|
51
|
+
const addMaxLengthValidator = (validator, { attr }) =>
|
|
52
|
+
_.isInteger(attr.maxLength) ? validator.max(attr.maxLength) : validator;
|
|
33
53
|
|
|
34
54
|
/**
|
|
35
55
|
* Adds min integer validator
|
|
36
|
-
* @param {
|
|
37
|
-
* @param {Object}
|
|
56
|
+
* @param {NumberSchema} validator yup validator
|
|
57
|
+
* @param {Object} metas
|
|
58
|
+
* @param {{ min: Number }} metas.attr model attribute
|
|
59
|
+
*
|
|
60
|
+
* @returns {NumberSchema}
|
|
38
61
|
*/
|
|
39
|
-
const addMinIntegerValidator = ({
|
|
40
|
-
_.isNumber(min) ? validator.min(_.toInteger(min)) : validator;
|
|
62
|
+
const addMinIntegerValidator = (validator, { attr }) =>
|
|
63
|
+
_.isNumber(attr.min) ? validator.min(_.toInteger(attr.min)) : validator;
|
|
41
64
|
|
|
42
65
|
/**
|
|
43
66
|
* Adds max integer validator
|
|
44
|
-
* @param {
|
|
45
|
-
* @param {Object}
|
|
67
|
+
* @param {NumberSchema} validator yup validator
|
|
68
|
+
* @param {Object} metas
|
|
69
|
+
* @param {{ max: Number }} metas.attr model attribute
|
|
70
|
+
*
|
|
71
|
+
* @returns {NumberSchema}
|
|
46
72
|
*/
|
|
47
|
-
const addMaxIntegerValidator = ({
|
|
48
|
-
_.isNumber(max) ? validator.max(_.toInteger(max)) : validator;
|
|
73
|
+
const addMaxIntegerValidator = (validator, { attr }) =>
|
|
74
|
+
_.isNumber(attr.max) ? validator.max(_.toInteger(attr.max)) : validator;
|
|
49
75
|
|
|
50
76
|
/**
|
|
51
77
|
* Adds min float/decimal validator
|
|
52
|
-
* @param {
|
|
53
|
-
* @param {Object}
|
|
78
|
+
* @param {NumberSchema} validator yup validator
|
|
79
|
+
* @param {Object} metas
|
|
80
|
+
* @param {{ min: Number }} metas.attr model attribute
|
|
81
|
+
*
|
|
82
|
+
* @returns {NumberSchema}
|
|
54
83
|
*/
|
|
55
|
-
const addMinFloatValidator = ({
|
|
56
|
-
_.isNumber(min) ? validator.min(min) : validator;
|
|
84
|
+
const addMinFloatValidator = (validator, { attr }) =>
|
|
85
|
+
_.isNumber(attr.min) ? validator.min(attr.min) : validator;
|
|
57
86
|
|
|
58
87
|
/**
|
|
59
88
|
* Adds max float/decimal validator
|
|
60
|
-
* @param {
|
|
61
|
-
* @param {Object}
|
|
89
|
+
* @param {NumberSchema} validator yup validator
|
|
90
|
+
* @param {Object} metas model attribute
|
|
91
|
+
* @param {{ max: Number }} metas.attr
|
|
92
|
+
*
|
|
93
|
+
* @returns {NumberSchema}
|
|
62
94
|
*/
|
|
63
|
-
const addMaxFloatValidator = ({
|
|
64
|
-
_.isNumber(max) ? validator.max(max) : validator;
|
|
95
|
+
const addMaxFloatValidator = (validator, { attr }) =>
|
|
96
|
+
_.isNumber(attr.max) ? validator.max(attr.max) : validator;
|
|
65
97
|
|
|
66
98
|
/**
|
|
67
99
|
* Adds regex validator
|
|
68
|
-
* @param {
|
|
69
|
-
* @param {Object}
|
|
100
|
+
* @param {StringSchema} validator yup validator
|
|
101
|
+
* @param {Object} metas model attribute
|
|
102
|
+
* @param {{ regex: RegExp }} metas.attr
|
|
103
|
+
*
|
|
104
|
+
* @returns {StringSchema}
|
|
70
105
|
*/
|
|
71
|
-
const addStringRegexValidator = ({
|
|
72
|
-
_.isUndefined(regex) ? validator : validator.matches(new RegExp(regex));
|
|
106
|
+
const addStringRegexValidator = (validator, { attr }) =>
|
|
107
|
+
_.isUndefined(attr.regex) ? validator : validator.matches(new RegExp(attr.regex));
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
*
|
|
111
|
+
* @param {AnySchema} validator
|
|
112
|
+
* @param {Object} metas
|
|
113
|
+
* @param {{ unique: Boolean, type: String }} metas.attr
|
|
114
|
+
* @param {{ uid: String }} metas.model
|
|
115
|
+
* @param {{ name: String, value: any }} metas.updatedAttribute
|
|
116
|
+
* @param {Object} metas.entity
|
|
117
|
+
*
|
|
118
|
+
* @returns {AnySchema}
|
|
119
|
+
*/
|
|
120
|
+
const addUniqueValidator = (validator, { attr, model, updatedAttribute, entity }) => {
|
|
121
|
+
if (!attr.unique && attr.type !== 'uid') {
|
|
122
|
+
return validator;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return validator.test('unique', 'This attribute must be unique', async value => {
|
|
126
|
+
/**
|
|
127
|
+
* If the attribute value is `null` we want to skip the unique validation.
|
|
128
|
+
* Otherwise it'll only accept a single `null` entry in the database.
|
|
129
|
+
*/
|
|
130
|
+
if (updatedAttribute.value === null) {
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* If the attribute is unchanged we skip the unique verification. This will
|
|
136
|
+
* prevent the validator to be triggered in case the user activated the
|
|
137
|
+
* unique constraint after already creating multiple entries with
|
|
138
|
+
* the same attribute value for that field.
|
|
139
|
+
*/
|
|
140
|
+
if (entity && updatedAttribute.value === entity[updatedAttribute.name]) {
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
let whereParams = entity
|
|
145
|
+
? { $and: [{ [updatedAttribute.name]: value }, { $not: { id: entity.id } }] }
|
|
146
|
+
: { [updatedAttribute.name]: value };
|
|
147
|
+
|
|
148
|
+
const record = await strapi.db.query(model.uid).findOne({
|
|
149
|
+
select: ['id'],
|
|
150
|
+
where: whereParams,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
return !record;
|
|
154
|
+
});
|
|
155
|
+
};
|
|
73
156
|
|
|
74
157
|
/* Type validators */
|
|
75
158
|
|
|
@@ -77,29 +160,32 @@ const stringValidator = composeValidators(
|
|
|
77
160
|
() => yup.string().transform((val, originalVal) => originalVal),
|
|
78
161
|
addMinLengthValidator,
|
|
79
162
|
addMaxLengthValidator,
|
|
80
|
-
addStringRegexValidator
|
|
163
|
+
addStringRegexValidator,
|
|
164
|
+
addUniqueValidator
|
|
81
165
|
);
|
|
82
166
|
|
|
83
|
-
const emailValidator = composeValidators(stringValidator,
|
|
167
|
+
const emailValidator = composeValidators(stringValidator, validator => validator.email());
|
|
84
168
|
|
|
85
|
-
const uidValidator = composeValidators(stringValidator,
|
|
169
|
+
const uidValidator = composeValidators(stringValidator, validator =>
|
|
86
170
|
validator.matches(new RegExp('^[A-Za-z0-9-_.~]*$'))
|
|
87
171
|
);
|
|
88
172
|
|
|
89
|
-
const enumerationValidator = attr => {
|
|
173
|
+
const enumerationValidator = ({ attr }) => {
|
|
90
174
|
return yup.string().oneOf((Array.isArray(attr.enum) ? attr.enum : [attr.enum]).concat(null));
|
|
91
175
|
};
|
|
92
176
|
|
|
93
177
|
const integerValidator = composeValidators(
|
|
94
178
|
() => yup.number().integer(),
|
|
95
179
|
addMinIntegerValidator,
|
|
96
|
-
addMaxIntegerValidator
|
|
180
|
+
addMaxIntegerValidator,
|
|
181
|
+
addUniqueValidator
|
|
97
182
|
);
|
|
98
183
|
|
|
99
184
|
const floatValidator = composeValidators(
|
|
100
185
|
() => yup.number(),
|
|
101
186
|
addMinFloatValidator,
|
|
102
|
-
addMaxFloatValidator
|
|
187
|
+
addMaxFloatValidator,
|
|
188
|
+
addUniqueValidator
|
|
103
189
|
);
|
|
104
190
|
|
|
105
191
|
module.exports = {
|
|
@@ -113,11 +199,11 @@ module.exports = {
|
|
|
113
199
|
uid: uidValidator,
|
|
114
200
|
json: () => yup.mixed(),
|
|
115
201
|
integer: integerValidator,
|
|
116
|
-
biginteger: ()
|
|
202
|
+
biginteger: composeValidators(addUniqueValidator),
|
|
117
203
|
float: floatValidator,
|
|
118
204
|
decimal: floatValidator,
|
|
119
|
-
date: ()
|
|
120
|
-
time: ()
|
|
121
|
-
datetime: ()
|
|
122
|
-
timestamp: ()
|
|
205
|
+
date: composeValidators(addUniqueValidator),
|
|
206
|
+
time: composeValidators(addUniqueValidator),
|
|
207
|
+
datetime: composeValidators(addUniqueValidator),
|
|
208
|
+
timestamp: composeValidators(addUniqueValidator),
|
|
123
209
|
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const createError = require('http-errors');
|
|
4
|
+
const {
|
|
5
|
+
NotFoundError,
|
|
6
|
+
UnauthorizedError,
|
|
7
|
+
ForbiddenError,
|
|
8
|
+
PayloadTooLargeError,
|
|
9
|
+
} = require('@strapi/utils').errors;
|
|
10
|
+
|
|
11
|
+
const mapErrorsAndStatus = [
|
|
12
|
+
{
|
|
13
|
+
classError: UnauthorizedError,
|
|
14
|
+
status: 401,
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
classError: ForbiddenError,
|
|
18
|
+
status: 403,
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
classError: NotFoundError,
|
|
22
|
+
status: 404,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
classError: PayloadTooLargeError,
|
|
26
|
+
status: 413,
|
|
27
|
+
},
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
const formatApplicationError = error => {
|
|
31
|
+
const errorAndStatus = mapErrorsAndStatus.find(pair => error instanceof pair.classError);
|
|
32
|
+
const status = errorAndStatus ? errorAndStatus.status : 400;
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
status,
|
|
36
|
+
body: {
|
|
37
|
+
data: null,
|
|
38
|
+
error: {
|
|
39
|
+
status,
|
|
40
|
+
name: error.name,
|
|
41
|
+
message: error.message,
|
|
42
|
+
details: error.details,
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const formatHttpError = error => {
|
|
49
|
+
return {
|
|
50
|
+
status: error.status,
|
|
51
|
+
body: {
|
|
52
|
+
data: null,
|
|
53
|
+
error: {
|
|
54
|
+
status: error.status,
|
|
55
|
+
name: error.name,
|
|
56
|
+
message: error.message,
|
|
57
|
+
details: error.details,
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const formatInternalError = error => {
|
|
64
|
+
const httpError = createError(error);
|
|
65
|
+
|
|
66
|
+
if (httpError.expose) {
|
|
67
|
+
return formatHttpError(httpError);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return formatHttpError(createError(httpError.status || 500));
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
module.exports = {
|
|
74
|
+
formatApplicationError,
|
|
75
|
+
formatHttpError,
|
|
76
|
+
formatInternalError,
|
|
77
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
/**
|
|
3
3
|
* Strapi telemetry package.
|
|
4
|
-
* You can learn more at https://strapi.io/
|
|
4
|
+
* You can learn more at https://docs.strapi.io/developer-docs/latest/getting-started/usage-information.html
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
const crypto = require('crypto');
|
|
@@ -30,42 +30,44 @@ const createTelemetryInstance = strapi => {
|
|
|
30
30
|
const sender = createSender(strapi);
|
|
31
31
|
const sendEvent = wrapWithRateLimit(sender, { limitedEvents: LIMITED_EVENTS });
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
33
|
+
return {
|
|
34
|
+
register() {
|
|
35
|
+
if (!isDisabled) {
|
|
36
|
+
const pingCron = scheduleJob('0 0 12 * * *', () => sendEvent('ping'));
|
|
37
|
+
crons.push(pingCron);
|
|
39
38
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
39
|
+
strapi.server.use(createMiddleware({ sendEvent }));
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
bootstrap() {
|
|
43
|
+
if (strapi.EE === true && ee.isEE === true) {
|
|
44
|
+
const pingDisabled =
|
|
45
|
+
isTruthy(process.env.STRAPI_LICENSE_PING_DISABLED) && ee.licenseInfo.type === 'gold';
|
|
46
|
+
|
|
47
|
+
const sendLicenseCheck = () => {
|
|
48
|
+
return sendEvent(
|
|
49
|
+
'didCheckLicense',
|
|
50
|
+
{
|
|
51
|
+
licenseInfo: {
|
|
52
|
+
...ee.licenseInfo,
|
|
53
|
+
projectHash: hashProject(strapi),
|
|
54
|
+
dependencyHash: hashDep(strapi),
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
headers: { 'x-strapi-project': 'enterprise' },
|
|
59
|
+
}
|
|
60
|
+
);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
if (!pingDisabled) {
|
|
64
|
+
const licenseCron = scheduleJob('0 0 0 * * 7', () => sendLicenseCheck());
|
|
65
|
+
crons.push(licenseCron);
|
|
66
|
+
|
|
67
|
+
sendLicenseCheck();
|
|
56
68
|
}
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (!pingDisabled) {
|
|
61
|
-
const licenseCron = scheduleJob('0 0 0 * * 7', () => sendLicenseCheck());
|
|
62
|
-
crons.push(licenseCron);
|
|
63
|
-
|
|
64
|
-
sendLicenseCheck();
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return {
|
|
69
|
+
}
|
|
70
|
+
},
|
|
69
71
|
destroy() {
|
|
70
72
|
// clear open handles
|
|
71
73
|
crons.forEach(cron => cron.cancel());
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { toLower, castArray, trim, prop } = require('lodash/fp');
|
|
3
|
+
const { has, toLower, castArray, trim, prop, isNil } = require('lodash/fp');
|
|
4
|
+
const { UnauthorizedError, ForbiddenError } = require('@strapi/utils').errors;
|
|
4
5
|
|
|
5
6
|
const compose = require('koa-compose');
|
|
6
7
|
const { resolveRouteMiddlewares } = require('./middleware');
|
|
@@ -31,8 +32,6 @@ const createAuthorizeMiddleware = strapi => async (ctx, next) => {
|
|
|
31
32
|
|
|
32
33
|
return next();
|
|
33
34
|
} catch (error) {
|
|
34
|
-
const { UnauthorizedError, ForbiddenError } = authService.errors;
|
|
35
|
-
|
|
36
35
|
if (error instanceof UnauthorizedError) {
|
|
37
36
|
return ctx.unauthorized();
|
|
38
37
|
}
|
|
@@ -49,6 +48,14 @@ const createAuthenticateMiddleware = strapi => async (ctx, next) => {
|
|
|
49
48
|
return strapi.container.get('auth').authenticate(ctx, next);
|
|
50
49
|
};
|
|
51
50
|
|
|
51
|
+
const returnBodyMiddleware = async (ctx, next) => {
|
|
52
|
+
const values = await next();
|
|
53
|
+
|
|
54
|
+
if (isNil(ctx.body) && !isNil(values)) {
|
|
55
|
+
ctx.body = values;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
52
59
|
module.exports = strapi => {
|
|
53
60
|
const authenticate = createAuthenticateMiddleware(strapi);
|
|
54
61
|
const authorize = createAuthorizeMiddleware(strapi);
|
|
@@ -69,39 +76,54 @@ module.exports = strapi => {
|
|
|
69
76
|
authorize,
|
|
70
77
|
...policies,
|
|
71
78
|
...middlewares,
|
|
79
|
+
returnBodyMiddleware,
|
|
72
80
|
...castArray(action),
|
|
73
81
|
]);
|
|
74
82
|
|
|
75
83
|
router[method](path, routeHandler);
|
|
76
84
|
} catch (error) {
|
|
77
|
-
|
|
85
|
+
error.message = `Error creating endpoint ${route.method} ${route.path}: ${error.message}`;
|
|
86
|
+
throw error;
|
|
78
87
|
}
|
|
79
88
|
};
|
|
80
89
|
};
|
|
81
90
|
|
|
82
91
|
const getController = (name, { pluginName, apiName }, strapi) => {
|
|
92
|
+
let ctrl;
|
|
93
|
+
|
|
83
94
|
if (pluginName) {
|
|
84
95
|
if (pluginName === 'admin') {
|
|
85
|
-
|
|
96
|
+
ctrl = strapi.controller(`admin::${name}`);
|
|
97
|
+
} else {
|
|
98
|
+
ctrl = strapi.plugin(pluginName).controller(name);
|
|
86
99
|
}
|
|
87
|
-
|
|
88
|
-
return strapi.plugin(pluginName).controller(name);
|
|
89
100
|
} else if (apiName) {
|
|
90
|
-
|
|
101
|
+
ctrl = strapi.controller(`api::${apiName}.${name}`);
|
|
91
102
|
}
|
|
92
103
|
|
|
93
|
-
|
|
104
|
+
if (!ctrl) {
|
|
105
|
+
return strapi.controller(name);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return ctrl;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const extractHandlerParts = name => {
|
|
112
|
+
const controllerName = name.slice(0, name.lastIndexOf('.'));
|
|
113
|
+
const actionName = name.slice(name.lastIndexOf('.') + 1);
|
|
114
|
+
|
|
115
|
+
return { controllerName, actionName };
|
|
94
116
|
};
|
|
95
117
|
|
|
96
118
|
const getAction = (route, strapi) => {
|
|
97
119
|
const { handler, info = {} } = route;
|
|
98
|
-
const { pluginName, apiName } = info;
|
|
120
|
+
const { pluginName, apiName, type } = info;
|
|
99
121
|
|
|
100
122
|
if (Array.isArray(handler) || typeof handler === 'function') {
|
|
101
123
|
return handler;
|
|
102
124
|
}
|
|
103
125
|
|
|
104
|
-
const
|
|
126
|
+
const { controllerName, actionName } = extractHandlerParts(trim(handler));
|
|
105
127
|
|
|
106
128
|
const controller = getController(controllerName, { pluginName, apiName }, strapi);
|
|
107
129
|
|
|
@@ -109,5 +131,11 @@ const getAction = (route, strapi) => {
|
|
|
109
131
|
throw new Error(`Handler not found "${handler}"`);
|
|
110
132
|
}
|
|
111
133
|
|
|
134
|
+
if (has(Symbol.for('__type__'), controller[actionName])) {
|
|
135
|
+
controller[actionName][Symbol.for('__type__')].push(type);
|
|
136
|
+
} else {
|
|
137
|
+
controller[actionName][Symbol.for('__type__')] = [type];
|
|
138
|
+
}
|
|
139
|
+
|
|
112
140
|
return controller[actionName].bind(controller);
|
|
113
141
|
};
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const Koa = require('koa');
|
|
4
3
|
const Router = require('@koa/router');
|
|
5
4
|
|
|
6
5
|
const { createHTTPServer } = require('./http-server');
|
|
@@ -9,6 +8,7 @@ const { createAdminAPI } = require('./admin-api');
|
|
|
9
8
|
const { createContentAPI } = require('./content-api');
|
|
10
9
|
const registerAllRoutes = require('./register-routes');
|
|
11
10
|
const registerApplicationMiddlewares = require('./register-middlewares');
|
|
11
|
+
const createKoaApp = require('./koa');
|
|
12
12
|
|
|
13
13
|
const healthCheck = async ctx => {
|
|
14
14
|
ctx.set('strapi', 'You are so French!');
|
|
@@ -28,14 +28,9 @@ const healthCheck = async ctx => {
|
|
|
28
28
|
* @returns {Server}
|
|
29
29
|
*/
|
|
30
30
|
const createServer = strapi => {
|
|
31
|
-
const app =
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const router = new Router({
|
|
36
|
-
// FIXME: this prefix can break the admin if not specified in the admin url
|
|
37
|
-
prefix: strapi.config.get('middleware.settings.router.prefix', ''),
|
|
38
|
-
});
|
|
31
|
+
const app = createKoaApp({ proxy: strapi.config.get('server.proxy') });
|
|
32
|
+
|
|
33
|
+
const router = new Router();
|
|
39
34
|
|
|
40
35
|
const routeManager = createRouteManager(strapi);
|
|
41
36
|
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { isNil, camelCase } = require('lodash/fp');
|
|
4
|
+
const Koa = require('koa');
|
|
5
|
+
const createError = require('http-errors');
|
|
6
|
+
const delegate = require('delegates');
|
|
7
|
+
var statuses = require('statuses');
|
|
8
|
+
const { formatHttpError } = require('../errors');
|
|
9
|
+
|
|
10
|
+
const addCustomMethods = app => {
|
|
11
|
+
const delegator = delegate(app.context, 'response');
|
|
12
|
+
|
|
13
|
+
/* errors */
|
|
14
|
+
statuses.codes
|
|
15
|
+
.filter(code => code >= 400 && code < 600)
|
|
16
|
+
.forEach(code => {
|
|
17
|
+
const name = statuses(code);
|
|
18
|
+
const camelCasedName = camelCase(name);
|
|
19
|
+
app.response[camelCasedName] = function(message, details = {}) {
|
|
20
|
+
const httpError = createError(code, message, { details });
|
|
21
|
+
const { status, body } = formatHttpError(httpError);
|
|
22
|
+
this.status = status;
|
|
23
|
+
this.body = body;
|
|
24
|
+
};
|
|
25
|
+
delegator.method(camelCasedName);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
/* send, created, deleted */
|
|
29
|
+
app.response.send = function(data, status = 200) {
|
|
30
|
+
this.status = status;
|
|
31
|
+
this.body = data;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
app.response.created = function(data) {
|
|
35
|
+
this.status = 201;
|
|
36
|
+
this.body = data;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
app.response.deleted = function(data) {
|
|
40
|
+
if (isNil(data)) {
|
|
41
|
+
this.status = 204;
|
|
42
|
+
} else {
|
|
43
|
+
this.status = 200;
|
|
44
|
+
this.body = data;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
delegator
|
|
49
|
+
.method('send')
|
|
50
|
+
.method('created')
|
|
51
|
+
.method('deleted');
|
|
52
|
+
|
|
53
|
+
return app;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const createKoaApp = ({ proxy }) => {
|
|
57
|
+
const app = new Koa({ proxy });
|
|
58
|
+
|
|
59
|
+
addCustomMethods(app);
|
|
60
|
+
|
|
61
|
+
return app;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
module.exports = createKoaApp;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const path = require('path');
|
|
4
|
-
const { propOr, isArray } = require('lodash/fp');
|
|
4
|
+
const { propOr, isArray, isNil } = require('lodash/fp');
|
|
5
5
|
|
|
6
6
|
const getMiddlewareConfig = propOr([], 'config.middlewares');
|
|
7
7
|
|
|
@@ -81,6 +81,13 @@ const resolveMiddlewares = (config, strapi) => {
|
|
|
81
81
|
);
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
middlewares.forEach(middleware => {
|
|
85
|
+
// NOTE: we replace null middlewares by a dumb one to avoid having to filter later on
|
|
86
|
+
if (isNil(middleware.handler)) {
|
|
87
|
+
middleware.handler = (_, next) => next();
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
84
91
|
return middlewares;
|
|
85
92
|
};
|
|
86
93
|
|
|
@@ -1,32 +1,30 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { propOr } = require('lodash/fp');
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
const { bodyPolicy } = policy;
|
|
4
|
+
const { ForbiddenError } = require('@strapi/utils').errors;
|
|
5
|
+
const { policy: policyUtils } = require('@strapi/utils');
|
|
7
6
|
|
|
8
7
|
const getPoliciesConfig = propOr([], 'config.policies');
|
|
9
8
|
|
|
10
9
|
const resolvePolicies = route => {
|
|
11
|
-
const { pluginName, apiName } = route.info || {};
|
|
12
10
|
const policiesConfig = getPoliciesConfig(route);
|
|
11
|
+
const resolvedPolicies = policyUtils.resolve(policiesConfig, route.info);
|
|
13
12
|
|
|
14
13
|
const policiesMiddleware = async (ctx, next) => {
|
|
15
|
-
const context =
|
|
14
|
+
const context = policyUtils.createPolicyContext('koa', ctx);
|
|
16
15
|
|
|
17
|
-
for (const
|
|
18
|
-
const
|
|
19
|
-
const result = await resolvedPolicy({ ctx: context, strapi });
|
|
16
|
+
for (const { handler, config } of resolvedPolicies) {
|
|
17
|
+
const result = await handler(context, config, { strapi });
|
|
20
18
|
|
|
21
19
|
if (![true, undefined].includes(result)) {
|
|
22
|
-
throw new
|
|
20
|
+
throw new ForbiddenError('Policies failed.');
|
|
23
21
|
}
|
|
24
22
|
}
|
|
25
23
|
|
|
26
24
|
await next();
|
|
27
25
|
};
|
|
28
26
|
|
|
29
|
-
return [policiesMiddleware
|
|
27
|
+
return [policiesMiddleware];
|
|
30
28
|
};
|
|
31
29
|
|
|
32
30
|
module.exports = {
|