@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 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
- // Instanciate the Strapi container
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
- // Instanciate the Koa app & the HTTP server
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();
@@ -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, 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
+ };
@@ -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);
@@ -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-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-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-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": "baad89e67844d972ef53a6f1b0839a361bf968c0"
143
+ "gitHead": "57635b60c9a7815830734d85fe76df3ce8ed5898"
143
144
  }