@strapi/strapi 4.4.0-beta.3 → 4.4.0-rc.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/lib/Strapi.js +12 -2
- package/lib/core/loaders/apis.js +2 -1
- package/lib/services/auth/index.js +4 -1
- package/lib/services/content-api/index.js +74 -0
- package/lib/services/content-api/permissions/engine.js +5 -0
- package/lib/services/content-api/permissions/index.js +148 -0
- package/lib/services/content-api/permissions/providers/action.js +19 -0
- package/lib/services/content-api/permissions/providers/condition.js +19 -0
- package/lib/services/content-api/permissions/providers/index.js +9 -0
- package/lib/services/request-context.js +17 -0
- package/lib/services/server/index.js +3 -0
- package/lib/types/core/strapi/index.d.ts +5 -0
- package/package.json +14 -13
package/lib/Strapi.js
CHANGED
|
@@ -21,7 +21,9 @@ const createEntityService = require('./services/entity-service');
|
|
|
21
21
|
const createCronService = require('./services/cron');
|
|
22
22
|
const entityValidator = require('./services/entity-validator');
|
|
23
23
|
const createTelemetry = require('./services/metrics');
|
|
24
|
+
const requestContext = require('./services/request-context');
|
|
24
25
|
const createAuth = require('./services/auth');
|
|
26
|
+
const createContentAPI = require('./services/content-api');
|
|
25
27
|
const createCustomFields = require('./services/custom-fields');
|
|
26
28
|
const createUpdateNotifier = require('./utils/update-notifier');
|
|
27
29
|
const createStartupLogger = require('./utils/startup-logger');
|
|
@@ -77,7 +79,7 @@ class Strapi {
|
|
|
77
79
|
// Load the app configuration from the dist directory
|
|
78
80
|
const appConfig = loadConfiguration({ appDir: rootDirs.app, distDir: rootDirs.dist }, opts);
|
|
79
81
|
|
|
80
|
-
//
|
|
82
|
+
// Instantiate the Strapi container
|
|
81
83
|
this.container = createContainer(this);
|
|
82
84
|
|
|
83
85
|
// Register every Strapi registry in the container
|
|
@@ -93,6 +95,7 @@ class Strapi {
|
|
|
93
95
|
this.container.register('custom-fields', customFieldsRegistry(this));
|
|
94
96
|
this.container.register('apis', apisRegistry(this));
|
|
95
97
|
this.container.register('auth', createAuth(this));
|
|
98
|
+
this.container.register('content-api', createContentAPI(this));
|
|
96
99
|
this.container.register('sanitizers', sanitizersRegistry(this));
|
|
97
100
|
|
|
98
101
|
// Create a mapping of every useful directory (for the app, dist and static directories)
|
|
@@ -102,7 +105,7 @@ class Strapi {
|
|
|
102
105
|
this.isLoaded = false;
|
|
103
106
|
this.reload = this.reload();
|
|
104
107
|
|
|
105
|
-
//
|
|
108
|
+
// Instantiate the Koa app & the HTTP server
|
|
106
109
|
this.server = createServer(this);
|
|
107
110
|
|
|
108
111
|
// Strapi utils instanciation
|
|
@@ -112,6 +115,7 @@ class Strapi {
|
|
|
112
115
|
this.log = createLogger(this.config.get('logger', {}));
|
|
113
116
|
this.cron = createCronService();
|
|
114
117
|
this.telemetry = createTelemetry(this);
|
|
118
|
+
this.requestContext = requestContext;
|
|
115
119
|
|
|
116
120
|
this.customFields = createCustomFields(this);
|
|
117
121
|
|
|
@@ -194,6 +198,10 @@ class Strapi {
|
|
|
194
198
|
return this.container.get('auth');
|
|
195
199
|
}
|
|
196
200
|
|
|
201
|
+
get contentAPI() {
|
|
202
|
+
return this.container.get('content-api');
|
|
203
|
+
}
|
|
204
|
+
|
|
197
205
|
get sanitizers() {
|
|
198
206
|
return this.container.get('sanitizers');
|
|
199
207
|
}
|
|
@@ -453,6 +461,8 @@ class Strapi {
|
|
|
453
461
|
await this.server.initMiddlewares();
|
|
454
462
|
await this.server.initRouting();
|
|
455
463
|
|
|
464
|
+
await this.contentAPI.permissions.registerActions();
|
|
465
|
+
|
|
456
466
|
await this.runLifecyclesFunctions(LIFECYCLES.BOOTSTRAP);
|
|
457
467
|
|
|
458
468
|
this.cron.start();
|
package/lib/core/loaders/apis.js
CHANGED
|
@@ -131,11 +131,12 @@ const loadDir = async (dir) => {
|
|
|
131
131
|
|
|
132
132
|
const root = {};
|
|
133
133
|
for (const fd of fds) {
|
|
134
|
-
if (!fd.isFile()) {
|
|
134
|
+
if (!fd.isFile() || extname(fd.name) === '.map') {
|
|
135
135
|
continue;
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
const key = basename(fd.name, extname(fd.name));
|
|
139
|
+
|
|
139
140
|
root[normalizeName(key)] = await loadFile(join(dir, fd.name));
|
|
140
141
|
}
|
|
141
142
|
|
|
@@ -32,6 +32,7 @@ const createAuthentication = () => {
|
|
|
32
32
|
|
|
33
33
|
return this;
|
|
34
34
|
},
|
|
35
|
+
|
|
35
36
|
async authenticate(ctx, next) {
|
|
36
37
|
const { route } = ctx.state;
|
|
37
38
|
|
|
@@ -47,7 +48,7 @@ const createAuthentication = () => {
|
|
|
47
48
|
for (const strategy of strategiesToUse) {
|
|
48
49
|
const result = await strategy.authenticate(ctx);
|
|
49
50
|
|
|
50
|
-
const { authenticated = false,
|
|
51
|
+
const { authenticated = false, credentials, ability = null, error = null } = result || {};
|
|
51
52
|
|
|
52
53
|
if (error !== null) {
|
|
53
54
|
return ctx.unauthorized(error);
|
|
@@ -58,6 +59,7 @@ const createAuthentication = () => {
|
|
|
58
59
|
ctx.state.auth = {
|
|
59
60
|
strategy,
|
|
60
61
|
credentials,
|
|
62
|
+
ability,
|
|
61
63
|
};
|
|
62
64
|
|
|
63
65
|
return next();
|
|
@@ -66,6 +68,7 @@ const createAuthentication = () => {
|
|
|
66
68
|
|
|
67
69
|
return ctx.unauthorized('Missing or invalid credentials');
|
|
68
70
|
},
|
|
71
|
+
|
|
69
72
|
async verify(auth, config = {}) {
|
|
70
73
|
if (config === false) {
|
|
71
74
|
return;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const _ = require('lodash');
|
|
4
|
+
const instantiatePermissionsUtilities = require('./permissions');
|
|
5
|
+
|
|
6
|
+
const transformRoutePrefixFor = (pluginName) => (route) => {
|
|
7
|
+
const prefix = route.config && route.config.prefix;
|
|
8
|
+
const path = prefix !== undefined ? `${prefix}${route.path}` : `/${pluginName}${route.path}`;
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
...route,
|
|
12
|
+
path,
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Create a content API container that holds logic, tools and utils. (eg: permissions, ...)
|
|
18
|
+
*/
|
|
19
|
+
const createContentAPI = (strapi) => {
|
|
20
|
+
const getRoutesMap = async () => {
|
|
21
|
+
const routesMap = {};
|
|
22
|
+
|
|
23
|
+
_.forEach(strapi.api, (api, apiName) => {
|
|
24
|
+
const routes = _.flatMap(api.routes, (route) => {
|
|
25
|
+
if (_.has(route, 'routes')) {
|
|
26
|
+
return route.routes;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return route;
|
|
30
|
+
}).filter((route) => route.info.type === 'content-api');
|
|
31
|
+
|
|
32
|
+
if (routes.length === 0) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const apiPrefix = strapi.config.get('api.rest.prefix');
|
|
37
|
+
routesMap[`api::${apiName}`] = routes.map((route) => ({
|
|
38
|
+
...route,
|
|
39
|
+
path: `${apiPrefix}${route.path}`,
|
|
40
|
+
}));
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
_.forEach(strapi.plugins, (plugin, pluginName) => {
|
|
44
|
+
const transformPrefix = transformRoutePrefixFor(pluginName);
|
|
45
|
+
|
|
46
|
+
const routes = _.flatMap(plugin.routes, (route) => {
|
|
47
|
+
if (_.has(route, 'routes')) {
|
|
48
|
+
return route.routes.map(transformPrefix);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return transformPrefix(route);
|
|
52
|
+
}).filter((route) => route.info.type === 'content-api');
|
|
53
|
+
|
|
54
|
+
if (routes.length === 0) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const apiPrefix = strapi.config.get('api.rest.prefix');
|
|
59
|
+
routesMap[`plugin::${pluginName}`] = routes.map((route) => ({
|
|
60
|
+
...route,
|
|
61
|
+
path: `${apiPrefix}${route.path}`,
|
|
62
|
+
}));
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
return routesMap;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
permissions: instantiatePermissionsUtilities(strapi),
|
|
70
|
+
getRoutesMap,
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
module.exports = createContentAPI;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const _ = require('lodash');
|
|
4
|
+
const { createActionProvider, createConditionProvider } = require('./providers');
|
|
5
|
+
const createPermissionEngine = require('./engine');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Creates an handler that checks if the permission's action exists in the action registry
|
|
9
|
+
*/
|
|
10
|
+
const createValidatePermissionHandler =
|
|
11
|
+
(actionProvider) =>
|
|
12
|
+
({ permission }) => {
|
|
13
|
+
const action = actionProvider.get(permission.action);
|
|
14
|
+
|
|
15
|
+
// If the action isn't registered into the action provider, then ignore the permission and warn the user
|
|
16
|
+
if (!action) {
|
|
17
|
+
strapi.log.debug(
|
|
18
|
+
`Unknown action "${permission.action}" supplied when registering a new permission`
|
|
19
|
+
);
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Create instances of providers and permission engine for the core content-API service.
|
|
26
|
+
* Also, expose utilities to get informations about available actions and such.
|
|
27
|
+
*
|
|
28
|
+
* @param {Strapi.Strapi} strapi
|
|
29
|
+
*/
|
|
30
|
+
module.exports = (strapi) => {
|
|
31
|
+
// NOTE: Here we define both an action and condition provider,
|
|
32
|
+
// but at the moment, we're only using the action one.
|
|
33
|
+
const providers = {
|
|
34
|
+
action: createActionProvider(),
|
|
35
|
+
condition: createConditionProvider(),
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Get a tree representation of the available Content API actions
|
|
40
|
+
* based on the methods of the Content API controllers.
|
|
41
|
+
*
|
|
42
|
+
* @note Only actions bound to a content-API route are returned.
|
|
43
|
+
*
|
|
44
|
+
* @return {{ [api: string]: { [controller: string]: string[] }}}
|
|
45
|
+
*/
|
|
46
|
+
const getActionsMap = () => {
|
|
47
|
+
const actionMap = {};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Check if a controller's action is bound to the
|
|
51
|
+
* content-api by looking at a potential __type__ symbol
|
|
52
|
+
*
|
|
53
|
+
* @param {object} action
|
|
54
|
+
*
|
|
55
|
+
* @return {boolean}
|
|
56
|
+
*/
|
|
57
|
+
const isContentApi = (action) => {
|
|
58
|
+
if (!_.has(action, Symbol.for('__type__'))) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return action[Symbol.for('__type__')].includes('content-api');
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Register actions from a specific API source into the result tree
|
|
67
|
+
*
|
|
68
|
+
* @param {{ [apiName]: { controllers: { [controller]: object } }}} apis The API container
|
|
69
|
+
* @param {string} source The prefix to use in front the API name
|
|
70
|
+
*
|
|
71
|
+
* @return {void}
|
|
72
|
+
*/
|
|
73
|
+
const registerAPIsActions = (apis, source) => {
|
|
74
|
+
_.forEach(apis, (api, apiName) => {
|
|
75
|
+
const controllers = _.reduce(
|
|
76
|
+
api.controllers,
|
|
77
|
+
(acc, controller, controllerName) => {
|
|
78
|
+
const contentApiActions = _.pickBy(controller, isContentApi);
|
|
79
|
+
|
|
80
|
+
if (_.isEmpty(contentApiActions)) {
|
|
81
|
+
return acc;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
acc[controllerName] = Object.keys(contentApiActions);
|
|
85
|
+
|
|
86
|
+
return acc;
|
|
87
|
+
},
|
|
88
|
+
{}
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
if (!_.isEmpty(controllers)) {
|
|
92
|
+
actionMap[`${source}::${apiName}`] = { controllers };
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
registerAPIsActions(strapi.api, 'api');
|
|
98
|
+
registerAPIsActions(strapi.plugins, 'plugin');
|
|
99
|
+
|
|
100
|
+
return actionMap;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Register all the content-API's controllers actions into the action provider.
|
|
105
|
+
* This method make use of the {@link getActionsMap} to generate the list of actions to register.
|
|
106
|
+
*
|
|
107
|
+
* @return {void}
|
|
108
|
+
*/
|
|
109
|
+
const registerActions = async () => {
|
|
110
|
+
const actionsMap = getActionsMap();
|
|
111
|
+
|
|
112
|
+
// For each API
|
|
113
|
+
for (const [api, value] of Object.entries(actionsMap)) {
|
|
114
|
+
const { controllers } = value;
|
|
115
|
+
|
|
116
|
+
// Register controllers methods as actions
|
|
117
|
+
for (const [controller, actions] of Object.entries(controllers)) {
|
|
118
|
+
// Register each action individually
|
|
119
|
+
await Promise.all(
|
|
120
|
+
actions.map((action) => {
|
|
121
|
+
const actionUID = `${api}.${controller}.${action}`;
|
|
122
|
+
|
|
123
|
+
return providers.action.register(actionUID, {
|
|
124
|
+
api,
|
|
125
|
+
controller,
|
|
126
|
+
action,
|
|
127
|
+
uid: actionUID,
|
|
128
|
+
});
|
|
129
|
+
})
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// Create an instance of a content-API permission engine
|
|
136
|
+
// and binds a custom validation handler to it
|
|
137
|
+
const engine = createPermissionEngine({ providers }).on(
|
|
138
|
+
'before-format::validate.permission',
|
|
139
|
+
createValidatePermissionHandler(providers.action)
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
engine,
|
|
144
|
+
providers,
|
|
145
|
+
registerActions,
|
|
146
|
+
getActionsMap,
|
|
147
|
+
};
|
|
148
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { providerFactory } = require('@strapi/utils');
|
|
4
|
+
|
|
5
|
+
module.exports = (options = {}) => {
|
|
6
|
+
const provider = providerFactory(options);
|
|
7
|
+
|
|
8
|
+
return {
|
|
9
|
+
...provider,
|
|
10
|
+
|
|
11
|
+
async register(action, payload) {
|
|
12
|
+
if (strapi.isLoaded) {
|
|
13
|
+
throw new Error(`You can't register new actions outside the bootstrap function.`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return provider.register(action, payload);
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { providerFactory } = require('@strapi/utils');
|
|
4
|
+
|
|
5
|
+
module.exports = (options = {}) => {
|
|
6
|
+
const provider = providerFactory(options);
|
|
7
|
+
|
|
8
|
+
return {
|
|
9
|
+
...provider,
|
|
10
|
+
|
|
11
|
+
async register(condition) {
|
|
12
|
+
if (strapi.isLoaded) {
|
|
13
|
+
throw new Error(`You can't register new conditions outside the bootstrap function.`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return provider.register(condition.name, condition);
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { AsyncLocalStorage } = require('async_hooks');
|
|
4
|
+
|
|
5
|
+
const storage = new AsyncLocalStorage();
|
|
6
|
+
|
|
7
|
+
const requestCtx = {
|
|
8
|
+
async run(store, cb) {
|
|
9
|
+
return storage.run(store, cb);
|
|
10
|
+
},
|
|
11
|
+
|
|
12
|
+
get() {
|
|
13
|
+
return storage.getStore();
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
module.exports = requestCtx;
|
|
@@ -9,6 +9,7 @@ const { createContentAPI } = require('./content-api');
|
|
|
9
9
|
const registerAllRoutes = require('./register-routes');
|
|
10
10
|
const registerApplicationMiddlewares = require('./register-middlewares');
|
|
11
11
|
const createKoaApp = require('./koa');
|
|
12
|
+
const requestCtx = require('../request-context');
|
|
12
13
|
|
|
13
14
|
const healthCheck = async (ctx) => {
|
|
14
15
|
ctx.set('strapi', 'You are so French!');
|
|
@@ -33,6 +34,8 @@ const createServer = (strapi) => {
|
|
|
33
34
|
keys: strapi.config.get('server.app.keys'),
|
|
34
35
|
});
|
|
35
36
|
|
|
37
|
+
app.use((ctx, next) => requestCtx.run(ctx, () => next()));
|
|
38
|
+
|
|
36
39
|
const router = new Router();
|
|
37
40
|
|
|
38
41
|
const routeManager = createRouteManager(strapi);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@strapi/strapi",
|
|
3
|
-
"version": "4.4.0-
|
|
3
|
+
"version": "4.4.0-rc.0",
|
|
4
4
|
"description": "An open source headless CMS solution to create and manage your own API. It provides a powerful dashboard and features to make your life easier. Databases supported: MySQL, MariaDB, PostgreSQL, SQLite",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"strapi",
|
|
@@ -80,17 +80,18 @@
|
|
|
80
80
|
"dependencies": {
|
|
81
81
|
"@koa/cors": "3.4.1",
|
|
82
82
|
"@koa/router": "10.1.1",
|
|
83
|
-
"@strapi/admin": "4.4.0-
|
|
84
|
-
"@strapi/database": "4.4.0-
|
|
85
|
-
"@strapi/generate-new": "4.4.0-
|
|
86
|
-
"@strapi/generators": "4.4.0-
|
|
87
|
-
"@strapi/logger": "4.4.0-
|
|
88
|
-
"@strapi/
|
|
89
|
-
"@strapi/plugin-content-
|
|
90
|
-
"@strapi/plugin-
|
|
91
|
-
"@strapi/plugin-
|
|
92
|
-
"@strapi/
|
|
93
|
-
"@strapi/utils": "4.4.0-
|
|
83
|
+
"@strapi/admin": "4.4.0-rc.0",
|
|
84
|
+
"@strapi/database": "4.4.0-rc.0",
|
|
85
|
+
"@strapi/generate-new": "4.4.0-rc.0",
|
|
86
|
+
"@strapi/generators": "4.4.0-rc.0",
|
|
87
|
+
"@strapi/logger": "4.4.0-rc.0",
|
|
88
|
+
"@strapi/permissions": "4.4.0-rc.0",
|
|
89
|
+
"@strapi/plugin-content-manager": "4.4.0-rc.0",
|
|
90
|
+
"@strapi/plugin-content-type-builder": "4.4.0-rc.0",
|
|
91
|
+
"@strapi/plugin-email": "4.4.0-rc.0",
|
|
92
|
+
"@strapi/plugin-upload": "4.4.0-rc.0",
|
|
93
|
+
"@strapi/typescript-utils": "4.4.0-rc.0",
|
|
94
|
+
"@strapi/utils": "4.4.0-rc.0",
|
|
94
95
|
"bcryptjs": "2.4.3",
|
|
95
96
|
"boxen": "5.1.2",
|
|
96
97
|
"chalk": "4.1.2",
|
|
@@ -139,5 +140,5 @@
|
|
|
139
140
|
"node": ">=14.19.1 <=18.x.x",
|
|
140
141
|
"npm": ">=6.0.0"
|
|
141
142
|
},
|
|
142
|
-
"gitHead": "
|
|
143
|
+
"gitHead": "57635b60c9a7815830734d85fe76df3ce8ed5898"
|
|
143
144
|
}
|