@strapi/strapi 4.4.0-beta.3 → 4.4.0-beta.4
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 +10 -2
- 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/types/core/strapi/index.d.ts +5 -0
- package/package.json +14 -13
package/lib/Strapi.js
CHANGED
|
@@ -22,6 +22,7 @@ const createCronService = require('./services/cron');
|
|
|
22
22
|
const entityValidator = require('./services/entity-validator');
|
|
23
23
|
const createTelemetry = require('./services/metrics');
|
|
24
24
|
const createAuth = require('./services/auth');
|
|
25
|
+
const createContentAPI = require('./services/content-api');
|
|
25
26
|
const createCustomFields = require('./services/custom-fields');
|
|
26
27
|
const createUpdateNotifier = require('./utils/update-notifier');
|
|
27
28
|
const createStartupLogger = require('./utils/startup-logger');
|
|
@@ -77,7 +78,7 @@ class Strapi {
|
|
|
77
78
|
// Load the app configuration from the dist directory
|
|
78
79
|
const appConfig = loadConfiguration({ appDir: rootDirs.app, distDir: rootDirs.dist }, opts);
|
|
79
80
|
|
|
80
|
-
//
|
|
81
|
+
// Instantiate the Strapi container
|
|
81
82
|
this.container = createContainer(this);
|
|
82
83
|
|
|
83
84
|
// Register every Strapi registry in the container
|
|
@@ -93,6 +94,7 @@ class Strapi {
|
|
|
93
94
|
this.container.register('custom-fields', customFieldsRegistry(this));
|
|
94
95
|
this.container.register('apis', apisRegistry(this));
|
|
95
96
|
this.container.register('auth', createAuth(this));
|
|
97
|
+
this.container.register('content-api', createContentAPI(this));
|
|
96
98
|
this.container.register('sanitizers', sanitizersRegistry(this));
|
|
97
99
|
|
|
98
100
|
// Create a mapping of every useful directory (for the app, dist and static directories)
|
|
@@ -102,7 +104,7 @@ class Strapi {
|
|
|
102
104
|
this.isLoaded = false;
|
|
103
105
|
this.reload = this.reload();
|
|
104
106
|
|
|
105
|
-
//
|
|
107
|
+
// Instantiate the Koa app & the HTTP server
|
|
106
108
|
this.server = createServer(this);
|
|
107
109
|
|
|
108
110
|
// Strapi utils instanciation
|
|
@@ -194,6 +196,10 @@ class Strapi {
|
|
|
194
196
|
return this.container.get('auth');
|
|
195
197
|
}
|
|
196
198
|
|
|
199
|
+
get contentAPI() {
|
|
200
|
+
return this.container.get('content-api');
|
|
201
|
+
}
|
|
202
|
+
|
|
197
203
|
get sanitizers() {
|
|
198
204
|
return this.container.get('sanitizers');
|
|
199
205
|
}
|
|
@@ -453,6 +459,8 @@ class Strapi {
|
|
|
453
459
|
await this.server.initMiddlewares();
|
|
454
460
|
await this.server.initRouting();
|
|
455
461
|
|
|
462
|
+
await this.contentAPI.permissions.registerActions();
|
|
463
|
+
|
|
456
464
|
await this.runLifecyclesFunctions(LIFECYCLES.BOOTSTRAP);
|
|
457
465
|
|
|
458
466
|
this.cron.start();
|
|
@@ -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
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@strapi/strapi",
|
|
3
|
-
"version": "4.4.0-beta.
|
|
3
|
+
"version": "4.4.0-beta.4",
|
|
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-beta.
|
|
84
|
-
"@strapi/database": "4.4.0-beta.
|
|
85
|
-
"@strapi/generate-new": "4.4.0-beta.
|
|
86
|
-
"@strapi/generators": "4.4.0-beta.
|
|
87
|
-
"@strapi/logger": "4.4.0-beta.
|
|
88
|
-
"@strapi/
|
|
89
|
-
"@strapi/plugin-content-
|
|
90
|
-
"@strapi/plugin-
|
|
91
|
-
"@strapi/plugin-
|
|
92
|
-
"@strapi/
|
|
93
|
-
"@strapi/utils": "4.4.0-beta.
|
|
83
|
+
"@strapi/admin": "4.4.0-beta.4",
|
|
84
|
+
"@strapi/database": "4.4.0-beta.4",
|
|
85
|
+
"@strapi/generate-new": "4.4.0-beta.4",
|
|
86
|
+
"@strapi/generators": "4.4.0-beta.4",
|
|
87
|
+
"@strapi/logger": "4.4.0-beta.4",
|
|
88
|
+
"@strapi/permissions": "4.4.0-beta.4",
|
|
89
|
+
"@strapi/plugin-content-manager": "4.4.0-beta.4",
|
|
90
|
+
"@strapi/plugin-content-type-builder": "4.4.0-beta.4",
|
|
91
|
+
"@strapi/plugin-email": "4.4.0-beta.4",
|
|
92
|
+
"@strapi/plugin-upload": "4.4.0-beta.4",
|
|
93
|
+
"@strapi/typescript-utils": "4.4.0-beta.4",
|
|
94
|
+
"@strapi/utils": "4.4.0-beta.4",
|
|
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": "8ad31453be3eda4e01eae027995e7e584892e688"
|
|
143
144
|
}
|