@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 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
- // Instanciate the Strapi container
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
- // Instanciate the Koa app & the HTTP server
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, error = null, credentials } = result || {};
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,5 @@
1
+ 'use strict';
2
+
3
+ const permissions = require('@strapi/permissions');
4
+
5
+ module.exports = ({ providers }) => permissions.engine.new({ providers });
@@ -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,9 @@
1
+ 'use strict';
2
+
3
+ const createActionProvider = require('./action');
4
+ const createConditionProvider = require('./condition');
5
+
6
+ module.exports = {
7
+ createActionProvider,
8
+ createConditionProvider,
9
+ };
@@ -46,6 +46,11 @@ export interface Strapi {
46
46
  */
47
47
  readonly auth: any;
48
48
 
49
+ /**
50
+ * Getter for the Strapi content API container
51
+ */
52
+ readonly contentAPI: any;
53
+
49
54
  /**
50
55
  * Getter for the Strapi sanitizers container
51
56
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@strapi/strapi",
3
- "version": "4.4.0-beta.3",
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.3",
84
- "@strapi/database": "4.4.0-beta.3",
85
- "@strapi/generate-new": "4.4.0-beta.3",
86
- "@strapi/generators": "4.4.0-beta.3",
87
- "@strapi/logger": "4.4.0-beta.3",
88
- "@strapi/plugin-content-manager": "4.4.0-beta.3",
89
- "@strapi/plugin-content-type-builder": "4.4.0-beta.3",
90
- "@strapi/plugin-email": "4.4.0-beta.3",
91
- "@strapi/plugin-upload": "4.4.0-beta.3",
92
- "@strapi/typescript-utils": "4.4.0-beta.3",
93
- "@strapi/utils": "4.4.0-beta.3",
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": "baad89e67844d972ef53a6f1b0839a361bf968c0"
143
+ "gitHead": "8ad31453be3eda4e01eae027995e7e584892e688"
143
144
  }