diff --git a/packages/core/admin/ee/middlewares/features-routes/defaults.json b/packages/core/admin/ee/middlewares/features-routes/defaults.json deleted file mode 100644 index 8258a3a555..0000000000 --- a/packages/core/admin/ee/middlewares/features-routes/defaults.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "features-routes": { - "enabled": true - } -} diff --git a/packages/core/admin/ee/middlewares/features-routes/index.js b/packages/core/admin/ee/middlewares/features-routes/index.js deleted file mode 100644 index 47d5fd655c..0000000000 --- a/packages/core/admin/ee/middlewares/features-routes/index.js +++ /dev/null @@ -1,23 +0,0 @@ -'use strict'; - -// eslint-disable-next-line node/no-extraneous-require -const { features } = require('@strapi/strapi/lib/utils/ee'); -const routes = require('./routes'); - -module.exports = strapi => ({ - beforeInitialize() { - strapi.config.middleware.load.before.unshift('features-routes'); - }, - - initialize() { - loadFeaturesRoutes(); - }, -}); - -const loadFeaturesRoutes = () => { - for (const [feature, getFeatureRoutes] of Object.entries(routes)) { - if (features.isEnabled(feature)) { - strapi.admin.routes.push(...getFeatureRoutes); // TODO - } - } -}; diff --git a/packages/core/admin/ee/server/index.js b/packages/core/admin/ee/server/index.js index 97b04bc0d2..6e7c2f12ac 100644 --- a/packages/core/admin/ee/server/index.js +++ b/packages/core/admin/ee/server/index.js @@ -1,7 +1,6 @@ 'use strict'; module.exports = { - // TODO: update load middleware to not load the admin middleware from here bootstrap: require('./bootstrap'), routes: require('./routes'), services: require('./services'), diff --git a/packages/core/admin/ee/server/routes.js b/packages/core/admin/ee/server/routes.js deleted file mode 100644 index c758e080d9..0000000000 --- a/packages/core/admin/ee/server/routes.js +++ /dev/null @@ -1,28 +0,0 @@ -'use strict'; - -module.exports = [ - { - method: 'POST', - path: '/roles', - handler: 'role.create', - config: { - policies: [], - }, - }, - { - method: 'DELETE', - path: '/roles/:id', - handler: 'role.deleteOne', - config: { - policies: [], - }, - }, - { - method: 'POST', - path: '/roles/batch-delete', - handler: 'role.deleteMany', - config: { - policies: [], - }, - }, -]; diff --git a/packages/core/admin/ee/middlewares/features-routes/routes.js b/packages/core/admin/ee/server/routes/features-routes.js similarity index 100% rename from packages/core/admin/ee/middlewares/features-routes/routes.js rename to packages/core/admin/ee/server/routes/features-routes.js diff --git a/packages/core/admin/ee/server/routes/index.js b/packages/core/admin/ee/server/routes/index.js new file mode 100644 index 0000000000..8e1c13394f --- /dev/null +++ b/packages/core/admin/ee/server/routes/index.js @@ -0,0 +1,65 @@ +'use strict'; + +// eslint-disable-next-line node/no-extraneous-require +const { features } = require('@strapi/strapi/lib/utils/ee'); +const featuresRoutes = require('./features-routes'); + +const getFeaturesRoutes = () => { + return Object.entries(featuresRoutes).flatMap(([featureName, featureRoutes]) => { + if (features.isEnabled(featureName)) { + return featureRoutes; + } + }); +}; + +module.exports = [ + { + method: 'POST', + path: '/roles', + handler: 'role.create', + config: { + policies: [ + 'admin::isAuthenticatedAdmin', + { + name: 'admin::hasPermissions', + options: { + actions: ['admin::roles.create'], + }, + }, + ], + }, + }, + { + method: 'DELETE', + path: '/roles/:id', + handler: 'role.deleteOne', + config: { + policies: [ + 'admin::isAuthenticatedAdmin', + { + name: 'admin::hasPermissions', + options: { + actions: ['admin::roles.delete'], + }, + }, + ], + }, + }, + { + method: 'POST', + path: '/roles/batch-delete', + handler: 'role.deleteMany', + config: { + policies: [ + 'admin::isAuthenticatedAdmin', + { + name: 'admin::hasPermissions', + options: { + actions: ['admin::roles.delete'], + }, + }, + ], + }, + }, + ...getFeaturesRoutes(), +]; diff --git a/packages/core/admin/ee/server/tests/provider-login.test.e2e.js b/packages/core/admin/ee/server/tests/provider-login.test.e2e.js index fa27919c91..f7740ee262 100644 --- a/packages/core/admin/ee/server/tests/provider-login.test.e2e.js +++ b/packages/core/admin/ee/server/tests/provider-login.test.e2e.js @@ -95,7 +95,7 @@ describe('Provider Login', () => { test('It should fail with a public request', async () => { const res = await requests.public.get('/admin/providers/options'); - expect(res.status).toBe(hasSSO ? 403 : 404); + expect(res.status).toBe(hasSSO ? 401 : 404); }); test('It should fail with an authenticated request (restricted user)', async () => { @@ -133,7 +133,7 @@ describe('Provider Login', () => { test('It should fail with a public request', async () => { const res = await requests.public.put('/admin/providers/options', { body: newOptions }); - expect(res.status).toBe(hasSSO ? 403 : 405); + expect(res.status).toBe(hasSSO ? 401 : 405); }); test('It should fail with an authenticated request (restricted user)', async () => { diff --git a/packages/core/admin/index.js b/packages/core/admin/index.js index a9e6f1b80f..30d04cde20 100644 --- a/packages/core/admin/index.js +++ b/packages/core/admin/index.js @@ -149,10 +149,6 @@ async function copyAdmin(dest) { await fs.ensureDir(path.resolve(dest, 'config')); await fs.copy(path.resolve(adminPath, 'admin'), path.resolve(dest, 'admin')); - await fs.copy( - path.resolve(adminPath, 'server', 'config', 'layout.js'), - path.resolve(dest, 'config', 'layout.js') - ); // Copy package.json await fs.copy(path.resolve(adminPath, 'package.json'), path.resolve(dest, 'package.json')); diff --git a/packages/core/admin/middlewares/auth/defaults.json b/packages/core/admin/middlewares/auth/defaults.json deleted file mode 100644 index 478f231dbf..0000000000 --- a/packages/core/admin/middlewares/auth/defaults.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "auth": { - "enabled": true - } -} diff --git a/packages/core/admin/middlewares/auth/index.js b/packages/core/admin/middlewares/auth/index.js deleted file mode 100644 index f488955369..0000000000 --- a/packages/core/admin/middlewares/auth/index.js +++ /dev/null @@ -1,41 +0,0 @@ -'use strict'; - -module.exports = strapi => ({ - initialize() { - const passportMiddleware = strapi.admin.services.passport.init(); - - strapi.app.use(passportMiddleware); - - strapi.app.use(async (ctx, next) => { - if ( - ctx.request.header.authorization && - ctx.request.header.authorization.split(' ')[0] === 'Bearer' - ) { - const token = ctx.request.header.authorization.split(' ')[1]; - - const { payload, isValid } = strapi.admin.services.token.decodeJwtToken(token); - - if (isValid) { - // request is made by an admin - const admin = await strapi - .query('admin::user') - .findOne({ where: { id: payload.id }, populate: ['roles'] }); - - if (!admin || !(admin.isActive === true)) { - return ctx.forbidden('Invalid credentials'); - } - - ctx.state.admin = admin; - ctx.state.user = admin; - ctx.state.userAbility = await strapi.admin.services.permission.engine.generateUserAbility( - admin - ); - ctx.state.isAuthenticatedAdmin = true; - return next(); - } - } - - return next(); - }); - }, -}); diff --git a/packages/core/admin/server/config/index.js b/packages/core/admin/server/config/index.js index 1e72b8b208..101859af66 100644 --- a/packages/core/admin/server/config/index.js +++ b/packages/core/admin/server/config/index.js @@ -1,6 +1,9 @@ 'use strict'; +const forgotPasswordTemplate = require('./email-templates/forgot-password'); + module.exports = { - layout: require('./layout'), - ...require('./settings'), + forgotPassword: { + emailTemplate: forgotPasswordTemplate, + }, }; diff --git a/packages/core/admin/server/config/layout.js b/packages/core/admin/server/config/layout.js deleted file mode 100644 index 4f8cfa808a..0000000000 --- a/packages/core/admin/server/config/layout.js +++ /dev/null @@ -1,10 +0,0 @@ -'use strict'; - -module.exports = { - administrator: { - actions: { - create: 'Admin.create', - update: 'Admin.update', - }, - }, -}; diff --git a/packages/core/admin/server/controllers/__tests__/admin.test.js b/packages/core/admin/server/controllers/__tests__/admin.test.js index 41addef785..e1d8e5202f 100644 --- a/packages/core/admin/server/controllers/__tests__/admin.test.js +++ b/packages/core/admin/server/controllers/__tests__/admin.test.js @@ -52,15 +52,13 @@ describe('Admin Controller', () => { describe('information', () => { beforeAll(() => { global.strapi = { - app: { - env: 'development', - }, config: { get: jest.fn( (key, value) => ({ autoReload: undefined, 'info.strapi': '1.0.0', + environment: 'development', }[key] || value) ), }, @@ -71,7 +69,11 @@ describe('Admin Controller', () => { test('Returns application information', async () => { const result = await adminController.information(); - expect(global.strapi.config.get).toHaveBeenCalledTimes(2); + expect(global.strapi.config.get.mock.calls).toEqual([ + ['environment'], + ['autoReload', false], + ['info.strapi', null], + ]); expect(result.data).toBeDefined(); expect(result.data).toStrictEqual({ currentEnvironment: 'development', diff --git a/packages/core/admin/server/controllers/admin.js b/packages/core/admin/server/controllers/admin.js index ef61803516..a9e810cfea 100644 --- a/packages/core/admin/server/controllers/admin.js +++ b/packages/core/admin/server/controllers/admin.js @@ -41,7 +41,7 @@ module.exports = { }, async information() { - const currentEnvironment = strapi.app.env; + const currentEnvironment = strapi.config.get('environment'); const autoReload = strapi.config.get('autoReload', false); const strapiVersion = strapi.config.get('info.strapi', null); const nodeVersion = process.version; diff --git a/packages/core/admin/server/policies/isAuthenticatedAdmin.js b/packages/core/admin/server/policies/isAuthenticatedAdmin.js index 0803cf2de7..90dc786865 100644 --- a/packages/core/admin/server/policies/isAuthenticatedAdmin.js +++ b/packages/core/admin/server/policies/isAuthenticatedAdmin.js @@ -2,7 +2,7 @@ module.exports = (ctx, next) => { if (!ctx.state.isAuthenticatedAdmin) { - throw strapi.errors.forbidden(); + return ctx.unauthorized(); } return next(); diff --git a/packages/core/admin/server/register.js b/packages/core/admin/server/register.js index c85066996a..e2d7d9c86b 100644 --- a/packages/core/admin/server/register.js +++ b/packages/core/admin/server/register.js @@ -3,7 +3,57 @@ // const permissionsFieldsToPropertiesMigration = require('../migrations/permissions-fields-to-properties'); +/** + * Tries to authenticated admin user and calls next. + * @param {KoaContext} ctx + * @param {Middleware} next + * @returns {undefined} + */ +const authMiddleware = async (ctx, next) => { + if (!ctx.request.header.authorization) { + return next(); + } + + if ( + ctx.request.header.authorization && + ctx.request.header.authorization.split(' ')[0] === 'Bearer' + ) { + const token = ctx.request.header.authorization.split(' ')[1]; + + const { payload, isValid } = strapi.admin.services.token.decodeJwtToken(token); + + if (isValid) { + const admin = await strapi + .query('admin::user') + .findOne({ where: { id: payload.id }, populate: ['roles'] }); + + if (!admin || !(admin.isActive === true)) { + return ctx.unauthorized('Invalid credentials'); + } + + // TODO: use simple user & isAuthenticated + + ctx.state.admin = admin; + ctx.state.user = admin; + ctx.state.userAbility = await strapi.admin.services.permission.engine.generateUserAbility( + admin + ); + + ctx.state.isAuthenticatedAdmin = true; + + return next(); + } + } + + ctx.unauthorized('Invalid credentials'); +}; + module.exports = () => { + const passportMiddleware = strapi.admin.services.passport.init(); + + strapi.server.api('admin').use(passportMiddleware); + strapi.server.api('admin').use(authMiddleware); + // FIXME: to implement // strapi.db.migrations.register(permissionsFieldsToPropertiesMigration); }; diff --git a/packages/core/admin/server/routes.js b/packages/core/admin/server/routes/index.js similarity index 100% rename from packages/core/admin/server/routes.js rename to packages/core/admin/server/routes/index.js index 2ebb67dc55..f559f0946b 100644 --- a/packages/core/admin/server/routes.js +++ b/packages/core/admin/server/routes/index.js @@ -1,17 +1,6 @@ 'use strict'; module.exports = [ - { - method: 'GET', - path: '/plugins', - handler: 'admin.plugins', - config: { - policies: [ - 'admin::isAuthenticatedAdmin', - { name: 'admin::hasPermissions', options: { actions: ['admin::marketplace.read'] } }, - ], - }, - }, { method: 'GET', path: '/init', @@ -30,6 +19,17 @@ module.exports = [ policies: ['admin::isAuthenticatedAdmin'], }, }, + { + method: 'GET', + path: '/plugins', + handler: 'admin.plugins', + config: { + policies: [ + 'admin::isAuthenticatedAdmin', + { name: 'admin::hasPermissions', options: { actions: ['admin::marketplace.read'] } }, + ], + }, + }, { method: 'POST', path: '/plugins/install', diff --git a/packages/core/admin/server/tests/admin-authenticated-user.test.e2e.js b/packages/core/admin/server/tests/admin-authenticated-user.test.e2e.js index d7c7e9a3c3..bf574b380a 100644 --- a/packages/core/admin/server/tests/admin-authenticated-user.test.e2e.js +++ b/packages/core/admin/server/tests/admin-authenticated-user.test.e2e.js @@ -44,7 +44,7 @@ describe('Authenticated User', () => { body: {}, }); - expect(res.statusCode).toBe(403); + expect(res.statusCode).toBe(401); }); }); @@ -57,7 +57,7 @@ describe('Authenticated User', () => { body: {}, }); - expect(res.statusCode).toBe(403); + expect(res.statusCode).toBe(401); }); test('Fails when trying to edit roles', async () => { diff --git a/packages/core/content-manager/.gitignore b/packages/core/content-manager/.gitignore index 9b43c3f973..da1f965030 100644 --- a/packages/core/content-manager/.gitignore +++ b/packages/core/content-manager/.gitignore @@ -2,7 +2,6 @@ coverage node_modules stats.json -config/layout.json package-lock.json diff --git a/packages/core/content-manager/config/policies/routing.js b/packages/core/content-manager/config/policies/routing.js index 8298ef70bb..089d503e62 100644 --- a/packages/core/content-manager/config/policies/routing.js +++ b/packages/core/content-manager/config/policies/routing.js @@ -12,10 +12,19 @@ module.exports = async (ctx, next) => { } const target = ct.plugin === 'admin' ? strapi.admin : strapi.plugin(ct.plugin); + + const { route } = ctx.state; + + if (typeof route.handler !== 'string') { + return next(); + } + + const [, action] = route.handler.split('.'); + const configPath = ct.plugin === 'admin' - ? ['server.admin.layout', ct.modelName, 'actions', ctx.request.route.action] - : ['plugin', ct.plugin, 'layout', ct.modelName, 'actions', ctx.request.route.action]; + ? ['server.admin.layout', ct.modelName, 'actions', action] + : ['plugin', ct.plugin, 'layout', ct.modelName, 'actions', action]; const actionConfig = strapi.config.get(configPath); diff --git a/packages/core/content-manager/server/routes/admin.js b/packages/core/content-manager/server/routes/admin.js new file mode 100644 index 0000000000..1457baf664 --- /dev/null +++ b/packages/core/content-manager/server/routes/admin.js @@ -0,0 +1,321 @@ +'use strict'; + +module.exports = { + type: 'admin', + routes: [ + { + method: 'GET', + path: '/content-types', + handler: 'content-types.findContentTypes', + config: { + policies: [], + }, + }, + { + method: 'GET', + path: '/content-types-settings', + handler: 'content-types.findContentTypesSettings', + config: { + policies: [], + }, + }, + { + method: 'GET', + path: '/content-types/:uid/configuration', + handler: 'content-types.findContentTypeConfiguration', + config: { + policies: [], + }, + }, + { + method: 'PUT', + path: '/content-types/:uid/configuration', + handler: 'content-types.updateContentTypeConfiguration', + config: { + policies: ['admin::isAuthenticatedAdmin'], + }, + }, + + { + method: 'GET', + path: '/components', + handler: 'components.findComponents', + config: { + policies: [], + }, + }, + { + method: 'GET', + path: '/components/:uid/configuration', + handler: 'components.findComponentConfiguration', + config: { + policies: [], + }, + }, + { + method: 'PUT', + path: '/components/:uid/configuration', + handler: 'components.updateComponentConfiguration', + config: { + policies: [], + }, + }, + + { + method: 'POST', + path: '/uid/generate', + handler: 'uid.generateUID', + config: { + policies: [], + }, + }, + { + method: 'POST', + path: '/uid/check-availability', + handler: 'uid.checkUIDAvailability', + config: { + policies: [], + }, + }, + { + method: 'POST', + path: '/relations/:model/:targetField', + handler: 'relations.find', + config: { + policies: [ + 'admin::isAuthenticatedAdmin', + { + name: 'plugin::content-manager.hasPermissions', + options: { + actions: [ + 'plugin::content-manager.explorer.create', + 'plugin::content-manager.explorer.update', + ], + hasAtLeastOne: true, + }, + }, + ], + }, + }, + { + method: 'GET', + path: '/single-types/:model', + handler: 'single-types.find', + config: { + policies: [ + 'routing', + 'admin::isAuthenticatedAdmin', + { + name: 'plugin::content-manager.hasPermissions', + options: { actions: ['plugin::content-manager.explorer.read'] }, + }, + ], + }, + }, + { + method: 'PUT', + path: '/single-types/:model', + handler: 'single-types.createOrUpdate', + config: { + policies: [ + 'routing', + 'admin::isAuthenticatedAdmin', + { + name: 'plugin::content-manager.hasPermissions', + options: { + actions: [ + 'plugin::content-manager.explorer.create', + 'plugin::content-manager.explorer.update', + ], + hasAtLeastOne: true, + }, + }, + ], + }, + }, + { + method: 'DELETE', + path: '/single-types/:model', + handler: 'single-types.delete', + config: { + policies: [ + 'routing', + 'admin::isAuthenticatedAdmin', + { + name: 'plugin::content-manager.hasPermissions', + options: { actions: ['plugin::content-manager.explorer.delete'] }, + }, + ], + }, + }, + { + method: 'POST', + path: '/single-types/:model/actions/publish', + handler: 'single-types.publish', + config: { + policies: [ + 'routing', + 'plugin::content-manager.has-draft-and-publish', + 'admin::isAuthenticatedAdmin', + { + name: 'plugin::content-manager.hasPermissions', + options: { actions: ['plugin::content-manager.explorer.publish'] }, + }, + ], + }, + }, + { + method: 'POST', + path: '/single-types/:model/actions/unpublish', + handler: 'single-types.unpublish', + config: { + policies: [ + 'routing', + 'plugin::content-manager.has-draft-and-publish', + 'admin::isAuthenticatedAdmin', + { + name: 'plugin::content-manager.hasPermissions', + options: { actions: ['plugin::content-manager.explorer.publish'] }, + }, + ], + }, + }, + { + method: 'GET', + path: '/collection-types/:model/:id/:targetField', + handler: 'collection-types.previewManyRelations', + config: { + policies: [ + 'routing', + 'admin::isAuthenticatedAdmin', + { + name: 'plugin::content-manager.hasPermissions', + options: { actions: ['plugin::content-manager.explorer.read'] }, + }, + ], + }, + }, + { + method: 'GET', + path: '/collection-types/:model', + handler: 'collection-types.find', + config: { + policies: [ + 'routing', + 'admin::isAuthenticatedAdmin', + { + name: 'plugin::content-manager.hasPermissions', + options: { actions: ['plugin::content-manager.explorer.read'] }, + }, + ], + }, + }, + { + method: 'POST', + path: '/collection-types/:model', + handler: 'collection-types.create', + config: { + policies: [ + 'routing', + 'admin::isAuthenticatedAdmin', + { + name: 'plugin::content-manager.hasPermissions', + options: { actions: ['plugin::content-manager.explorer.create'] }, + }, + ], + }, + }, + { + method: 'GET', + path: '/collection-types/:model/:id', + handler: 'collection-types.findOne', + config: { + policies: [ + 'routing', + 'admin::isAuthenticatedAdmin', + { + name: 'plugin::content-manager.hasPermissions', + options: { actions: ['plugin::content-manager.explorer.read'] }, + }, + ], + }, + }, + { + method: 'PUT', + path: '/collection-types/:model/:id', + handler: 'collection-types.update', + config: { + policies: [ + 'routing', + 'admin::isAuthenticatedAdmin', + { + name: 'plugin::content-manager.hasPermissions', + options: { actions: ['plugin::content-manager.explorer.update'] }, + }, + ], + }, + }, + { + method: 'DELETE', + path: '/collection-types/:model/:id', + handler: 'collection-types.delete', + config: { + policies: [ + 'routing', + 'admin::isAuthenticatedAdmin', + { + name: 'plugin::content-manager.hasPermissions', + options: { actions: ['plugin::content-manager.explorer.delete'] }, + }, + ], + }, + }, + { + method: 'POST', + path: '/collection-types/:model/:id/actions/publish', + handler: 'collection-types.publish', + config: { + policies: [ + 'routing', + 'plugin::content-manager.has-draft-and-publish', + 'admin::isAuthenticatedAdmin', + { + name: 'plugin::content-manager.hasPermissions', + options: { actions: ['plugin::content-manager.explorer.publish'] }, + }, + ], + }, + }, + { + method: 'POST', + path: '/collection-types/:model/:id/actions/unpublish', + handler: 'collection-types.unpublish', + config: { + policies: [ + 'routing', + 'plugin::content-manager.has-draft-and-publish', + 'admin::isAuthenticatedAdmin', + { + name: 'plugin::content-manager.hasPermissions', + options: { actions: ['plugin::content-manager.explorer.publish'] }, + }, + ], + }, + }, + { + method: 'POST', + path: '/collection-types/:model/actions/bulkDelete', + handler: 'collection-types.bulkDelete', + config: { + policies: [ + 'routing', + 'admin::isAuthenticatedAdmin', + { + name: 'plugin::content-manager.hasPermissions', + options: { actions: ['plugin::content-manager.explorer.delete'] }, + }, + ], + }, + }, + ], +}; diff --git a/packages/core/content-manager/server/routes/index.js b/packages/core/content-manager/server/routes/index.js index 0ae8fd0c79..772e5b9ab3 100644 --- a/packages/core/content-manager/server/routes/index.js +++ b/packages/core/content-manager/server/routes/index.js @@ -1,318 +1,5 @@ 'use strict'; -module.exports = [ - { - method: 'GET', - path: '/content-types', - handler: 'content-types.findContentTypes', - config: { - policies: [], - }, - }, - { - method: 'GET', - path: '/content-types-settings', - handler: 'content-types.findContentTypesSettings', - config: { - policies: [], - }, - }, - { - method: 'GET', - path: '/content-types/:uid/configuration', - handler: 'content-types.findContentTypeConfiguration', - config: { - policies: [], - }, - }, - { - method: 'PUT', - path: '/content-types/:uid/configuration', - handler: 'content-types.updateContentTypeConfiguration', - config: { - policies: ['admin::isAuthenticatedAdmin'], - }, - }, - - { - method: 'GET', - path: '/components', - handler: 'components.findComponents', - config: { - policies: [], - }, - }, - { - method: 'GET', - path: '/components/:uid/configuration', - handler: 'components.findComponentConfiguration', - config: { - policies: [], - }, - }, - { - method: 'PUT', - path: '/components/:uid/configuration', - handler: 'components.updateComponentConfiguration', - config: { - policies: [], - }, - }, - - { - method: 'POST', - path: '/uid/generate', - handler: 'uid.generateUID', - config: { - policies: [], - }, - }, - { - method: 'POST', - path: '/uid/check-availability', - handler: 'uid.checkUIDAvailability', - config: { - policies: [], - }, - }, - { - method: 'POST', - path: '/relations/:model/:targetField', - handler: 'relations.find', - config: { - policies: [ - 'admin::isAuthenticatedAdmin', - { - name: 'plugin::content-manager.hasPermissions', - options: { - actions: [ - 'plugin::content-manager.explorer.create', - 'plugin::content-manager.explorer.update', - ], - hasAtLeastOne: true, - }, - }, - ], - }, - }, - { - method: 'GET', - path: '/single-types/:model', - handler: 'single-types.find', - config: { - policies: [ - 'routing', - 'admin::isAuthenticatedAdmin', - { - name: 'plugin::content-manager.hasPermissions', - options: { actions: ['plugin::content-manager.explorer.read'] }, - }, - ], - }, - }, - { - method: 'PUT', - path: '/single-types/:model', - handler: 'single-types.createOrUpdate', - config: { - policies: [ - 'routing', - 'admin::isAuthenticatedAdmin', - { - name: 'plugin::content-manager.hasPermissions', - options: { - actions: [ - 'plugin::content-manager.explorer.create', - 'plugin::content-manager.explorer.update', - ], - hasAtLeastOne: true, - }, - }, - ], - }, - }, - { - method: 'DELETE', - path: '/single-types/:model', - handler: 'single-types.delete', - config: { - policies: [ - 'routing', - 'admin::isAuthenticatedAdmin', - { - name: 'plugin::content-manager.hasPermissions', - options: { actions: ['plugin::content-manager.explorer.delete'] }, - }, - ], - }, - }, - { - method: 'POST', - path: '/single-types/:model/actions/publish', - handler: 'single-types.publish', - config: { - policies: [ - 'routing', - 'plugin::content-manager.has-draft-and-publish', - 'admin::isAuthenticatedAdmin', - { - name: 'plugin::content-manager.hasPermissions', - options: { actions: ['plugin::content-manager.explorer.publish'] }, - }, - ], - }, - }, - { - method: 'POST', - path: '/single-types/:model/actions/unpublish', - handler: 'single-types.unpublish', - config: { - policies: [ - 'routing', - 'plugin::content-manager.has-draft-and-publish', - 'admin::isAuthenticatedAdmin', - { - name: 'plugin::content-manager.hasPermissions', - options: { actions: ['plugin::content-manager.explorer.publish'] }, - }, - ], - }, - }, - { - method: 'GET', - path: '/collection-types/:model/:id/:targetField', - handler: 'collection-types.previewManyRelations', - config: { - policies: [ - 'routing', - 'admin::isAuthenticatedAdmin', - { - name: 'plugin::content-manager.hasPermissions', - options: { actions: ['plugin::content-manager.explorer.read'] }, - }, - ], - }, - }, - { - method: 'GET', - path: '/collection-types/:model', - handler: 'collection-types.find', - config: { - policies: [ - 'routing', - 'admin::isAuthenticatedAdmin', - { - name: 'plugin::content-manager.hasPermissions', - options: { actions: ['plugin::content-manager.explorer.read'] }, - }, - ], - }, - }, - { - method: 'POST', - path: '/collection-types/:model', - handler: 'collection-types.create', - config: { - policies: [ - 'routing', - 'admin::isAuthenticatedAdmin', - { - name: 'plugin::content-manager.hasPermissions', - options: { actions: ['plugin::content-manager.explorer.create'] }, - }, - ], - }, - }, - { - method: 'GET', - path: '/collection-types/:model/:id', - handler: 'collection-types.findOne', - config: { - policies: [ - 'routing', - 'admin::isAuthenticatedAdmin', - { - name: 'plugin::content-manager.hasPermissions', - options: { actions: ['plugin::content-manager.explorer.read'] }, - }, - ], - }, - }, - { - method: 'PUT', - path: '/collection-types/:model/:id', - handler: 'collection-types.update', - config: { - policies: [ - 'routing', - 'admin::isAuthenticatedAdmin', - { - name: 'plugin::content-manager.hasPermissions', - options: { actions: ['plugin::content-manager.explorer.update'] }, - }, - ], - }, - }, - { - method: 'DELETE', - path: '/collection-types/:model/:id', - handler: 'collection-types.delete', - config: { - policies: [ - 'routing', - 'admin::isAuthenticatedAdmin', - { - name: 'plugin::content-manager.hasPermissions', - options: { actions: ['plugin::content-manager.explorer.delete'] }, - }, - ], - }, - }, - { - method: 'POST', - path: '/collection-types/:model/:id/actions/publish', - handler: 'collection-types.publish', - config: { - policies: [ - 'routing', - 'plugin::content-manager.has-draft-and-publish', - 'admin::isAuthenticatedAdmin', - { - name: 'plugin::content-manager.hasPermissions', - options: { actions: ['plugin::content-manager.explorer.publish'] }, - }, - ], - }, - }, - { - method: 'POST', - path: '/collection-types/:model/:id/actions/unpublish', - handler: 'collection-types.unpublish', - config: { - policies: [ - 'routing', - 'plugin::content-manager.has-draft-and-publish', - 'admin::isAuthenticatedAdmin', - { - name: 'plugin::content-manager.hasPermissions', - options: { actions: ['plugin::content-manager.explorer.publish'] }, - }, - ], - }, - }, - { - method: 'POST', - path: '/collection-types/:model/actions/bulkDelete', - handler: 'collection-types.bulkDelete', - config: { - policies: [ - 'routing', - 'admin::isAuthenticatedAdmin', - { - name: 'plugin::content-manager.hasPermissions', - options: { actions: ['plugin::content-manager.explorer.delete'] }, - }, - ], - }, - }, -]; +module.exports = { + admin: require('./admin'), +}; diff --git a/packages/core/content-type-builder/server/routes/admin.js b/packages/core/content-type-builder/server/routes/admin.js new file mode 100644 index 0000000000..b9ec594307 --- /dev/null +++ b/packages/core/content-type-builder/server/routes/admin.js @@ -0,0 +1,176 @@ +'use strict'; + +module.exports = { + type: 'admin', + routes: [ + { + method: 'GET', + path: '/reserved-names', + handler: 'builder.getReservedNames', + config: { + policies: [ + { + name: 'admin::hasPermissions', + options: { actions: ['plugin::content-type-builder.read'] }, + }, + ], + }, + }, + { + method: 'GET', + path: '/content-types', + handler: 'content-types.getContentTypes', + config: { + policies: [ + { + name: 'admin::hasPermissions', + options: { actions: ['plugin::content-type-builder.read'] }, + }, + ], + }, + }, + { + method: 'GET', + path: '/content-types/:uid', + handler: 'content-types.getContentType', + config: { + policies: [ + { + name: 'admin::hasPermissions', + options: { actions: ['plugin::content-type-builder.read'] }, + }, + ], + }, + }, + { + method: 'POST', + path: '/content-types', + handler: 'content-types.createContentType', + config: { + policies: [ + { + name: 'admin::hasPermissions', + options: { actions: ['plugin::content-type-builder.read'] }, + }, + ], + }, + }, + { + method: 'PUT', + path: '/content-types/:uid', + handler: 'content-types.updateContentType', + config: { + policies: [ + { + name: 'admin::hasPermissions', + options: { actions: ['plugin::content-type-builder.read'] }, + }, + ], + }, + }, + { + method: 'DELETE', + path: '/content-types/:uid', + handler: 'content-types.deleteContentType', + config: { + policies: [ + { + name: 'admin::hasPermissions', + options: { actions: ['plugin::content-type-builder.read'] }, + }, + ], + }, + }, + { + method: 'GET', + path: '/components', + handler: 'components.getComponents', + config: { + policies: [ + { + name: 'admin::hasPermissions', + options: { actions: ['plugin::content-type-builder.read'] }, + }, + ], + }, + }, + { + method: 'GET', + path: '/components/:uid', + handler: 'components.getComponent', + config: { + policies: [ + { + name: 'admin::hasPermissions', + options: { actions: ['plugin::content-type-builder.read'] }, + }, + ], + }, + }, + { + method: 'POST', + path: '/components', + handler: 'components.createComponent', + config: { + policies: [ + { + name: 'admin::hasPermissions', + options: { actions: ['plugin::content-type-builder.read'] }, + }, + ], + }, + }, + { + method: 'PUT', + path: '/components/:uid', + handler: 'components.updateComponent', + config: { + policies: [ + { + name: 'admin::hasPermissions', + options: { actions: ['plugin::content-type-builder.read'] }, + }, + ], + }, + }, + { + method: 'DELETE', + path: '/components/:uid', + handler: 'components.deleteComponent', + config: { + policies: [ + { + name: 'admin::hasPermissions', + options: { actions: ['plugin::content-type-builder.read'] }, + }, + ], + }, + }, + { + method: 'PUT', + path: '/component-categories/:name', + handler: 'component-categories.editCategory', + config: { + policies: [ + { + name: 'admin::hasPermissions', + options: { actions: ['plugin::content-type-builder.read'] }, + }, + ], + }, + }, + { + method: 'DELETE', + path: '/component-categories/:name', + handler: 'component-categories.deleteCategory', + config: { + policies: [ + { + name: 'admin::hasPermissions', + options: { actions: ['plugin::content-type-builder.read'] }, + }, + ], + }, + }, + ], +}; diff --git a/packages/core/content-type-builder/server/routes/content-api.js b/packages/core/content-type-builder/server/routes/content-api.js new file mode 100644 index 0000000000..0d397cac00 --- /dev/null +++ b/packages/core/content-type-builder/server/routes/content-api.js @@ -0,0 +1,27 @@ +'use strict'; + +module.exports = { + type: 'content-api', + routes: [ + { + method: 'GET', + path: '/content-types', + handler: 'content-types.getContentTypes', + }, + { + method: 'GET', + path: '/content-types/:uid', + handler: 'content-types.getContentType', + }, + { + method: 'GET', + path: '/components', + handler: 'components.getComponents', + }, + { + method: 'GET', + path: '/components/:uid', + handler: 'components.getComponent', + }, + ], +}; diff --git a/packages/core/content-type-builder/server/routes/index.js b/packages/core/content-type-builder/server/routes/index.js index 593ad2c2aa..6939f2d012 100644 --- a/packages/core/content-type-builder/server/routes/index.js +++ b/packages/core/content-type-builder/server/routes/index.js @@ -1,173 +1,6 @@ 'use strict'; -module.exports = [ - { - method: 'GET', - path: '/reserved-names', - handler: 'builder.getReservedNames', - config: { - policies: [ - { - name: 'admin::hasPermissions', - options: { actions: ['plugin::content-type-builder.read'] }, - }, - ], - }, - }, - { - method: 'GET', - path: '/content-types', - handler: 'content-types.getContentTypes', - config: { - policies: [ - { - name: 'admin::hasPermissions', - options: { actions: ['plugin::content-type-builder.read'] }, - }, - ], - }, - }, - { - method: 'GET', - path: '/content-types/:uid', - handler: 'content-types.getContentType', - config: { - policies: [ - { - name: 'admin::hasPermissions', - options: { actions: ['plugin::content-type-builder.read'] }, - }, - ], - }, - }, - { - method: 'POST', - path: '/content-types', - handler: 'content-types.createContentType', - config: { - policies: [ - { - name: 'admin::hasPermissions', - options: { actions: ['plugin::content-type-builder.read'] }, - }, - ], - }, - }, - { - method: 'PUT', - path: '/content-types/:uid', - handler: 'content-types.updateContentType', - config: { - policies: [ - { - name: 'admin::hasPermissions', - options: { actions: ['plugin::content-type-builder.read'] }, - }, - ], - }, - }, - { - method: 'DELETE', - path: '/content-types/:uid', - handler: 'content-types.deleteContentType', - config: { - policies: [ - { - name: 'admin::hasPermissions', - options: { actions: ['plugin::content-type-builder.read'] }, - }, - ], - }, - }, - { - method: 'GET', - path: '/components', - handler: 'components.getComponents', - config: { - policies: [ - { - name: 'admin::hasPermissions', - options: { actions: ['plugin::content-type-builder.read'] }, - }, - ], - }, - }, - { - method: 'GET', - path: '/components/:uid', - handler: 'components.getComponent', - config: { - policies: [ - { - name: 'admin::hasPermissions', - options: { actions: ['plugin::content-type-builder.read'] }, - }, - ], - }, - }, - { - method: 'POST', - path: '/components', - handler: 'components.createComponent', - config: { - policies: [ - { - name: 'admin::hasPermissions', - options: { actions: ['plugin::content-type-builder.read'] }, - }, - ], - }, - }, - { - method: 'PUT', - path: '/components/:uid', - handler: 'components.updateComponent', - config: { - policies: [ - { - name: 'admin::hasPermissions', - options: { actions: ['plugin::content-type-builder.read'] }, - }, - ], - }, - }, - { - method: 'DELETE', - path: '/components/:uid', - handler: 'components.deleteComponent', - config: { - policies: [ - { - name: 'admin::hasPermissions', - options: { actions: ['plugin::content-type-builder.read'] }, - }, - ], - }, - }, - { - method: 'PUT', - path: '/component-categories/:name', - handler: 'component-categories.editCategory', - config: { - policies: [ - { - name: 'admin::hasPermissions', - options: { actions: ['plugin::content-type-builder.read'] }, - }, - ], - }, - }, - { - method: 'DELETE', - path: '/component-categories/:name', - handler: 'component-categories.deleteCategory', - config: { - policies: [ - { - name: 'admin::hasPermissions', - options: { actions: ['plugin::content-type-builder.read'] }, - }, - ], - }, - }, -]; +module.exports = { + admin: require('./admin'), + 'content-api': require('./content-api'), +}; diff --git a/packages/core/content-type-builder/server/services/content-types.js b/packages/core/content-type-builder/server/services/content-types.js index e042db96f4..6aff69ea04 100644 --- a/packages/core/content-type-builder/server/services/content-types.js +++ b/packages/core/content-type-builder/server/services/content-types.js @@ -2,7 +2,6 @@ const _ = require('lodash'); const { getOr } = require('lodash/fp'); -const strapiGenerators = require('@strapi/generators'); const { nameToSlug, contentTypes: contentTypesUtils } = require('@strapi/utils'); const { formatAttributes, replaceTemporaryUIDs } = require('../utils/attributes'); @@ -127,6 +126,7 @@ const createContentType = async ({ contentType, components = [] }, options = {}) * @param {string} name */ const generateAPI = ({ name, kind = 'collectionType' }) => { + const strapiGenerators = require('@strapi/generators'); return strapiGenerators.generate('api', { id: nameToSlug(name), kind }, { dir: strapi.dir }); }; diff --git a/packages/core/content-type-builder/tests/content-types.test.e2e.js b/packages/core/content-type-builder/tests/content-types.test.e2e.js index febb70add2..11d6eb5ad3 100644 --- a/packages/core/content-type-builder/tests/content-types.test.e2e.js +++ b/packages/core/content-type-builder/tests/content-types.test.e2e.js @@ -227,15 +227,7 @@ describe('Content Type Builder - Content types', () => { // create data for (let i = 0; i < 2; i++) { - const res = await rq({ - method: 'POST', - url: `/test-collections`, - body: { - title: 'Test', - }, - }); - - expect(res.statusCode).toBe(200); + await strapi.query(uid).create({ data: { title: 'Test' } }); } const updateRes = await rq({ diff --git a/packages/core/database/lib/entity-manager.js b/packages/core/database/lib/entity-manager.js index 71fbdb2541..8229454916 100644 --- a/packages/core/database/lib/entity-manager.js +++ b/packages/core/database/lib/entity-manager.js @@ -29,7 +29,6 @@ const toAssocs = data => { }); }; -// TODO: handle programmatic defaults const toRow = (metadata, data = {}, { withDefaults = false } = {}) => { const { attributes } = metadata; @@ -42,6 +41,7 @@ const toRow = (metadata, data = {}, { withDefaults = false } = {}) => { if (types.isScalar(attribute.type)) { const field = createField(attribute); + // TODO: move application level default to entity service if (_.isUndefined(data[attributeName])) { if (!_.isUndefined(attribute.default) && withDefaults) { if (typeof attribute.default === 'function') { @@ -654,7 +654,10 @@ const createEntityManager = db => { .where(joinTable.on || {}) .execute(); - if (['oneToOne', 'oneToMany'].includes(attribute.relation)) { + if ( + isBidirectional(attribute) && + ['oneToOne', 'oneToMany'].includes(attribute.relation) + ) { await this.createQueryBuilder(joinTable.name) .delete() .where({ [inverseJoinColumn.name]: toIds(data[attributeName]) }) diff --git a/packages/core/email/server/controllers/email.js b/packages/core/email/server/controllers/email.js index d6dba72a01..7290b739a9 100644 --- a/packages/core/email/server/controllers/email.js +++ b/packages/core/email/server/controllers/email.js @@ -10,6 +10,7 @@ const { isNil, pick } = require('lodash/fp'); module.exports = { async send(ctx) { let options = ctx.request.body; + try { await strapi .plugin('email') diff --git a/packages/core/email/server/routes/admin.js b/packages/core/email/server/routes/admin.js new file mode 100644 index 0000000000..341550bb48 --- /dev/null +++ b/packages/core/email/server/routes/admin.js @@ -0,0 +1,35 @@ +'use strict'; + +module.exports = { + type: 'admin', + routes: [ + { + method: 'POST', + path: '/', + handler: 'email.send', + config: {}, + }, + { + method: 'POST', + path: '/test', + handler: 'email.test', + config: { + policies: [ + 'admin::isAuthenticatedAdmin', + { name: 'admin::hasPermissions', options: { actions: ['plugin::email.settings.read'] } }, + ], + }, + }, + { + method: 'GET', + path: '/settings', + handler: 'email.getSettings', + config: { + policies: [ + 'admin::isAuthenticatedAdmin', + { name: 'admin::hasPermissions', options: { actions: ['plugin::email.settings.read'] } }, + ], + }, + }, + ], +}; diff --git a/packages/core/email/server/routes/content-api.js b/packages/core/email/server/routes/content-api.js new file mode 100644 index 0000000000..23f1764864 --- /dev/null +++ b/packages/core/email/server/routes/content-api.js @@ -0,0 +1,12 @@ +'use strict'; + +module.exports = { + type: 'content-api', + routes: [ + { + method: 'POST', + path: '/', + handler: 'email.send', + }, + ], +}; diff --git a/packages/core/email/server/routes/index.js b/packages/core/email/server/routes/index.js index b517ede188..6939f2d012 100644 --- a/packages/core/email/server/routes/index.js +++ b/packages/core/email/server/routes/index.js @@ -1,49 +1,6 @@ 'use strict'; -module.exports = [ - { - method: 'POST', - path: '/', - handler: 'Email.send', - config: { - policies: [], - description: 'Send an email', - tag: { - plugin: 'email', - name: 'Email', - }, - }, - }, - { - method: 'POST', - path: '/test', - handler: 'Email.test', - config: { - policies: [ - 'admin::isAuthenticatedAdmin', - { name: 'admin::hasPermissions', options: { actions: ['plugin::email.settings.read'] } }, - ], - description: 'Send an test email', - tag: { - plugin: 'email', - name: 'Email', - }, - }, - }, - { - method: 'GET', - path: '/settings', - handler: 'Email.getSettings', - config: { - policies: [ - 'admin::isAuthenticatedAdmin', - { name: 'admin::hasPermissions', options: { actions: ['plugin::email.settings.read'] } }, - ], - description: 'Get the email settings', - tag: { - plugin: 'email', - name: 'Email', - }, - }, - }, -]; +module.exports = { + admin: require('./admin'), + 'content-api': require('./content-api'), +}; diff --git a/packages/core/strapi/lib/Strapi.js b/packages/core/strapi/lib/Strapi.js index f56c8b8c55..40c0699f96 100644 --- a/packages/core/strapi/lib/Strapi.js +++ b/packages/core/strapi/lib/Strapi.js @@ -1,25 +1,24 @@ 'use strict'; -const Koa = require('koa'); -const Router = require('koa-router'); const _ = require('lodash'); const { createLogger } = require('@strapi/logger'); const { Database } = require('@strapi/database'); const loadConfiguration = require('./core/app-configuration'); -const { createHTTPServer } = require('./server'); const { createContainer } = require('./container'); const utils = require('./utils'); const initializeMiddlewares = require('./middlewares'); const createStrapiFs = require('./services/fs'); const createEventHub = require('./services/event-hub'); +const { createServer } = require('./services/server'); const createWebhookRunner = require('./services/webhook-runner'); const { webhookModel, createWebhookStore } = require('./services/webhook-store'); const { createCoreStore, coreStoreModel } = require('./services/core-store'); const createEntityService = require('./services/entity-service'); const entityValidator = require('./services/entity-validator'); const createTelemetry = require('./services/metrics'); +const createContentAPI = require('./services/content-api'); const createUpdateNotifier = require('./utils/update-notifier'); const createStartupLogger = require('./utils/startup-logger'); const ee = require('./utils/ee'); @@ -54,16 +53,15 @@ class Strapi { this.container.register('modules', modulesRegistry(this)); this.container.register('plugins', pluginsRegistry(this)); this.container.register('apis', apisRegistry(this)); + this.container.register('content-api', createContentAPI(this)); this.isLoaded = false; this.reload = this.reload(); - this.app = new Koa(); - this.router = new Router(); - this.server = createHTTPServer(this, this.app); + this.server = createServer(this); + this.fs = createStrapiFs(this); this.eventHub = createEventHub(); this.startupLogger = createStartupLogger(this); - this.app.proxy = this.config.get('server.proxy'); this.log = createLogger(this.config.get('logger', {})); createUpdateNotifier(this).notify(); @@ -123,9 +121,6 @@ class Strapi { await this.load(); } - this.app.use(this.router.routes()).use(this.router.allowedMethods()); - - // Launch server. await this.listen(); return this; @@ -268,15 +263,6 @@ class Strapi { } async load() { - this.app.use(async (ctx, next) => { - if (ctx.request.url === '/_health' && ['HEAD', 'GET'].includes(ctx.request.method)) { - ctx.set('strapi', 'You are so French!'); - ctx.status = 204; - } else { - await next(); - } - }); - await Promise.all([ this.loadPlugins(), this.loadAdmin(), @@ -332,11 +318,12 @@ class Strapi { this.telemetry = createTelemetry(this); // Initialize middlewares. - await initializeMiddlewares.call(this); + await initializeMiddlewares(this); await this.runLifecyclesFunctions(LIFECYCLES.BOOTSTRAP); this.isLoaded = true; + return this; } @@ -403,7 +390,7 @@ class Strapi { await execLifecycle(this.config.get(configPath)); // admin - await this.admin[lifecycleName](); + await this.admin[lifecycleName](this); } getModel(uid) { diff --git a/packages/core/strapi/lib/commands/build.js b/packages/core/strapi/lib/commands/build.js index c2939db01a..256d7f1e78 100644 --- a/packages/core/strapi/lib/commands/build.js +++ b/packages/core/strapi/lib/commands/build.js @@ -1,7 +1,6 @@ 'use strict'; const { green } = require('chalk'); -// eslint-disable-next-line node/no-extraneous-require const strapiAdmin = require('@strapi/admin'); const { getConfigUrls } = require('@strapi/utils'); const loadConfiguration = require('../core/app-configuration'); diff --git a/packages/core/strapi/lib/commands/watchAdmin.js b/packages/core/strapi/lib/commands/watchAdmin.js index 0bb21b8a8e..9a68367b6a 100644 --- a/packages/core/strapi/lib/commands/watchAdmin.js +++ b/packages/core/strapi/lib/commands/watchAdmin.js @@ -1,6 +1,5 @@ 'use strict'; -// eslint-disable-next-line node/no-extraneous-require const strapiAdmin = require('@strapi/admin'); const { getOr } = require('lodash/fp'); const { getConfigUrls, getAbsoluteServerUrl } = require('@strapi/utils'); diff --git a/packages/core/strapi/lib/core/bootstrap.js b/packages/core/strapi/lib/core/bootstrap.js index 2d6f88fd3d..91629d878e 100644 --- a/packages/core/strapi/lib/core/bootstrap.js +++ b/packages/core/strapi/lib/core/bootstrap.js @@ -11,6 +11,10 @@ module.exports = function(strapi) { }); }); + _.forEach(strapi.admin.middlewares, (middleware, middlewareName) => { + strapi.middleware[middlewareName] = middleware; + }); + _.forEach(strapi.plugins, plugin => { _.forEach(plugin.middlewares, (middleware, middlewareName) => { strapi.middleware[middlewareName] = middleware; diff --git a/packages/core/strapi/lib/core/loaders/middlewares.js b/packages/core/strapi/lib/core/loaders/middlewares.js index cc594e34c9..47de585986 100644 --- a/packages/core/strapi/lib/core/loaders/middlewares.js +++ b/packages/core/strapi/lib/core/loaders/middlewares.js @@ -2,10 +2,8 @@ // Dependencies. const path = require('path'); -const fs = require('fs-extra'); const _ = require('lodash'); const glob = require('../../load/glob'); -const findPackagePath = require('../../load/package-path'); /** * Load middlewares @@ -23,12 +21,6 @@ module.exports = async function(strapi) { await loaders.loadInternalMiddlewares(middlewares); // local middleware await loaders.loadLocalMiddlewares(appPath, middlewares); - // plugins middlewares - await loaders.loadPluginsMiddlewares(strapi.plugins, middlewares); - // local plugin middlewares - await loaders.loadLocalPluginsMiddlewares(appPath, middlewares); - // load admin middlewares - await loaders.loadAdminMiddlewares(middlewares); return middlewares; }; @@ -55,45 +47,6 @@ const createLoaders = strapi => { const loadLocalMiddlewares = (appPath, middlewares) => loadMiddlewaresInDir(path.resolve(appPath, 'middlewares'), middlewares); - const loadPluginsMiddlewares = async (plugins, middlewares) => { - for (const pluginName in plugins) { - const pluginMiddlewares = plugins[pluginName].middlewares; - for (const middlewareName in pluginMiddlewares) { - middlewares[middlewareName] = { - loaded: false, - ...pluginMiddlewares[middlewareName], - }; - } - } - }; - - const loadLocalPluginsMiddlewares = async (appPath, middlewares) => { - const pluginsDir = path.resolve(appPath, 'plugins'); - if (!fs.existsSync(pluginsDir)) return; - - const pluginsNames = await fs.readdir(pluginsDir); - - for (let pluginFolder of pluginsNames) { - // ignore files - const stat = await fs.stat(path.resolve(pluginsDir, pluginFolder)); - if (!stat.isDirectory()) continue; - - const dir = path.resolve(pluginsDir, pluginFolder, 'middlewares'); - await loadMiddlewaresInDir(dir, middlewares); - } - }; - - const loadAdminMiddlewares = async middlewares => { - const middlewaresDir = 'middlewares'; - const dir = path.resolve(findPackagePath(`@strapi/admin`), middlewaresDir); - await loadMiddlewaresInDir(dir, middlewares); - - // load ee admin middlewares if they exist - if (process.env.STRAPI_DISABLE_EE !== 'true' && strapi.EE) { - await loadMiddlewaresInDir(`${dir}/../ee/${middlewaresDir}`, middlewares); - } - }; - const loadMiddlewareDependencies = async (packages, middlewares) => { for (let packageName of packages) { const baseDir = path.dirname(require.resolve(`@strapi/middleware-${packageName}`)); @@ -128,9 +81,6 @@ const createLoaders = strapi => { return { loadInternalMiddlewares, loadLocalMiddlewares, - loadPluginsMiddlewares, - loadLocalPluginsMiddlewares, loadMiddlewareDependencies, - loadAdminMiddlewares, }; }; diff --git a/packages/core/strapi/lib/core/loaders/plugins/get-enabled-plugins.js b/packages/core/strapi/lib/core/loaders/plugins/get-enabled-plugins.js index 534e30db83..352e204da5 100644 --- a/packages/core/strapi/lib/core/loaders/plugins/get-enabled-plugins.js +++ b/packages/core/strapi/lib/core/loaders/plugins/get-enabled-plugins.js @@ -76,6 +76,7 @@ const getEnabledPlugins = async strapi => { const userPluginsConfig = existsSync(userPluginConfigPath) ? loadConfigFile(userPluginConfigPath) : {}; + _.forEach(userPluginsConfig, (declaration, pluginName) => { validatePluginName(pluginName); declaredPlugins[pluginName] = toDetailedDeclaration(declaration); @@ -86,6 +87,7 @@ const getEnabledPlugins = async strapi => { p => !declaredPluginsResolves.includes(p.pathToPlugin), installedPlugins ); + const enabledPlugins = pipe( defaultsDeep(declaredPlugins), defaultsDeep(installedPluginsNotAlreadyUsed), diff --git a/packages/core/strapi/lib/core/loaders/plugins/index.js b/packages/core/strapi/lib/core/loaders/plugins/index.js index e6057b2a17..4d2a959fc4 100644 --- a/packages/core/strapi/lib/core/loaders/plugins/index.js +++ b/packages/core/strapi/lib/core/loaders/plugins/index.js @@ -87,6 +87,7 @@ const applyUserConfig = plugins => { const loadPlugins = async strapi => { const plugins = {}; + const enabledPlugins = await getEnabledPlugins(strapi); for (const pluginName in enabledPlugins) { @@ -94,6 +95,7 @@ const loadPlugins = async strapi => { const pluginServer = loadConfigFile(join(enabledPlugin.pathToPlugin, 'strapi-server.js')); plugins[pluginName] = defaultsDeep(defaultPlugin, pluginServer); } + // TODO: validate plugin format applyUserConfig(plugins); await applyUserExtension(plugins); diff --git a/packages/core/strapi/lib/core/registries/plugins.js b/packages/core/strapi/lib/core/registries/plugins.js index 1db3dcb2bd..d2c68bfb85 100644 --- a/packages/core/strapi/lib/core/registries/plugins.js +++ b/packages/core/strapi/lib/core/registries/plugins.js @@ -17,8 +17,8 @@ const pluginsRegistry = strapi => { throw new Error(`Plugin ${name} has already been registered.`); } - const moduleInstance = strapi.container.get('modules').add(`plugin::${name}`, pluginConfig); - plugins[name] = moduleInstance; + const pluginModule = strapi.container.get('modules').add(`plugin::${name}`, pluginConfig); + plugins[name] = pluginModule; return plugins[name]; }, diff --git a/packages/core/strapi/lib/middlewares/boom/index.js b/packages/core/strapi/lib/middlewares/boom/index.js index 5b8c866413..bdbe520d95 100644 --- a/packages/core/strapi/lib/middlewares/boom/index.js +++ b/packages/core/strapi/lib/middlewares/boom/index.js @@ -65,18 +65,18 @@ module.exports = strapi => { */ initialize() { - this.delegator = delegate(strapi.app.context, 'response'); + this.delegator = delegate(strapi.server.app.context, 'response'); this.createResponses(); strapi.errors = Boom; - strapi.app.use(async (ctx, next) => { + strapi.server.use(async (ctx, next) => { try { // App logic. await next(); } catch (error) { // emit error if configured if (strapi.config.get('server.emitErrors', false)) { - strapi.app.emit('error', error, ctx); + strapi.server.app.emit('error', error, ctx); } // Log error. @@ -92,10 +92,10 @@ module.exports = strapi => { } }); - strapi.app.use(async (ctx, next) => { + strapi.server.use(async (ctx, next) => { await next(); // Empty body is considered as `notFound` response. - if (_.isNil(ctx.body) && _.isNil(ctx.status)) { + if (_.isNil(ctx.body) && (_.isNil(ctx.status) || ctx.status === 404)) { ctx.notFound(); } }); @@ -104,7 +104,7 @@ module.exports = strapi => { // Custom function to avoid ctx.body repeat createResponses() { boomMethods.forEach(method => { - strapi.app.response[method] = function(msg, ...rest) { + strapi.server.app.response[method] = function(msg, ...rest) { const boomError = Boom[method](msg, ...rest) || {}; const { status, body } = formatBoomPayload(boomError); @@ -119,17 +119,17 @@ module.exports = strapi => { this.delegator.method(method); }); - strapi.app.response.send = function(data, status = 200) { + strapi.server.app.response.send = function(data, status = 200) { this.status = status; this.body = data; }; - strapi.app.response.created = function(data) { + strapi.server.app.response.created = function(data) { this.status = 201; this.body = data; }; - strapi.app.response.deleted = function(data) { + strapi.server.app.response.deleted = function(data) { if (_.isNil(data)) { this.status = 204; } else { diff --git a/packages/core/strapi/lib/middlewares/cors/index.js b/packages/core/strapi/lib/middlewares/cors/index.js index 23ead8118d..e79c69686a 100644 --- a/packages/core/strapi/lib/middlewares/cors/index.js +++ b/packages/core/strapi/lib/middlewares/cors/index.js @@ -30,7 +30,7 @@ module.exports = strapi => { keepHeadersOnError, } = Object.assign({}, defaults, strapi.config.get('middleware.settings.cors')); - strapi.app.use( + strapi.server.use( cors({ origin: async function(ctx) { let originList; diff --git a/packages/core/strapi/lib/middlewares/favicon/index.js b/packages/core/strapi/lib/middlewares/favicon/index.js index 82089bbcf2..cc8fb6e49a 100644 --- a/packages/core/strapi/lib/middlewares/favicon/index.js +++ b/packages/core/strapi/lib/middlewares/favicon/index.js @@ -22,7 +22,7 @@ module.exports = strapi => { const { dir } = strapi; const { maxAge, path: faviconPath } = strapi.config.middleware.settings.favicon; - strapi.app.use( + strapi.server.use( favicon(resolve(dir, faviconPath), { maxAge, }) diff --git a/packages/core/strapi/lib/middlewares/gzip/index.js b/packages/core/strapi/lib/middlewares/gzip/index.js index 39c8825607..479bba7523 100644 --- a/packages/core/strapi/lib/middlewares/gzip/index.js +++ b/packages/core/strapi/lib/middlewares/gzip/index.js @@ -13,7 +13,7 @@ module.exports = strapi => { initialize() { const { options = {} } = strapi.config.middleware.settings.gzip; - strapi.app.use(compress(options)); + strapi.server.use(compress(options)); }, }; }; diff --git a/packages/core/strapi/lib/middlewares/helmet/index.js b/packages/core/strapi/lib/middlewares/helmet/index.js index bdf2aea52e..f2a396ce04 100644 --- a/packages/core/strapi/lib/middlewares/helmet/index.js +++ b/packages/core/strapi/lib/middlewares/helmet/index.js @@ -4,6 +4,6 @@ const helmet = require('koa-helmet'); module.exports = strapi => ({ initialize() { - strapi.app.use(helmet(strapi.config.middleware.settings.helmet)); + strapi.server.use(helmet(strapi.config.middleware.settings.helmet)); }, }); diff --git a/packages/core/strapi/lib/middlewares/index.js b/packages/core/strapi/lib/middlewares/index.js index 12db49d074..506ed3734e 100644 --- a/packages/core/strapi/lib/middlewares/index.js +++ b/packages/core/strapi/lib/middlewares/index.js @@ -15,13 +15,13 @@ const requiredMiddlewares = [ 'favicon', ]; -module.exports = async function() { +module.exports = async function(strapi) { /** Utils */ - const middlewareConfig = this.config.middleware; + const middlewareConfig = strapi.config.middleware; // check if a middleware exists const middlewareExists = key => { - return !isUndefined(this.middleware[key]); + return !isUndefined(strapi.middleware[key]); }; // check if a middleware is enabled @@ -33,26 +33,26 @@ module.exports = async function() { }; // list of enabled middlewares - const enabledMiddlewares = Object.keys(this.middleware).filter(middlewareEnabled); + const enabledMiddlewares = Object.keys(strapi.middleware).filter(middlewareEnabled); // Method to initialize middlewares and emit an event. const initialize = middlewareKey => { - if (this.middleware[middlewareKey].loaded === true) return; + if (strapi.middleware[middlewareKey].loaded === true) return; - const module = this.middleware[middlewareKey].load; + const module = strapi.middleware[middlewareKey].load; return new Promise((resolve, reject) => { const timeout = setTimeout( () => reject(`(middleware: ${middlewareKey}) is taking too long to load.`), middlewareConfig.timeout || 1000 ); - this.middleware[middlewareKey] = merge(this.middleware[middlewareKey], module); + strapi.middleware[middlewareKey] = merge(strapi.middleware[middlewareKey], module); Promise.resolve() - .then(() => module.initialize()) + .then(() => module.initialize(strapi)) .then(() => { clearTimeout(timeout); - this.middleware[middlewareKey].loaded = true; + strapi.middleware[middlewareKey].loaded = true; resolve(); }) .catch(err => { @@ -72,9 +72,9 @@ module.exports = async function() { // Run beforeInitialize of every middleware await Promise.all( enabledMiddlewares.map(key => { - const { beforeInitialize } = this.middleware[key].load; + const { beforeInitialize } = strapi.middleware[key].load; if (typeof beforeInitialize === 'function') { - return beforeInitialize(); + return beforeInitialize(strapi); } }) ); diff --git a/packages/core/strapi/lib/middlewares/ip/index.js b/packages/core/strapi/lib/middlewares/ip/index.js index 938aa2bc62..6ca8c227d2 100644 --- a/packages/core/strapi/lib/middlewares/ip/index.js +++ b/packages/core/strapi/lib/middlewares/ip/index.js @@ -14,7 +14,7 @@ module.exports = strapi => { initialize() { const { whiteList, blackList } = strapi.config.middleware.settings.ip; - strapi.app.use( + strapi.server.use( ip({ whitelist: whiteList, blacklist: blackList, diff --git a/packages/core/strapi/lib/middlewares/language/index.js b/packages/core/strapi/lib/middlewares/language/index.js index 355c544556..7481391e5e 100644 --- a/packages/core/strapi/lib/middlewares/language/index.js +++ b/packages/core/strapi/lib/middlewares/language/index.js @@ -19,14 +19,14 @@ module.exports = strapi => { */ initialize() { - locale(strapi.app); + locale(strapi.server.app); const { defaultLocale, modes, cookieName } = strapi.config.middleware.settings.language; const directory = resolve(strapi.config.appPath, strapi.config.paths.config, 'locales'); - strapi.app.use( - i18n(strapi.app, { + strapi.server.use( + i18n(strapi.server.app, { directory, locales: strapi.config.get('middleware.settings.language.locales', []), defaultLocale, diff --git a/packages/core/strapi/lib/middlewares/logger/index.js b/packages/core/strapi/lib/middlewares/logger/index.js index b5c40ebe57..a5a05d9a7d 100644 --- a/packages/core/strapi/lib/middlewares/logger/index.js +++ b/packages/core/strapi/lib/middlewares/logger/index.js @@ -23,9 +23,9 @@ module.exports = strapi => { * Initialize the hook */ initialize() { - strapi.app.context.log = strapi.log; + strapi.server.app.context.log = strapi.log; - strapi.app.use(async (ctx, next) => { + strapi.server.use(async (ctx, next) => { const start = Date.now(); await next(); const delta = Math.ceil(Date.now() - start); diff --git a/packages/core/strapi/lib/middlewares/parser/index.js b/packages/core/strapi/lib/middlewares/parser/index.js index 27c4a3c0d1..b1a18526ee 100644 --- a/packages/core/strapi/lib/middlewares/parser/index.js +++ b/packages/core/strapi/lib/middlewares/parser/index.js @@ -37,7 +37,7 @@ module.exports = strapi => { * Initialize the hook */ initialize() { - strapi.app.use(async (ctx, next) => { + strapi.server.use(async (ctx, next) => { // disable for graphql // TODO: find a better way later if (ctx.url === '/graphql') { @@ -66,7 +66,10 @@ module.exports = strapi => { } }); - addQsParser(strapi.app, strapi.config.get('middleware.settings.parser.queryStringParser')); + addQsParser( + strapi.server.app, + strapi.config.get('middleware.settings.parser.queryStringParser') + ); }, }; }; diff --git a/packages/core/strapi/lib/middlewares/poweredBy/index.js b/packages/core/strapi/lib/middlewares/poweredBy/index.js index f0d8b6e82d..da5ceb4e42 100644 --- a/packages/core/strapi/lib/middlewares/poweredBy/index.js +++ b/packages/core/strapi/lib/middlewares/poweredBy/index.js @@ -3,7 +3,7 @@ module.exports = strapi => { return { initialize() { - strapi.app.use(async (ctx, next) => { + strapi.server.use(async (ctx, next) => { await next(); ctx.set( diff --git a/packages/core/strapi/lib/middlewares/public/index.js b/packages/core/strapi/lib/middlewares/public/index.js index 4b4561f707..e273850d5b 100644 --- a/packages/core/strapi/lib/middlewares/public/index.js +++ b/packages/core/strapi/lib/middlewares/public/index.js @@ -33,6 +33,7 @@ module.exports = strapi => { const serveIndexPage = async (ctx, next) => { // defer rendering of strapi index page await next(); + if (ctx.body != null || ctx.status !== 404) return; ctx.url = 'index.html'; @@ -61,38 +62,64 @@ module.exports = strapi => { ctx.body = body; }; - strapi.router.get('/', serveIndexPage); - strapi.router.get('/index.html', serveIndexPage); - strapi.router.get( - '/assets/images/(.*)', - serveStatic(path.resolve(__dirname, 'assets/images'), { maxage: maxAge, defer: true }) - ); + strapi.server.routes([ + { + method: 'GET', + path: '/', + handler: serveIndexPage, + }, + { + method: 'GET', + path: '/index.html', + handler: serveIndexPage, + }, + { + method: 'GET', + path: '/assets/images/(.*)', + handler: serveStatic(path.resolve(__dirname, 'assets/images'), { + maxage: maxAge, + defer: true, + }), + }, + { + method: 'GET', + path: '/(.*)', + handler: koaStatic(staticDir, { + maxage: maxAge, + defer: true, + }), + }, + ]); } - // serve files in public folder unless a sub router renders something else - strapi.router.get( - '/(.*)', - koaStatic(staticDir, { - maxage: maxAge, - defer: true, - }) - ); - if (!strapi.config.serveAdminPanel) return; const buildDir = path.resolve(strapi.dir, 'build'); - const serveAdmin = ctx => { + const serveAdmin = async (ctx, next) => { + await next(); + + if (ctx.method !== 'HEAD' && ctx.method !== 'GET') { + return; + } + + if (ctx.body != null || ctx.status !== 404) { + return; + } + ctx.type = 'html'; ctx.body = fs.createReadStream(path.join(buildDir + '/index.html')); }; - strapi.router.get( - `${strapi.config.admin.path}/*`, - serveStatic(buildDir, { maxage: maxAge, defer: false, index: 'index.html' }) - ); - - strapi.router.get(`${strapi.config.admin.path}`, serveAdmin); - strapi.router.get(`${strapi.config.admin.path}/*`, serveAdmin); + strapi.server.routes([ + { + method: 'GET', + path: `${strapi.config.admin.path}/:path*`, + handler: [ + serveAdmin, + serveStatic(buildDir, { maxage: maxAge, defer: false, index: 'index.html' }), + ], + }, + ]); }, }; }; diff --git a/packages/core/strapi/lib/middlewares/responseTime/index.js b/packages/core/strapi/lib/middlewares/responseTime/index.js index b2c2f9f09a..b019a2feaa 100644 --- a/packages/core/strapi/lib/middlewares/responseTime/index.js +++ b/packages/core/strapi/lib/middlewares/responseTime/index.js @@ -11,7 +11,7 @@ module.exports = strapi => { */ initialize() { - strapi.app.use(async (ctx, next) => { + strapi.server.use(async (ctx, next) => { const start = Date.now(); await next(); diff --git a/packages/core/strapi/lib/middlewares/responses/index.js b/packages/core/strapi/lib/middlewares/responses/index.js index f1f723d20b..5a26215a16 100644 --- a/packages/core/strapi/lib/middlewares/responses/index.js +++ b/packages/core/strapi/lib/middlewares/responses/index.js @@ -5,7 +5,7 @@ const _ = require('lodash'); module.exports = strapi => { return { initialize() { - strapi.app.use(async (ctx, next) => { + strapi.server.use(async (ctx, next) => { await next(); const responseFn = strapi.config.get(['functions', 'responses', ctx.status]); diff --git a/packages/core/strapi/lib/middlewares/router/index.js b/packages/core/strapi/lib/middlewares/router/index.js index c56b34e87e..837276608a 100644 --- a/packages/core/strapi/lib/middlewares/router/index.js +++ b/packages/core/strapi/lib/middlewares/router/index.js @@ -1,42 +1,67 @@ 'use strict'; -/** - * Module dependencies - */ - -// Public node modules. const _ = require('lodash'); -const Router = require('koa-router'); -const createEndpointComposer = require('./utils/compose-endpoint'); +const { toLower } = require('lodash/fp'); + +const createRouteScopeGenerator = namespace => route => { + const prefix = namespace.endsWith('::') ? namespace : `${namespace}.`; + + if (typeof route.handler === 'string') { + const [controller, action] = route.handler.split('.'); + + _.defaultsDeep(route.config, { + auth: { + scope: `${prefix}${controller}.${toLower(action)}`, + }, + }); + } +}; module.exports = strapi => { - const composeEndpoint = createEndpointComposer(strapi); - const registerAdminRoutes = () => { - const router = new Router({ prefix: '/admin' }); + const generateRouteScope = createRouteScopeGenerator(`admin::`); - for (const route of strapi.admin.routes) { - composeEndpoint(route, { pluginName: 'admin', router }); - } + strapi.admin.routes.forEach(route => { + generateRouteScope(route); + route.info = { pluginName: 'admin' }; + }); - strapi.app.use(router.routes()).use(router.allowedMethods()); + strapi.server.routes({ + type: 'admin', + prefix: '/admin', + routes: strapi.admin.routes, + }); }; const registerPluginRoutes = () => { for (const pluginName in strapi.plugins) { const plugin = strapi.plugins[pluginName]; - const router = new Router({ prefix: `/${pluginName}` }); + const generateRouteScope = createRouteScopeGenerator(`plugin::${pluginName}`); - for (const route of plugin.routes || []) { - const hasPrefix = _.has(route.config, 'prefix'); - composeEndpoint(route, { - pluginName, - router: hasPrefix ? strapi.router : router, + if (Array.isArray(plugin.routes)) { + plugin.routes.forEach(route => { + generateRouteScope(route); + route.info = { pluginName }; + }); + + strapi.server.routes({ + type: 'admin', + prefix: `/${pluginName}`, + routes: plugin.routes, + }); + } else { + _.forEach(plugin.routes, router => { + router.type = router.type || 'admin'; + router.prefix = `/${pluginName}`; + router.routes.forEach(route => { + generateRouteScope(route); + route.info = { pluginName }; + }); + + strapi.server.routes(router); }); } - - strapi.app.use(router.routes()).use(router.allowedMethods()); } }; @@ -44,28 +69,26 @@ module.exports = strapi => { for (const apiName in strapi.api) { const api = strapi.api[apiName]; - _.forEach(api.routes, routeInfo => { - // nested router - if (_.has(routeInfo, 'routes')) { - const router = new Router({ prefix: routeInfo.prefix }); + const generateRouteScope = createRouteScopeGenerator(`api::${apiName}`); - for (const route of routeInfo.routes || []) { - composeEndpoint(route, { apiName, router }); - } + _.forEach(api.routes, router => { + // TODO: remove once auth setup + // pass meta down to compose endpoint + router.type = 'content-api'; + router.routes.forEach(route => { + generateRouteScope(route); + route.info = { apiName }; + }); - strapi.router.use(router.routes()).use(router.allowedMethods()); - } else { - composeEndpoint(routeInfo, { apiName, router: strapi.router }); - } + return strapi.server.routes(router); }); } }; return { initialize() { - strapi.router.prefix(strapi.config.get('middleware.settings.router.prefix', '')); - registerAPIRoutes(); registerAdminRoutes(); + registerAPIRoutes(); registerPluginRoutes(); }, }; diff --git a/packages/core/strapi/lib/middlewares/router/utils/compose-endpoint.js b/packages/core/strapi/lib/middlewares/router/utils/compose-endpoint.js deleted file mode 100644 index ecf55001b3..0000000000 --- a/packages/core/strapi/lib/middlewares/router/utils/compose-endpoint.js +++ /dev/null @@ -1,169 +0,0 @@ -'use strict'; - -const _ = require('lodash'); -const compose = require('koa-compose'); -const { yup, policy: policyUtils } = require('@strapi/utils'); - -const policyOrMiddlewareSchema = yup.lazy(value => { - if (typeof value === 'string') { - return yup.string().required(); - } - - if (typeof value === 'function') { - return yup.mixed().isFunction(); - } - - return yup.object({ - name: yup.string().required(), - options: yup.object().notRequired(), // any options - }); -}); - -const routeSchema = yup.object({ - method: yup - .string() - .oneOf(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'ALL']) - .required(), - path: yup.string().required(), - handler: yup.lazy(value => { - if (typeof value === 'string') { - return yup.string().required(); - } - - return yup - .mixed() - .isFunction() - .required(); - }), - config: yup - .object({ - policies: yup - .array() - .of(policyOrMiddlewareSchema) - .notRequired(), - middlwares: yup - .array() - .of(policyOrMiddlewareSchema) - .notRequired(), - }) - .notRequired(), -}); - -const validateRouteConfig = routeConfig => { - try { - return routeSchema.validateSync(routeConfig, { - strict: true, - abortEarly: false, - stripUnknown: true, - }); - } catch (error) { - console.error(error); - throw new Error('Invalid route config'); - } -}; - -module.exports = strapi => { - const routerChecker = createRouteChecker(strapi); - - return (routeConfig, { pluginName, router, apiName }) => { - validateRouteConfig(routeConfig); - - try { - const middlewares = resolveMiddlewares(routeConfig); - - const { method, endpoint, policies, action } = routerChecker(routeConfig, { - pluginName, - apiName, - }); - - if (_.isUndefined(action) || !_.isFunction(action)) { - return strapi.log.warn( - `Ignored attempt to bind route '${routeConfig.method} ${routeConfig.path}' to unknown controller/action.` - ); - } - - router[method](endpoint, compose([...policies, ...middlewares, action])); - } catch (error) { - throw new Error( - `Error creating endpoint ${routeConfig.method} ${routeConfig.path}: ${error.message}` - ); - } - }; -}; - -const resolveMiddlewares = route => { - const middlewaresConfig = _.get(route, 'config.middlewares', []); - - return middlewaresConfig.map(middlewareConfig => { - if (typeof middlewareConfig === 'function') { - return middlewareConfig; - } - - const middleware = strapi.middleware(middlewareConfig); - - if (!middleware) { - throw new Error(`Middleware ${middlewareConfig} not found.`); - } - - return middleware; - }); -}; - -const getMethod = route => _.trim(_.toLower(route.method)); -const getEndpoint = route => _.trim(route.path); - -const createRouteChecker = strapi => { - return (value, { pluginName, apiName }) => { - const method = getMethod(value); - const endpoint = getEndpoint(value); - - // Define controller and action names. - const [controllerName, actionName] = _.trim(value.handler).split('.'); - const controllerKey = _.toLower(controllerName); - - let controller; - - if (pluginName) { - if (pluginName === 'admin') { - controller = strapi.admin.controllers[controllerKey]; - } else { - controller = strapi.plugin(pluginName).controller(controllerKey); - } - } else { - controller = strapi.container.get('controllers').get(`api::${apiName}.${controllerKey}`); - } - if (!_.isFunction(controller[actionName])) { - strapi.stopWithError( - `Error creating endpoint ${method} ${endpoint}: handler not found "${controllerKey}.${actionName}"` - ); - } - - const action = controller[actionName].bind(controller); - - const { bodyPolicy } = policyUtils; - - const globalPolicy = policyUtils.globalPolicy({ - controller: controllerKey, - action: actionName, - method, - endpoint, - plugin: pluginName, - }); - - const policyOption = _.get(value, 'config.policies', []); - - const routePolicies = policyOption.map(policyConfig => { - return policyUtils.get(policyConfig, { pluginName, apiName }); - }); - - // Init policies array. - const policies = [globalPolicy, ...routePolicies, bodyPolicy]; - - return { - method, - endpoint, - policies, - action, - }; - }; -}; diff --git a/packages/core/strapi/lib/middlewares/session/__tests__/session.test.js b/packages/core/strapi/lib/middlewares/session/__tests__/session.test.js index 08b1377f7a..01d253da61 100644 --- a/packages/core/strapi/lib/middlewares/session/__tests__/session.test.js +++ b/packages/core/strapi/lib/middlewares/session/__tests__/session.test.js @@ -27,9 +27,12 @@ describe('Session middleware', () => { ); const mockStrapi = { - app: { + server: { + app: { + use: jest.fn(), + context: {}, + }, use: jest.fn(), - context: {}, }, config: { appPath: __dirname, diff --git a/packages/core/strapi/lib/middlewares/session/index.js b/packages/core/strapi/lib/middlewares/session/index.js index 771ed8845e..c41db59ae3 100644 --- a/packages/core/strapi/lib/middlewares/session/index.js +++ b/packages/core/strapi/lib/middlewares/session/index.js @@ -93,7 +93,7 @@ module.exports = strapi => { return { initialize() { - strapi.app.keys = strapi.config.get('middleware.settings.session.secretKeys'); + strapi.server.app.keys = strapi.config.get('middleware.settings.session.secretKeys'); if ( _.has(strapi.config.middleware.settings.session, 'client') && @@ -112,8 +112,8 @@ module.exports = strapi => { strapi.config.middleware.settings.session ); - strapi.app.use(session(options, strapi.app)); - strapi.app.use((ctx, next) => { + strapi.server.use(session(options, strapi.server.app)); + strapi.server.use((ctx, next) => { ctx.state = ctx.state || {}; ctx.state.session = ctx.session || {}; @@ -127,8 +127,8 @@ module.exports = strapi => { ) { const options = _.assign(strapi.config.middleware.settings.session); - strapi.app.use(session(options, strapi.app)); - strapi.app.use((ctx, next) => { + strapi.server.use(session(options, strapi.server.app)); + strapi.server.use((ctx, next) => { ctx.state = ctx.state || {}; ctx.state.session = ctx.session || {}; diff --git a/packages/core/strapi/lib/services/content-api/index.js b/packages/core/strapi/lib/services/content-api/index.js new file mode 100644 index 0000000000..e500ea53d8 --- /dev/null +++ b/packages/core/strapi/lib/services/content-api/index.js @@ -0,0 +1,71 @@ +'use strict'; + +const { strict: assert } = require('assert'); +const { has } = require('lodash/fp'); + +class UnauthorizedError extends Error {} +class ForbiddenError extends Error {} + +const INVALID_STRATEGY_MSG = + 'Invalid auth strategy. Expecting an object with properties {name: string, authenticate: function, verify: function}'; + +const validStrategy = strategy => { + assert(has('name', strategy), INVALID_STRATEGY_MSG); + assert(typeof strategy.name === 'string', INVALID_STRATEGY_MSG); + + assert(has('authenticate', strategy), INVALID_STRATEGY_MSG); + assert(typeof strategy.authenticate === 'function', INVALID_STRATEGY_MSG); + + assert(has('verify', strategy), INVALID_STRATEGY_MSG); + assert(typeof strategy.verify === 'function', INVALID_STRATEGY_MSG); +}; + +const createAuthentication = () => { + const strategies = []; + + return { + register(strategy) { + validStrategy(strategy); + strategies.push(strategy); + + return () => { + strategies.splice(strategies.indexOf(strategy), 1); + }; + }, + async authenticate(ctx, next) { + for (const strategy of strategies) { + const result = await strategy.authenticate(ctx); + + const { authenticated = false, credentials } = result || {}; + + if (authenticated) { + ctx.state.auth = { strategy, credentials }; + return next(); + } + } + + return next(); + }, + async verify(auth, config = {}) { + if (config.public) { + return undefined; + } + + if (!auth) { + throw new UnauthorizedError(); + } + + return await auth.strategy.verify(auth, config); + }, + }; +}; + +module.exports = () => { + return { + auth: createAuthentication(), + errors: { + UnauthorizedError, + ForbiddenError, + }, + }; +}; diff --git a/packages/core/strapi/lib/services/metrics/__tests__/index.test.js b/packages/core/strapi/lib/services/metrics/__tests__/index.test.js index e9f3a8fec2..c94803878c 100644 --- a/packages/core/strapi/lib/services/metrics/__tests__/index.test.js +++ b/packages/core/strapi/lib/services/metrics/__tests__/index.test.js @@ -21,7 +21,7 @@ describe('metrics', () => { strapi: '0.0.0', }, }, - app: { + server: { use, }, }); @@ -43,7 +43,7 @@ describe('metrics', () => { strapi: '0.0.0', }, }, - app: { + server: { use, }, }); @@ -63,7 +63,7 @@ describe('metrics', () => { strapi: '0.0.0', }, }, - app: { + server: { use() {}, }, }); @@ -96,7 +96,7 @@ describe('metrics', () => { strapi: '0.0.0', }, }, - app: { + server: { use() {}, }, }); diff --git a/packages/core/strapi/lib/services/metrics/index.js b/packages/core/strapi/lib/services/metrics/index.js index 7fb7406855..7a9a92d715 100644 --- a/packages/core/strapi/lib/services/metrics/index.js +++ b/packages/core/strapi/lib/services/metrics/index.js @@ -34,7 +34,7 @@ const createTelemetryInstance = strapi => { const pingCron = scheduleJob('0 0 12 * * *', () => sendEvent('ping')); crons.push(pingCron); - strapi.app.use(createMiddleware({ sendEvent })); + strapi.server.use(createMiddleware({ sendEvent })); } if (strapi.EE === true && ee.isEE === true) { diff --git a/packages/core/strapi/lib/services/server/admin-api.js b/packages/core/strapi/lib/services/server/admin-api.js new file mode 100644 index 0000000000..23f5b6e7b8 --- /dev/null +++ b/packages/core/strapi/lib/services/server/admin-api.js @@ -0,0 +1,13 @@ +'use strict'; + +const { createAPI } = require('./api'); + +const createAdminAPI = strapi => { + const opts = { + prefix: '', // '/admin'; + }; + + return createAPI(strapi, opts); +}; + +module.exports = { createAdminAPI }; diff --git a/packages/core/strapi/lib/services/server/api.js b/packages/core/strapi/lib/services/server/api.js new file mode 100644 index 0000000000..02fadb0f1d --- /dev/null +++ b/packages/core/strapi/lib/services/server/api.js @@ -0,0 +1,32 @@ +'use strict'; + +const Router = require('@koa/router'); + +const { createRouteManager } = require('./routing'); + +const createAPI = (strapi, opts = {}) => { + const { prefix, defaultPolicies } = opts; + + const api = new Router({ prefix }); + + const routeManager = createRouteManager(strapi, { defaultPolicies }); + + return { + use(fn) { + api.use(fn); + return this; + }, + + routes(routes) { + routeManager.addRoutes(routes, api); + return this; + }, + + mount(router) { + router.use(api.routes(), api.allowedMethods()); + return this; + }, + }; +}; + +module.exports = { createAPI }; diff --git a/packages/core/strapi/lib/services/server/compose-endpoint.js b/packages/core/strapi/lib/services/server/compose-endpoint.js new file mode 100644 index 0000000000..97e65ba5d8 --- /dev/null +++ b/packages/core/strapi/lib/services/server/compose-endpoint.js @@ -0,0 +1,69 @@ +'use strict'; + +const { toLower, castArray, trim } = require('lodash/fp'); + +const compose = require('koa-compose'); +const { resolveMiddlewares } = require('./middleware'); +const { resolvePolicies } = require('./policy'); + +const getMethod = route => trim(toLower(route.method)); +const getPath = route => trim(route.path); + +const routeInfoMiddleware = route => (ctx, next) => { + ctx.state.route = route; + return next(); +}; + +module.exports = strapi => { + return (route, { pluginName, router, apiName }) => { + try { + const method = getMethod(route); + const path = getPath(route); + + const middlewares = resolveMiddlewares(route); + const policies = resolvePolicies(route, { pluginName, apiName }); + + const action = getAction(route, { pluginName, apiName }, strapi); + + const routeHandler = compose([ + routeInfoMiddleware(route), + ...policies, + ...middlewares, + ...castArray(action), + ]); + + router[method](path, routeHandler); + } catch (error) { + throw new Error(`Error creating endpoint ${route.method} ${route.path}: ${error.message}`); + } + }; +}; + +const getController = (name, { pluginName, apiName }, strapi) => { + if (pluginName) { + if (pluginName === 'admin') { + return strapi.controller(`admin::${name}`); + } + return strapi.plugin(pluginName).controller(name); + } else if (apiName) { + return strapi.controller(`api::${apiName}.${name}`); + } + + return strapi.controller(name); +}; + +const getAction = ({ handler }, { pluginName, apiName }, strapi) => { + if (Array.isArray(handler) || typeof handler === 'function') { + return handler; + } + + const [controllerName, actionName] = trim(handler).split('.'); + + const controller = getController(toLower(controllerName), { pluginName, apiName }, strapi); + + if (typeof controller[actionName] !== 'function') { + throw new Error(`Handler not found "${handler}"`); + } + + return controller[actionName].bind(controller); +}; diff --git a/packages/core/strapi/lib/services/server/content-api.js b/packages/core/strapi/lib/services/server/content-api.js new file mode 100644 index 0000000000..e1ec4a6fda --- /dev/null +++ b/packages/core/strapi/lib/services/server/content-api.js @@ -0,0 +1,52 @@ +'use strict'; + +const { prop } = require('lodash/fp'); +const { createAPI } = require('./api'); + +const getAuthConfig = prop('config.auth'); + +const createAuthPolicy = strapi => async (ctx, next) => { + const { auth, route } = ctx.state; + + if (!route) { + return ctx.unauthorized(); + } + + try { + await strapi.container.get('content-api').auth.verify(auth, getAuthConfig(route)); + + return next(); + } catch (error) { + const { errors } = strapi.container.get('content-api'); + + if (error instanceof errors.UnauthorizedError) { + return ctx.unauthorized(); + } + + if (error instanceof errors.ForbiddenError) { + return ctx.forbidden(); + } + + throw error; + } +}; + +const createContentAPI = strapi => { + const opts = { + prefix: strapi.config.get('api.prefix', '/api'), + defaultPolicies: [createAuthPolicy(strapi)], + }; + + const api = createAPI(strapi, opts); + + // implement auth providers + api.use((ctx, next) => { + return strapi.container.get('content-api').auth.authenticate(ctx, next); + }); + + return api; +}; + +module.exports = { + createContentAPI, +}; diff --git a/packages/core/strapi/lib/server.js b/packages/core/strapi/lib/services/server/http-server.js similarity index 100% rename from packages/core/strapi/lib/server.js rename to packages/core/strapi/lib/services/server/http-server.js diff --git a/packages/core/strapi/lib/services/server/index.js b/packages/core/strapi/lib/services/server/index.js new file mode 100644 index 0000000000..d5ae9453cb --- /dev/null +++ b/packages/core/strapi/lib/services/server/index.js @@ -0,0 +1,112 @@ +'use strict'; + +const Koa = require('koa'); +const Router = require('@koa/router'); + +const { createHTTPServer } = require('./http-server'); +const { createRouteManager } = require('./routing'); +const { createAdminAPI } = require('./admin-api'); +const { createContentAPI } = require('./content-api'); + +const healthCheck = async (ctx, next) => { + if (ctx.request.url === '/_health' && ['HEAD', 'GET'].includes(ctx.request.method)) { + ctx.set('strapi', 'You are so French!'); + ctx.status = 204; + } else { + await next(); + } +}; + +/** + * @typedef Server + * + * @property {Koa} app + * @property {http.Server} app + */ + +/** + * + * @param {Strapi} strapi + * @returns {Server} + */ +const createServer = strapi => { + const app = new Koa({ + proxy: strapi.config.get('server.proxy'), + }); + + const router = new Router({ + // FIXME: this prefix can break the admin if not specified in the admin url + prefix: strapi.config.get('middleware.settings.router.prefix', ''), + }); + + const routeManager = createRouteManager(strapi); + + const httpServer = createHTTPServer(strapi, app); + + const apis = { + 'content-api': createContentAPI(strapi), + admin: createAdminAPI(strapi), + }; + + // init health check + app.use(healthCheck); + + const state = { + mounted: false, + }; + + return { + app, + router, + httpServer, + + api(name) { + return apis[name]; + }, + + use(...args) { + app.use(...args); + return this; + }, + + routes(routes) { + if (routes.type) { + const api = apis[routes.type]; + if (!api) { + throw new Error(`API ${routes.type} not found. Possible APIs are ${Object.keys(apis)}`); + } + + apis[routes.type].routes(routes); + return this; + } + + routeManager.addRoutes(routes, router); + return this; + }, + + mount() { + state.mounted = true; + + Object.values(apis).forEach(api => api.mount(router)); + app.use(router.routes()).use(router.allowedMethods()); + + return this; + }, + + listen(...args) { + if (!state.mounted) { + this.mount(); + } + + return httpServer.listen(...args); + }, + + async destroy() { + await httpServer.destroy(); + }, + }; +}; + +module.exports = { + createServer, +}; diff --git a/packages/core/strapi/lib/services/server/middleware.js b/packages/core/strapi/lib/services/server/middleware.js new file mode 100644 index 0000000000..aaad682551 --- /dev/null +++ b/packages/core/strapi/lib/services/server/middleware.js @@ -0,0 +1,28 @@ +'use strict'; + +const { propOr } = require('lodash/fp'); + +const getMiddlewareConfig = propOr([], 'config.middlewares'); + +const resolveMiddlewares = route => { + const middlewaresConfig = getMiddlewareConfig(route); + + return middlewaresConfig.map(middlewareConfig => { + if (typeof middlewareConfig === 'function') { + return middlewareConfig; + } + + // TODO: this won't work until we have the new middleware formats + const middleware = strapi.middleware(middlewareConfig); + + if (!middleware) { + throw new Error(`Middleware ${middlewareConfig} not found.`); + } + + return middleware; + }); +}; + +module.exports = { + resolveMiddlewares, +}; diff --git a/packages/core/strapi/lib/services/server/policy.js b/packages/core/strapi/lib/services/server/policy.js new file mode 100644 index 0000000000..984f24bda1 --- /dev/null +++ b/packages/core/strapi/lib/services/server/policy.js @@ -0,0 +1,19 @@ +'use strict'; + +const { propOr } = require('lodash/fp'); +const policy = require('@strapi/utils/lib/policy'); + +const { bodyPolicy } = policy; + +const getPoliciesConfig = propOr([], 'config.policies'); + +const resolvePolicies = (route, opts = {}) => { + const policiesConfig = getPoliciesConfig(route); + + const policies = policiesConfig.map(policyName => policy.get(policyName, opts)); + return [...policies, bodyPolicy]; +}; + +module.exports = { + resolvePolicies, +}; diff --git a/packages/core/strapi/lib/services/server/routing.js b/packages/core/strapi/lib/services/server/routing.js new file mode 100644 index 0000000000..e696063845 --- /dev/null +++ b/packages/core/strapi/lib/services/server/routing.js @@ -0,0 +1,111 @@ +'use strict'; + +const Router = require('@koa/router'); +const _ = require('lodash'); +const { has } = require('lodash/fp'); +const { yup } = require('@strapi/utils'); + +const createEndpointComposer = require('./compose-endpoint'); + +const policyOrMiddlewareSchema = yup.lazy(value => { + if (typeof value === 'string') { + return yup.string().required(); + } + + if (typeof value === 'function') { + return yup.mixed().isFunction(); + } + + return yup.object({ + name: yup.string().required(), + options: yup.object().notRequired(), // any options + }); +}); + +const routeSchema = yup.object({ + method: yup + .string() + .oneOf(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'ALL']) + .required(), + path: yup.string().required(), + handler: yup.lazy(value => { + if (typeof value === 'string') { + return yup.string().required(); + } + + if (Array.isArray(value)) { + return yup.array().required(); + } + + return yup + .mixed() + .isFunction() + .required(); + }), + config: yup + .object({ + policies: yup + .array() + .of(policyOrMiddlewareSchema) + .notRequired(), + middlwares: yup + .array() + .of(policyOrMiddlewareSchema) + .notRequired(), + }) + .notRequired(), +}); + +const validateRouteConfig = routeConfig => { + try { + return routeSchema.validateSync(routeConfig, { + strict: true, + abortEarly: false, + stripUnknown: true, + }); + } catch (error) { + throw new Error('Invalid route config', error.message); + } +}; + +const createRouteManager = (strapi, opts = {}) => { + const composeEndpoint = createEndpointComposer(strapi); + + const createRoute = (route, router) => { + validateRouteConfig(route); + + if (opts.defaultPolicies) { + if (has('config.policies', route)) { + route.config.policies.unshift(...opts.defaultPolicies); + } else { + _.set(route, 'config.policies', [...opts.defaultPolicies]); + } + } + + composeEndpoint(route, { ...route.info, router }); + }; + + const addRoutes = (routes, router) => { + if (Array.isArray(routes)) { + routes.forEach(route => createRoute(route, router)); + } else if (routes.routes) { + const subRouter = new Router({ prefix: routes.prefix }); + + routes.routes.forEach(route => { + const hasPrefix = has('prefix', route.config); + createRoute(route, hasPrefix ? router : subRouter); + }); + + return router.use(subRouter.routes(), subRouter.allowedMethods()); + } + }; + + return { + addRoutes, + }; +}; + +module.exports = { + validateRouteConfig, + createRouteManager, +}; diff --git a/packages/core/strapi/package.json b/packages/core/strapi/package.json index 83b26d71d7..e180b0035d 100644 --- a/packages/core/strapi/package.json +++ b/packages/core/strapi/package.json @@ -14,10 +14,11 @@ }, "dependencies": { "@koa/cors": "^3.0.0", + "@koa/router": "10.1.1", "@strapi/admin": "3.6.8", "@strapi/database": "3.6.8", - "@strapi/generators": "3.6.8", "@strapi/generate-new": "3.6.8", + "@strapi/generators": "3.6.8", "@strapi/logger": "3.6.8", "@strapi/plugin-content-manager": "3.6.8", "@strapi/plugin-content-type-builder": "3.6.8", @@ -52,7 +53,7 @@ "koa-i18n": "^2.1.0", "koa-ip": "^2.0.0", "koa-locale": "~1.3.0", - "koa-router": "^7.4.0", + "koa-mount": "4.0.0", "koa-session": "^6.2.0", "koa-static": "^5.0.0", "lodash": "4.17.21", diff --git a/packages/core/strapi/tests/api/basic-compo-repeatable.test.e2e.js b/packages/core/strapi/tests/api/basic-compo-repeatable.test.e2e.js index 399913a8b5..8374ec0586 100644 --- a/packages/core/strapi/tests/api/basic-compo-repeatable.test.e2e.js +++ b/packages/core/strapi/tests/api/basic-compo-repeatable.test.e2e.js @@ -4,7 +4,7 @@ const _ = require('lodash'); const { createStrapiInstance } = require('../../../../../test/helpers/strapi'); const { createTestBuilder } = require('../../../../../test/helpers/builder'); -const { createAuthRequest } = require('../../../../../test/helpers/request'); +const { createContentAPIRequest } = require('../../../../../test/helpers/request'); const builder = createTestBuilder(); let strapi; @@ -56,7 +56,7 @@ describe('Core API - Basic + compo', () => { .build(); strapi = await createStrapiInstance(); - rq = await createAuthRequest({ strapi }); + rq = await createContentAPIRequest({ strapi }); }); afterAll(async () => { diff --git a/packages/core/strapi/tests/api/basic-compo.test.e2e.js b/packages/core/strapi/tests/api/basic-compo.test.e2e.js index 736801ccbb..a3f0d19015 100644 --- a/packages/core/strapi/tests/api/basic-compo.test.e2e.js +++ b/packages/core/strapi/tests/api/basic-compo.test.e2e.js @@ -4,7 +4,7 @@ const _ = require('lodash'); const { createStrapiInstance } = require('../../../../../test/helpers/strapi'); const { createTestBuilder } = require('../../../../../test/helpers/builder'); -const { createAuthRequest } = require('../../../../../test/helpers/request'); +const { createContentAPIRequest } = require('../../../../../test/helpers/request'); const builder = createTestBuilder(); let strapi; @@ -55,7 +55,7 @@ describe('Core API - Basic + compo', () => { .build(); strapi = await createStrapiInstance(); - rq = await createAuthRequest({ strapi }); + rq = await createContentAPIRequest({ strapi }); }); afterAll(async () => { diff --git a/packages/core/strapi/tests/api/basic-dp-compo-repeatable.test.e2e.js b/packages/core/strapi/tests/api/basic-dp-compo-repeatable.test.e2e.js index df9e414eb7..53d7c1af97 100644 --- a/packages/core/strapi/tests/api/basic-dp-compo-repeatable.test.e2e.js +++ b/packages/core/strapi/tests/api/basic-dp-compo-repeatable.test.e2e.js @@ -4,7 +4,7 @@ const _ = require('lodash'); const { createStrapiInstance } = require('../../../../../test/helpers/strapi'); const { createTestBuilder } = require('../../../../../test/helpers/builder'); -const { createAuthRequest } = require('../../../../../test/helpers/request'); +const { createContentAPIRequest } = require('../../../../../test/helpers/request'); const builder = createTestBuilder(); let strapi; @@ -57,7 +57,7 @@ describe('Core API - Basic + compo + draftAndPublish', () => { .build(); strapi = await createStrapiInstance(); - rq = await createAuthRequest({ strapi }); + rq = await createContentAPIRequest({ strapi }); }); afterAll(async () => { diff --git a/packages/core/strapi/tests/api/basic-dp-compo.test.e2e.js b/packages/core/strapi/tests/api/basic-dp-compo.test.e2e.js index de1ad29afb..17ed2958f6 100644 --- a/packages/core/strapi/tests/api/basic-dp-compo.test.e2e.js +++ b/packages/core/strapi/tests/api/basic-dp-compo.test.e2e.js @@ -4,7 +4,7 @@ const _ = require('lodash'); const { createStrapiInstance } = require('../../../../../test/helpers/strapi'); const { createTestBuilder } = require('../../../../../test/helpers/builder'); -const { createAuthRequest } = require('../../../../../test/helpers/request'); +const { createContentAPIRequest } = require('../../../../../test/helpers/request'); const builder = createTestBuilder(); let strapi; @@ -56,7 +56,7 @@ describe('Core API - Basic + compo + draftAndPublish', () => { .build(); strapi = await createStrapiInstance(); - rq = await createAuthRequest({ strapi }); + rq = await createContentAPIRequest({ strapi }); }); afterAll(async () => { diff --git a/packages/core/strapi/tests/api/basic-dp.test.e2e.js b/packages/core/strapi/tests/api/basic-dp.test.e2e.js index 1517ac2ad5..c994996977 100644 --- a/packages/core/strapi/tests/api/basic-dp.test.e2e.js +++ b/packages/core/strapi/tests/api/basic-dp.test.e2e.js @@ -4,7 +4,7 @@ const _ = require('lodash'); const { createStrapiInstance } = require('../../../../../test/helpers/strapi'); const { createTestBuilder } = require('../../../../../test/helpers/builder'); -const { createAuthRequest } = require('../../../../../test/helpers/request'); +const { createContentAPIRequest } = require('../../../../../test/helpers/request'); const builder = createTestBuilder(); let strapi; @@ -54,7 +54,7 @@ describe('Core API - Basic + draftAndPublish', () => { .build(); strapi = await createStrapiInstance(); - rq = await createAuthRequest({ strapi }); + rq = await createContentAPIRequest({ strapi }); }); afterAll(async () => { diff --git a/packages/core/strapi/tests/api/basic-dz.test.e2e.js b/packages/core/strapi/tests/api/basic-dz.test.e2e.js index c59e1943ba..cfd40d1097 100644 --- a/packages/core/strapi/tests/api/basic-dz.test.e2e.js +++ b/packages/core/strapi/tests/api/basic-dz.test.e2e.js @@ -4,7 +4,7 @@ const _ = require('lodash'); const { createStrapiInstance } = require('../../../../../test/helpers/strapi'); const { createTestBuilder } = require('../../../../../test/helpers/builder'); -const { createAuthRequest } = require('../../../../../test/helpers/request'); +const { createContentAPIRequest } = require('../../../../../test/helpers/request'); const builder = createTestBuilder(); let strapi; @@ -55,7 +55,7 @@ describe('Core API - Basic + dz', () => { .build(); strapi = await createStrapiInstance(); - rq = await createAuthRequest({ strapi }); + rq = await createContentAPIRequest({ strapi }); }); afterAll(async () => { diff --git a/packages/core/strapi/tests/api/basic.test.e2e.js b/packages/core/strapi/tests/api/basic.test.e2e.js index 294ea0a201..b6fa575a99 100644 --- a/packages/core/strapi/tests/api/basic.test.e2e.js +++ b/packages/core/strapi/tests/api/basic.test.e2e.js @@ -2,7 +2,7 @@ const { createStrapiInstance } = require('../../../../../test/helpers/strapi'); const { createTestBuilder } = require('../../../../../test/helpers/builder'); -const { createAuthRequest } = require('../../../../../test/helpers/request'); +const { createContentAPIRequest } = require('../../../../../test/helpers/request'); const builder = createTestBuilder(); let strapi; @@ -48,7 +48,7 @@ describe('Core API - Basic', () => { .build(); strapi = await createStrapiInstance(); - rq = await createAuthRequest({ strapi }); + rq = await createContentAPIRequest({ strapi }); }); afterAll(async () => { diff --git a/packages/core/strapi/tests/components/repeatable-not-required-min-max.test.e2e.js b/packages/core/strapi/tests/components/repeatable-not-required-min-max.test.e2e.js index a44b34d17c..48efeb3dbb 100644 --- a/packages/core/strapi/tests/components/repeatable-not-required-min-max.test.e2e.js +++ b/packages/core/strapi/tests/components/repeatable-not-required-min-max.test.e2e.js @@ -2,7 +2,7 @@ const { createTestBuilder } = require('../../../../../test/helpers/builder'); const { createStrapiInstance } = require('../../../../../test/helpers/strapi'); -const { createAuthRequest } = require('../../../../../test/helpers/request'); +const { createContentAPIRequest } = require('../../../../../test/helpers/request'); let strapi; let rq; @@ -40,8 +40,8 @@ describe('Non repeatable and Not required component', () => { .build(); strapi = await createStrapiInstance(); - rq = await createAuthRequest({ strapi }); - rq.setURLPrefix('/withcomponents'); + rq = await createContentAPIRequest({ strapi }); + rq.setURLPrefix('/api/withcomponents'); }); afterAll(async () => { diff --git a/packages/core/strapi/tests/components/repeatable-not-required.test.e2e.js b/packages/core/strapi/tests/components/repeatable-not-required.test.e2e.js index 2c355e8d2c..ac8b720b7e 100644 --- a/packages/core/strapi/tests/components/repeatable-not-required.test.e2e.js +++ b/packages/core/strapi/tests/components/repeatable-not-required.test.e2e.js @@ -2,7 +2,7 @@ const { createTestBuilder } = require('../../../../../test/helpers/builder'); const { createStrapiInstance } = require('../../../../../test/helpers/strapi'); -const { createAuthRequest } = require('../../../../../test/helpers/request'); +const { createContentAPIRequest } = require('../../../../../test/helpers/request'); let strapi; let rq; @@ -38,8 +38,8 @@ describe('Non repeatable and Not required component', () => { .build(); strapi = await createStrapiInstance(); - rq = await createAuthRequest({ strapi }); - rq.setURLPrefix('/withcomponents'); + rq = await createContentAPIRequest({ strapi }); + rq.setURLPrefix('/api/withcomponents'); }); afterAll(async () => { diff --git a/packages/core/strapi/tests/components/repeatable-required-min-max.test.e2e.js b/packages/core/strapi/tests/components/repeatable-required-min-max.test.e2e.js index 16971c8d3c..cd7a4d9ff9 100644 --- a/packages/core/strapi/tests/components/repeatable-required-min-max.test.e2e.js +++ b/packages/core/strapi/tests/components/repeatable-required-min-max.test.e2e.js @@ -2,7 +2,7 @@ const { createTestBuilder } = require('../../../../../test/helpers/builder'); const { createStrapiInstance } = require('../../../../../test/helpers/strapi'); -const { createAuthRequest } = require('../../../../../test/helpers/request'); +const { createContentAPIRequest } = require('../../../../../test/helpers/request'); let strapi; let rq; @@ -40,8 +40,8 @@ describe('Non repeatable and Not required component', () => { .build(); strapi = await createStrapiInstance(); - rq = await createAuthRequest({ strapi }); - rq.setURLPrefix('/withcomponents'); + rq = await createContentAPIRequest({ strapi }); + rq.setURLPrefix('/api/withcomponents'); }); afterAll(async () => { diff --git a/packages/core/strapi/tests/components/repeatable-required.test.e2e.js b/packages/core/strapi/tests/components/repeatable-required.test.e2e.js index e6db53b0b7..dffe75b9de 100644 --- a/packages/core/strapi/tests/components/repeatable-required.test.e2e.js +++ b/packages/core/strapi/tests/components/repeatable-required.test.e2e.js @@ -2,7 +2,7 @@ const { createTestBuilder } = require('../../../../../test/helpers/builder'); const { createStrapiInstance } = require('../../../../../test/helpers/strapi'); -const { createAuthRequest } = require('../../../../../test/helpers/request'); +const { createContentAPIRequest } = require('../../../../../test/helpers/request'); let strapi; let rq; @@ -38,8 +38,8 @@ describe('Non repeatable and Not required component', () => { .build(); strapi = await createStrapiInstance(); - rq = await createAuthRequest({ strapi }); - rq.setURLPrefix('/withcomponents'); + rq = await createContentAPIRequest({ strapi }); + rq.setURLPrefix('/api/withcomponents'); }); afterAll(async () => { diff --git a/packages/core/strapi/tests/components/single-not-required.test.e2e.js b/packages/core/strapi/tests/components/single-not-required.test.e2e.js index 1ed5132a54..9abae12ee7 100644 --- a/packages/core/strapi/tests/components/single-not-required.test.e2e.js +++ b/packages/core/strapi/tests/components/single-not-required.test.e2e.js @@ -2,7 +2,7 @@ const { createTestBuilder } = require('../../../../../test/helpers/builder'); const { createStrapiInstance } = require('../../../../../test/helpers/strapi'); -const { createAuthRequest } = require('../../../../../test/helpers/request'); +const { createContentAPIRequest } = require('../../../../../test/helpers/request'); let strapi; let rq; @@ -38,8 +38,8 @@ describe('Non repeatable and Not required component', () => { .build(); strapi = await createStrapiInstance(); - rq = await createAuthRequest({ strapi }); - rq.setURLPrefix('/withcomponents'); + rq = await createContentAPIRequest({ strapi }); + rq.setURLPrefix('/api/withcomponents'); }); afterAll(async () => { diff --git a/packages/core/strapi/tests/components/single-required.test.e2e.js b/packages/core/strapi/tests/components/single-required.test.e2e.js index ee3f7fc006..458d0f5a81 100644 --- a/packages/core/strapi/tests/components/single-required.test.e2e.js +++ b/packages/core/strapi/tests/components/single-required.test.e2e.js @@ -2,7 +2,7 @@ const { createTestBuilder } = require('../../../../../test/helpers/builder'); const { createStrapiInstance } = require('../../../../../test/helpers/strapi'); -const { createAuthRequest } = require('../../../../../test/helpers/request'); +const { createContentAPIRequest } = require('../../../../../test/helpers/request'); let strapi; let rq; @@ -38,8 +38,8 @@ describe('Non repeatable and required component', () => { .build(); strapi = await createStrapiInstance(); - rq = await createAuthRequest({ strapi }); - rq.setURLPrefix('/withcomponents'); + rq = await createContentAPIRequest({ strapi }); + rq.setURLPrefix('/api/withcomponents'); }); afterAll(async () => { diff --git a/packages/core/strapi/tests/deepFiltering.test.e2e.js b/packages/core/strapi/tests/deepFiltering.test.e2e.js index a0c85b825f..e8b6d9262e 100644 --- a/packages/core/strapi/tests/deepFiltering.test.e2e.js +++ b/packages/core/strapi/tests/deepFiltering.test.e2e.js @@ -4,7 +4,10 @@ const _ = require('lodash'); const { createStrapiInstance } = require('../../../../test/helpers/strapi'); const { createTestBuilder } = require('../../../../test/helpers/builder'); -const { createAuthRequest, transformToRESTResource } = require('../../../../test/helpers/request'); +const { + createContentAPIRequest, + transformToRESTResource, +} = require('../../../../test/helpers/request'); const builder = createTestBuilder(); const data = { @@ -89,7 +92,7 @@ describe('Deep Filtering API', () => { .build(); strapi = await createStrapiInstance(); - rq = await createAuthRequest({ strapi }); + rq = await createContentAPIRequest({ strapi }); Object.assign( data, diff --git a/packages/core/strapi/tests/dynamiczones/simple.test.e2e.js b/packages/core/strapi/tests/dynamiczones/simple.test.e2e.js index 4d69092927..6ebd5ec487 100644 --- a/packages/core/strapi/tests/dynamiczones/simple.test.e2e.js +++ b/packages/core/strapi/tests/dynamiczones/simple.test.e2e.js @@ -2,7 +2,7 @@ const { createTestBuilder } = require('../../../../../test/helpers/builder'); const { createStrapiInstance } = require('../../../../../test/helpers/strapi'); -const { createAuthRequest } = require('../../../../../test/helpers/request'); +const { createContentAPIRequest } = require('../../../../../test/helpers/request'); let strapi; let rq; @@ -87,8 +87,8 @@ describe('Not required dynamiczone', () => { .build(); strapi = await createStrapiInstance(); - rq = await createAuthRequest({ strapi }); - rq.setURLPrefix('/withdynamiczones'); + rq = await createContentAPIRequest({ strapi }); + rq.setURLPrefix('/api/withdynamiczones'); }); afterAll(async () => { diff --git a/packages/core/strapi/tests/dynamiczones/with-media.test.e2e.js b/packages/core/strapi/tests/dynamiczones/with-media.test.e2e.js index e097dd51f1..f80ead28cd 100644 --- a/packages/core/strapi/tests/dynamiczones/with-media.test.e2e.js +++ b/packages/core/strapi/tests/dynamiczones/with-media.test.e2e.js @@ -5,7 +5,7 @@ const path = require('path'); const { createTestBuilder } = require('../../../../../test/helpers/builder'); const { createStrapiInstance } = require('../../../../../test/helpers/strapi'); -const { createAuthRequest } = require('../../../../../test/helpers/request'); +const { createContentAPIRequest } = require('../../../../../test/helpers/request'); let strapi; let rq; @@ -77,10 +77,10 @@ describe('Not required dynamiczone', () => { strapi = await createStrapiInstance(); - baseRq = await createAuthRequest({ strapi }); + baseRq = await createContentAPIRequest({ strapi }); - rq = await createAuthRequest({ strapi }); - rq.setURLPrefix('/withdynamiczonemedias'); + rq = await createContentAPIRequest({ strapi }); + rq.setURLPrefix('/api/withdynamiczonemedias'); }); afterAll(async () => { diff --git a/packages/core/strapi/tests/endpoint.test.e2e.js b/packages/core/strapi/tests/endpoint.test.e2e.js index 6b7c747e61..da77d6c2c8 100644 --- a/packages/core/strapi/tests/endpoint.test.e2e.js +++ b/packages/core/strapi/tests/endpoint.test.e2e.js @@ -2,7 +2,7 @@ // Helpers. const { createStrapiInstance } = require('../../../../test/helpers/strapi'); -const { createAuthRequest } = require('../../../../test/helpers/request'); +const { createContentAPIRequest } = require('../../../../test/helpers/request'); const { createTestBuilder } = require('../../../../test/helpers/builder'); const modelsUtils = require('../../../../test/helpers/models'); @@ -29,7 +29,7 @@ describe('Create Strapi API End to End', () => { .build(); strapi = await createStrapiInstance(); - rq = await createAuthRequest({ strapi }); + rq = await createContentAPIRequest({ strapi }); }); afterAll(async () => { diff --git a/packages/core/strapi/tests/filtering.test.e2e.js b/packages/core/strapi/tests/filtering.test.e2e.js index afef7944cd..e3b7c0e7fa 100644 --- a/packages/core/strapi/tests/filtering.test.e2e.js +++ b/packages/core/strapi/tests/filtering.test.e2e.js @@ -4,7 +4,10 @@ const _ = require('lodash'); const { createStrapiInstance } = require('../../../../test/helpers/strapi'); const { createTestBuilder } = require('../../../../test/helpers/builder'); -const { createAuthRequest, transformToRESTResource } = require('../../../../test/helpers/request'); +const { + createContentAPIRequest, + transformToRESTResource, +} = require('../../../../test/helpers/request'); const builder = createTestBuilder(); let strapi; @@ -89,7 +92,7 @@ describe('Filtering API', () => { .build(); strapi = await createStrapiInstance(); - rq = await createAuthRequest({ strapi }); + rq = await createContentAPIRequest({ strapi }); Object.assign( data, diff --git a/packages/core/strapi/tests/publication-state.test.e2e.js b/packages/core/strapi/tests/publication-state.test.e2e.js index 8ad987472f..74cbf08d0c 100644 --- a/packages/core/strapi/tests/publication-state.test.e2e.js +++ b/packages/core/strapi/tests/publication-state.test.e2e.js @@ -1,7 +1,7 @@ 'use strict'; const { createStrapiInstance } = require('../../../../test/helpers/strapi'); -const { createAuthRequest } = require('../../../../test/helpers/request'); +const { createContentAPIRequest } = require('../../../../test/helpers/request'); const { createTestBuilder } = require('../../../../test/helpers/builder'); const builder = createTestBuilder(); @@ -152,7 +152,7 @@ describe('Publication State', () => { .build(); strapi = await createStrapiInstance(); - rq = await createAuthRequest({ strapi }); + rq = await createContentAPIRequest({ strapi }); Object.assign(data, builder.sanitizedFixtures(strapi)); }); diff --git a/packages/core/strapi/tests/search.test.e2e.js b/packages/core/strapi/tests/search.test.e2e.js index 431ef3ec25..f6f07a6e0d 100644 --- a/packages/core/strapi/tests/search.test.e2e.js +++ b/packages/core/strapi/tests/search.test.e2e.js @@ -3,7 +3,10 @@ // Test an API with all the possible filed types and simple filtering (no deep filtering, no relations) const { createStrapiInstance } = require('../../../../test/helpers/strapi'); const { createTestBuilder } = require('../../../../test/helpers/builder'); -const { createAuthRequest, transformToRESTResource } = require('../../../../test/helpers/request'); +const { + createContentAPIRequest, + transformToRESTResource, +} = require('../../../../test/helpers/request'); const builder = createTestBuilder(); let rq; @@ -127,7 +130,7 @@ describe('Search query', () => { .build(); strapi = await createStrapiInstance(); - rq = await createAuthRequest({ strapi }); + rq = await createContentAPIRequest({ strapi }); data.bed = builder.sanitizedFixturesFor(bedModel.name, strapi); }); diff --git a/packages/core/strapi/tests/single-type.test.e2e.js b/packages/core/strapi/tests/single-type.test.e2e.js index 0af2acbaa0..acf9f346e3 100644 --- a/packages/core/strapi/tests/single-type.test.e2e.js +++ b/packages/core/strapi/tests/single-type.test.e2e.js @@ -2,7 +2,7 @@ // Helpers. const { createStrapiInstance } = require('../../../../test/helpers/strapi'); -const { createAuthRequest } = require('../../../../test/helpers/request'); +const { createContentAPIRequest } = require('../../../../test/helpers/request'); const { createTestBuilder } = require('../../../../test/helpers/builder'); const builder = createTestBuilder(); @@ -26,7 +26,8 @@ describe('Content Manager single types', () => { await builder.addContentType(model).build(); strapi = await createStrapiInstance(); - rq = await createAuthRequest({ strapi }); + + rq = await createContentAPIRequest({ strapi }); }); afterAll(async () => { diff --git a/packages/core/upload/server/controllers/index.js b/packages/core/upload/server/controllers/index.js index f41009653e..48cb696a5f 100644 --- a/packages/core/upload/server/controllers/index.js +++ b/packages/core/upload/server/controllers/index.js @@ -1,7 +1,7 @@ 'use strict'; -const uploadController = require('./upload'); +const upload = require('./upload'); module.exports = { - upload: uploadController, + upload, }; diff --git a/packages/core/upload/server/controllers/upload.js b/packages/core/upload/server/controllers/upload.js index 17a9781553..2fe3754ce9 100644 --- a/packages/core/upload/server/controllers/upload.js +++ b/packages/core/upload/server/controllers/upload.js @@ -38,18 +38,11 @@ module.exports = { getSettings: resolveControllerMethod('getSettings'), async upload(ctx) { - const isUploadDisabled = _.get(strapi.plugins, 'upload.config.enabled', true) === false; - - if (isUploadDisabled) { - throw strapi.errors.badRequest(null, { - errors: [{ id: 'Upload.status.disabled', message: 'File upload is disabled' }], - }); - } - const { query: { id }, request: { files: { files } = {} }, } = ctx; + const controller = resolveController(ctx); if (id && (_.isEmpty(files) || files.size === 0)) { diff --git a/packages/core/upload/server/controllers/upload/api.js b/packages/core/upload/server/controllers/upload/api.js index ce6c604b7e..fb0bd4e114 100644 --- a/packages/core/upload/server/controllers/upload/api.js +++ b/packages/core/upload/server/controllers/upload/api.js @@ -7,7 +7,7 @@ const { getService } = require('../../utils'); const sanitize = (data, options = {}) => { return sanitizeEntity(data, { - model: strapi.getModel('file', 'upload'), + model: strapi.getModel('plugin::upload.file'), ...options, }); }; diff --git a/packages/core/upload/server/middlewares/upload.js b/packages/core/upload/server/middlewares/upload.js index 4dbe0bf266..8756833ccd 100644 --- a/packages/core/upload/server/middlewares/upload.js +++ b/packages/core/upload/server/middlewares/upload.js @@ -3,7 +3,6 @@ const { resolve } = require('path'); const range = require('koa-range'); const koaStatic = require('koa-static'); -const _ = require('lodash'); module.exports = { defaults: { upload: { enabled: true } }, @@ -15,7 +14,7 @@ module.exports = { ); const staticDir = resolve(strapi.dir, configPublicPath); - strapi.app.on('error', err => { + strapi.server.app.on('error', err => { if (err.code === 'EPIPE') { // when serving audio or video the browsers sometimes close the connection to go to range requests instead. // This causes koa to emit a write EPIPE error. We can ignore it. @@ -23,16 +22,18 @@ module.exports = { return; } - strapi.app.onerror(err); + strapi.server.app.onerror(err); }); - const localServerConfig = - _.get(strapi, 'plugins.upload.config.providerOptions.localServer') || {}; - strapi.router.get( - '/uploads/(.*)', - range, - koaStatic(staticDir, { defer: true, ...localServerConfig }) - ); + const localServerConfig = strapi.config.get('plugin.upload.providerOptions.localeServer', {}); + + strapi.server.routes([ + { + method: 'GET', + path: '/uploads/(.*)', + handler: [range, koaStatic(staticDir, { defer: true, ...localServerConfig })], + }, + ]); }, }, }; diff --git a/packages/core/upload/server/routes/admin.js b/packages/core/upload/server/routes/admin.js new file mode 100644 index 0000000000..ce63f86988 --- /dev/null +++ b/packages/core/upload/server/routes/admin.js @@ -0,0 +1,119 @@ +'use strict'; + +module.exports = { + type: 'admin', + routes: [ + { + method: 'GET', + path: '/settings', + handler: 'upload.getSettings', + config: { + policies: [ + 'admin::isAuthenticatedAdmin', + { + name: 'admin::hasPermissions', + options: { + actions: ['plugin::upload.settings.read'], + }, + }, + ], + }, + }, + { + method: 'PUT', + path: '/settings', + handler: 'upload.updateSettings', + config: { + policies: [ + 'admin::isAuthenticatedAdmin', + { + name: 'admin::hasPermissions', + options: { + actions: ['plugin::upload.settings.read'], + }, + }, + ], + }, + }, + { + method: 'POST', + path: '/', + handler: 'upload.upload', + config: { + policies: ['admin::isAuthenticatedAdmin'], + }, + }, + { + method: 'GET', + path: '/files/count', + handler: 'upload.count', + config: { + policies: [ + 'admin::isAuthenticatedAdmin', + { + name: 'admin::hasPermissions', + options: { + actions: ['plugin::upload.read'], + }, + }, + ], + }, + }, + { + method: 'GET', + path: '/files', + handler: 'upload.find', + config: { + policies: [ + 'admin::isAuthenticatedAdmin', + { + name: 'admin::hasPermissions', + options: { + actions: ['plugin::upload.read'], + }, + }, + ], + }, + }, + { + method: 'GET', + path: '/files/:id', + handler: 'upload.findOne', + config: { + policies: [ + 'admin::isAuthenticatedAdmin', + { + name: 'admin::hasPermissions', + options: { + actions: ['plugin::upload.read'], + }, + }, + ], + }, + }, + { + method: 'GET', + path: '/search/:id', + handler: 'upload.search', + config: { + policies: ['admin::isAuthenticatedAdmin'], + }, + }, + { + method: 'DELETE', + path: '/files/:id', + handler: 'upload.destroy', + config: { + policies: [ + 'admin::isAuthenticatedAdmin', + { + name: 'admin::hasPermissions', + options: { + actions: ['plugin::upload.assets.update'], + }, + }, + ], + }, + }, + ], +}; diff --git a/packages/core/upload/server/routes/content-api.js b/packages/core/upload/server/routes/content-api.js new file mode 100644 index 0000000000..3a9c0c809a --- /dev/null +++ b/packages/core/upload/server/routes/content-api.js @@ -0,0 +1,32 @@ +'use strict'; + +module.exports = { + type: 'content-api', + routes: [ + { + method: 'POST', + path: '/', + handler: 'upload.upload', + }, + { + method: 'GET', + path: '/files/count', + handler: 'upload.count', + }, + { + method: 'GET', + path: '/files', + handler: 'upload.find', + }, + { + method: 'GET', + path: '/files/:id', + handler: 'upload.findOne', + }, + { + method: 'DELETE', + path: '/files/:id', + handler: 'upload.destroy', + }, + ], +}; diff --git a/packages/core/upload/server/routes/index.js b/packages/core/upload/server/routes/index.js index 43a6305165..6939f2d012 100644 --- a/packages/core/upload/server/routes/index.js +++ b/packages/core/upload/server/routes/index.js @@ -1,98 +1,6 @@ 'use strict'; -module.exports = [ - { - method: 'GET', - path: '/settings', - handler: 'upload.getSettings', - config: { - policies: [], - }, - }, - { - method: 'PUT', - path: '/settings', - handler: 'upload.updateSettings', - config: { - policies: [], - }, - }, - { - method: 'POST', - path: '/', - handler: 'upload.upload', - config: { - policies: [], - description: 'upload a file', - tag: { - plugin: 'upload', - name: 'File', - }, - }, - }, - { - method: 'GET', - path: '/files/count', - handler: 'upload.count', - config: { - policies: [], - description: 'Retrieve the total number of uploaded files', - tag: { - plugin: 'upload', - name: 'File', - }, - }, - }, - { - method: 'GET', - path: '/files', - handler: 'upload.find', - config: { - policies: [], - description: 'Retrieve all file documents', - tag: { - plugin: 'upload', - name: 'File', - }, - }, - }, - { - method: 'GET', - path: '/files/:id', - handler: 'upload.findOne', - config: { - policies: [], - description: 'Retrieve a single file depending on its id', - tag: { - plugin: 'upload', - name: 'File', - }, - }, - }, - { - method: 'GET', - path: '/search/:id', - handler: 'upload.search', - config: { - policies: [], - description: 'Search for an uploaded file', - tag: { - plugin: 'upload', - name: 'File', - }, - }, - }, - { - method: 'DELETE', - path: '/files/:id', - handler: 'upload.destroy', - config: { - policies: [], - description: 'Delete an uploaded file', - tag: { - plugin: 'upload', - name: 'File', - }, - }, - }, -]; +module.exports = { + admin: require('./admin'), + 'content-api': require('./content-api'), +}; diff --git a/packages/core/upload/server/services/upload.js b/packages/core/upload/server/services/upload.js index 00c89d3c82..24916fe255 100644 --- a/packages/core/upload/server/services/upload.js +++ b/packages/core/upload/server/services/upload.js @@ -53,7 +53,7 @@ const combineFilters = params => { module.exports = ({ strapi }) => ({ emitEvent(event, data) { - const modelDef = strapi.getModel('file', 'upload'); + const modelDef = strapi.getModel('plugin::upload.file'); strapi.eventHub.emit(event, { media: sanitizeEntity(data, { model: modelDef }) }); }, diff --git a/packages/core/upload/strapi-server.js b/packages/core/upload/strapi-server.js index c0d2842ce0..08d4fcfdfb 100644 --- a/packages/core/upload/strapi-server.js +++ b/packages/core/upload/strapi-server.js @@ -6,15 +6,15 @@ const services = require('./server/services'); const routes = require('./server/routes'); const config = require('./server/config'); const controllers = require('./server/controllers'); +const middlewares = require('./server/middlewares'); module.exports = () => { return { - register: () => {}, bootstrap, config, routes, controllers, - middlewares: {}, + middlewares, contentTypes, policies: {}, services, diff --git a/packages/generators/generators/lib/templates/single-type-routes.js.hbs b/packages/generators/generators/lib/templates/single-type-routes.js.hbs index c231d228a0..d03c161944 100644 --- a/packages/generators/generators/lib/templates/single-type-routes.js.hbs +++ b/packages/generators/generators/lib/templates/single-type-routes.js.hbs @@ -18,7 +18,7 @@ module.exports = { }, { method: 'DELETE', - path: '/{{id}}/', + path: '/{{id}}', handler: '{{id}}.delete', config: { policies: [], diff --git a/packages/plugins/documentation/server/controllers/documentation.js b/packages/plugins/documentation/server/controllers/documentation.js index 46eb437a2f..ec72cd73a0 100644 --- a/packages/plugins/documentation/server/controllers/documentation.js +++ b/packages/plugins/documentation/server/controllers/documentation.js @@ -11,7 +11,6 @@ const path = require('path'); // Public dependencies. const fs = require('fs-extra'); -const cheerio = require('cheerio'); const _ = require('lodash'); const koaStatic = require('koa-static'); @@ -101,6 +100,9 @@ module.exports = { }, async loginView(ctx, next) { + // lazy require cheerio + const cheerio = require('cheerio'); + const { error } = ctx.query; try { diff --git a/packages/plugins/documentation/server/middlewares/documentation.js b/packages/plugins/documentation/server/middlewares/documentation.js index 1bf8248a72..43566dd984 100755 --- a/packages/plugins/documentation/server/middlewares/documentation.js +++ b/packages/plugins/documentation/server/middlewares/documentation.js @@ -2,7 +2,6 @@ const path = require('path'); const _ = require('lodash'); -const swaggerUi = require('swagger-ui-dist'); const koaStatic = require('koa-static'); const initialRoutes = []; @@ -17,6 +16,8 @@ module.exports = { }, initialize() { + const swaggerUi = require('swagger-ui-dist'); + // Find the plugins routes. strapi.plugins.documentation.routes = strapi.plugins.documentation.routes.map( (route, index) => { @@ -40,14 +41,20 @@ module.exports = { } ); - strapi.router.get('/plugins/documentation/*', async (ctx, next) => { - ctx.url = path.basename(ctx.url); + strapi.server.routes([ + { + method: 'GET', + path: '/plugins/documentation/(.*)', + handler: async (ctx, next) => { + ctx.url = path.basename(ctx.url); - return await koaStatic(swaggerUi.getAbsoluteFSPath(), { - maxage: strapi.config.middleware.settings.public.maxAge, - defer: true, - })(ctx, next); - }); + return await koaStatic(swaggerUi.getAbsoluteFSPath(), { + maxage: strapi.config.middleware.settings.public.maxAge, + defer: true, + })(ctx, next); + }, + }, + ]); }, }, }; diff --git a/packages/plugins/i18n/server/policies/validate-locale-creation.js b/packages/plugins/i18n/server/controllers/validate-locale-creation.js similarity index 100% rename from packages/plugins/i18n/server/policies/validate-locale-creation.js rename to packages/plugins/i18n/server/controllers/validate-locale-creation.js diff --git a/packages/plugins/i18n/server/middlewares/i18n/index.js b/packages/plugins/i18n/server/middlewares/i18n/index.js deleted file mode 100644 index 931eb0659a..0000000000 --- a/packages/plugins/i18n/server/middlewares/i18n/index.js +++ /dev/null @@ -1,30 +0,0 @@ -'use strict'; - -const { getOr, isMatch } = require('lodash/fp'); -const _ = require('lodash'); - -module.exports = { - defaults: { i18n: { enabled: true } }, - load: { - beforeInitialize() { - strapi.config.middleware.load.before.unshift('i18n'); - }, - initialize() { - const routes = strapi.plugin('content-manager').routes; - - const routesToAddPolicyTo = routes.filter( - route => - isMatch({ method: 'POST', path: '/collection-types/:model' }, route) || - isMatch({ method: 'PUT', path: '/single-types/:model' }, route) - ); - - routesToAddPolicyTo.forEach(route => { - const policies = getOr([], 'config.policies', route).concat( - 'plugin::i18n.validateLocaleCreation' - ); - - _.set(route, 'config.policies', policies); - }); - }, - }, -}; diff --git a/packages/plugins/i18n/server/middlewares/index.js b/packages/plugins/i18n/server/middlewares/index.js deleted file mode 100644 index 9c28c1f17a..0000000000 --- a/packages/plugins/i18n/server/middlewares/index.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -const i18n = require('./i18n'); - -module.exports = { - i18n, -}; diff --git a/packages/plugins/i18n/server/policies/index.js b/packages/plugins/i18n/server/policies/index.js deleted file mode 100644 index 5c1e2b9ca3..0000000000 --- a/packages/plugins/i18n/server/policies/index.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -const validateLocaleCreation = require('./validate-locale-creation'); - -module.exports = { - validateLocaleCreation, -}; diff --git a/packages/plugins/i18n/server/register.js b/packages/plugins/i18n/server/register.js index 0527299de3..6d7d5cfe54 100644 --- a/packages/plugins/i18n/server/register.js +++ b/packages/plugins/i18n/server/register.js @@ -3,6 +3,8 @@ const _ = require('lodash'); // const { PUBLISHED_AT_ATTRIBUTE } = require('@strapi/utils').contentTypes.constants; +const validateLocaleCreation = require('./controllers/validate-locale-creation'); + const { getService } = require('./utils'); // const fieldMigration = require('./migrations/field'); // const enableContentTypeMigration = require('./migrations/content-type/enable'); @@ -42,6 +44,22 @@ module.exports = strapi => { } }); + strapi.server.router.use('/content-manager/collection-types/:model', (ctx, next) => { + if (ctx.method === 'POST') { + return validateLocaleCreation(ctx, next); + } + + return next(); + }); + + strapi.server.router.use('/content-manager/single-types/:model', (ctx, next) => { + if (ctx.method === 'PUT') { + return validateLocaleCreation(ctx, next); + } + + return next(); + }); + // TODO: to implement // strapi.db.migrations.register(fieldMigration); // strapi.db.migrations.register(enableContentTypeMigration); diff --git a/packages/plugins/i18n/server/routes/admin.js b/packages/plugins/i18n/server/routes/admin.js new file mode 100644 index 0000000000..e9772b5680 --- /dev/null +++ b/packages/plugins/i18n/server/routes/admin.js @@ -0,0 +1,79 @@ +'use strict'; + +module.exports = { + type: 'admin', + routes: [ + { + method: 'GET', + path: '/iso-locales', + handler: 'iso-locales.listIsoLocales', + config: { + policies: [ + 'admin::isAuthenticatedAdmin', + { + name: 'plugin::content-manager.hasPermissions', + options: { actions: ['plugin::i18n.locale.read'] }, + }, + ], + }, + }, + { + method: 'GET', + path: '/locales', + handler: 'locales.listLocales', + config: { + policies: ['admin::isAuthenticatedAdmin'], + }, + }, + { + method: 'POST', + path: '/locales', + handler: 'locales.createLocale', + config: { + policies: [ + 'admin::isAuthenticatedAdmin', + { + name: 'plugin::content-manager.hasPermissions', + options: { actions: ['plugin::i18n.locale.create'] }, + }, + ], + }, + }, + { + method: 'PUT', + path: '/locales/:id', + handler: 'locales.updateLocale', + config: { + policies: [ + 'admin::isAuthenticatedAdmin', + { + name: 'plugin::content-manager.hasPermissions', + options: { actions: ['plugin::i18n.locale.update'] }, + }, + ], + }, + }, + { + method: 'DELETE', + path: '/locales/:id', + handler: 'locales.deleteLocale', + config: { + policies: [ + 'admin::isAuthenticatedAdmin', + { + name: 'plugin::content-manager.hasPermissions', + options: { actions: ['plugin::i18n.locale.delete'] }, + }, + ], + }, + }, + { + method: 'POST', + path: '/content-manager/actions/get-non-localized-fields', + handler: 'content-types.getNonLocalizedAttributes', + config: { + policies: ['admin::isAuthenticatedAdmin'], + }, + }, + ], +}; diff --git a/packages/plugins/i18n/server/routes/content-api.js b/packages/plugins/i18n/server/routes/content-api.js new file mode 100644 index 0000000000..b0f38404d2 --- /dev/null +++ b/packages/plugins/i18n/server/routes/content-api.js @@ -0,0 +1,12 @@ +'use strict'; + +module.exports = { + type: 'content-api', + routes: [ + { + method: 'GET', + path: '/locales', + handler: 'locales.listLocales', + }, + ], +}; diff --git a/packages/plugins/i18n/server/routes/index.js b/packages/plugins/i18n/server/routes/index.js index ddfb9140cd..6939f2d012 100644 --- a/packages/plugins/i18n/server/routes/index.js +++ b/packages/plugins/i18n/server/routes/index.js @@ -1,76 +1,6 @@ 'use strict'; -module.exports = [ - { - method: 'GET', - path: '/iso-locales', - handler: 'iso-locales.listIsoLocales', - config: { - policies: [ - 'admin::isAuthenticatedAdmin', - { - name: 'plugin::content-manager.hasPermissions', - options: { actions: ['plugin::i18n.locale.read'] }, - }, - ], - }, - }, - { - method: 'GET', - path: '/locales', - handler: 'locales.listLocales', - config: { - policies: ['admin::isAuthenticatedAdmin'], - }, - }, - { - method: 'POST', - path: '/locales', - handler: 'locales.createLocale', - config: { - policies: [ - 'admin::isAuthenticatedAdmin', - { - name: 'plugin::content-manager.hasPermissions', - options: { actions: ['plugin::i18n.locale.create'] }, - }, - ], - }, - }, - { - method: 'PUT', - path: '/locales/:id', - handler: 'locales.updateLocale', - config: { - policies: [ - 'admin::isAuthenticatedAdmin', - { - name: 'plugin::content-manager.hasPermissions', - options: { actions: ['plugin::i18n.locale.update'] }, - }, - ], - }, - }, - { - method: 'DELETE', - path: '/locales/:id', - handler: 'locales.deleteLocale', - config: { - policies: [ - 'admin::isAuthenticatedAdmin', - { - name: 'plugin::content-manager.hasPermissions', - options: { actions: ['plugin::i18n.locale.delete'] }, - }, - ], - }, - }, - { - method: 'POST', - path: '/content-manager/actions/get-non-localized-fields', - handler: 'content-types.getNonLocalizedAttributes', - config: { - policies: ['admin::isAuthenticatedAdmin'], - }, - }, -]; +module.exports = { + admin: require('./admin'), + 'content-api': require('./content-api'), +}; diff --git a/packages/plugins/i18n/server/services/entity-service-decorator.js b/packages/plugins/i18n/server/services/entity-service-decorator.js index 5c6ec7d72f..a7bbf045ee 100644 --- a/packages/plugins/i18n/server/services/entity-service-decorator.js +++ b/packages/plugins/i18n/server/services/entity-service-decorator.js @@ -18,7 +18,7 @@ const paramsContain = (key, params) => { * Adds default locale or replaces locale by locale in query params * @param {object} params - query params */ -// TODO: fix +// TODO: remove _locale const wrapParams = async (params = {}, ctx = {}) => { const { action } = ctx; diff --git a/packages/plugins/i18n/strapi-server.js b/packages/plugins/i18n/strapi-server.js index b8bfa6fcfd..0800cc0084 100644 --- a/packages/plugins/i18n/strapi-server.js +++ b/packages/plugins/i18n/strapi-server.js @@ -3,19 +3,15 @@ const bootstrap = require('./server/bootstrap'); const register = require('./server/register'); const contentTypes = require('./server/content-types'); -const policies = require('./server/policies'); const services = require('./server/services'); const routes = require('./server/routes'); const controllers = require('./server/controllers'); -const middlewares = require('./server/middlewares'); module.exports = () => ({ register, bootstrap, routes, controllers, - middlewares, contentTypes, - policies, services, }); diff --git a/packages/plugins/sentry/server/middlewares/sentry/index.js b/packages/plugins/sentry/server/middlewares/sentry/index.js index ccee1a06d1..56c90f3f1f 100644 --- a/packages/plugins/sentry/server/middlewares/sentry/index.js +++ b/packages/plugins/sentry/server/middlewares/sentry/index.js @@ -10,7 +10,7 @@ module.exports = { const sentry = strapi.plugin('sentry').service('sentry'); sentry.init(); - strapi.app.use(async (ctx, next) => { + strapi.server.use(async (ctx, next) => { try { await next(); } catch (error) { diff --git a/packages/plugins/users-permissions/.gitignore b/packages/plugins/users-permissions/.gitignore index 304c5bddad..697139034c 100644 --- a/packages/plugins/users-permissions/.gitignore +++ b/packages/plugins/users-permissions/.gitignore @@ -5,10 +5,9 @@ node_modules # writable files jwt.json -config/layout.json actions.json # Cruft .DS_Store npm-debug.log -.idea \ No newline at end of file +.idea diff --git a/packages/plugins/users-permissions/server/auth/strategy.js b/packages/plugins/users-permissions/server/auth/strategy.js new file mode 100644 index 0000000000..9f261f313b --- /dev/null +++ b/packages/plugins/users-permissions/server/auth/strategy.js @@ -0,0 +1,128 @@ +'use strict'; + +const { castArray, map } = require('lodash/fp'); + +const { getService } = require('../utils'); + +const getAdvancedSettings = () => { + return strapi + .store({ + environment: '', + type: 'plugin', + name: 'users-permissions', + }) + .get({ key: 'advanced' }); +}; + +const authenticate = async ctx => { + if (ctx.request && ctx.request.header && ctx.request.header.authorization) { + try { + const { id } = await getService('jwt').getToken(ctx); + + if (id === undefined) { + throw new Error('Invalid token: Token did not contain required fields'); + } + + // fetch authenticated user + const user = await getService('user').fetchAuthenticatedUser(id); + + if (user) { + return { + authenticated: true, + credentials: user, + }; + } + } catch (err) { + return { authenticated: false }; + } + + if (!ctx.state.user) { + return { authenticated: false }; + } + + const advancedSettings = await getAdvancedSettings(); + + if (advancedSettings.email_confirmation && !ctx.state.user.confirmed) { + return { authenticated: false }; + } + + if (ctx.state.user.blocked) { + return { authenticated: false }; + } + } + + const publicPermissions = await strapi.query('plugin::users-permissions.permission').findMany({ + where: { + role: { type: 'public' }, + }, + }); + + if (publicPermissions.length === 0) { + return { authenticated: false }; + } + + return { + authenticated: true, + credentials: null, + }; +}; + +const verify = async (auth, config) => { + const { errors } = strapi.container.get('content-api'); + + const { credentials: user } = auth; + + // public accesss + if (!user) { + // test against public role + const publicPermissions = await strapi.query('plugin::users-permissions.permission').findMany({ + where: { + role: { type: 'public' }, + }, + }); + + const allowedActions = map('action', publicPermissions); + + // A non authenticated user cannot access routes that do not have a scope + if (!config.scope) { + throw new errors.UnauthorizedError(); + } + + const isAllowed = castArray(config.scope).every(scope => allowedActions.includes(scope)); + + if (!isAllowed) { + throw new errors.ForbiddenError(); + } + + return; + } + + const permissions = await strapi.query('plugin::users-permissions.permission').findMany({ + where: { role: user.role.id }, + }); + + const allowedActions = map('action', permissions); + + // An authenticated user can access non scoped routes + if (!config.scope) { + return; + } + + const isAllowed = castArray(config.scope).every(scope => allowedActions.includes(scope)); + + if (!isAllowed) { + throw new errors.ForbiddenError(); + } + + // TODO: if we need to keep policies for u&p execution + // Execute the policies. + // if (permission.policy) { + // return await strapi.plugin('users-permissions').policy(permission.policy)(ctx, next); + // } +}; + +module.exports = { + name: 'users-permissions', + authenticate, + verify, +}; diff --git a/packages/plugins/users-permissions/server/content-types/index.js b/packages/plugins/users-permissions/server/content-types/index.js index 9d7d0af335..8476e7dd27 100644 --- a/packages/plugins/users-permissions/server/content-types/index.js +++ b/packages/plugins/users-permissions/server/content-types/index.js @@ -5,7 +5,7 @@ const role = require('./role'); const user = require('./user'); module.exports = { - permission, - role, - user, + permission: { schema: permission }, + role: { schema: role }, + user: { schema: user }, }; diff --git a/packages/plugins/users-permissions/server/content-types/permission/index.js b/packages/plugins/users-permissions/server/content-types/permission/index.js index fd1640d89b..fe6cf15d6c 100644 --- a/packages/plugins/users-permissions/server/content-types/permission/index.js +++ b/packages/plugins/users-permissions/server/content-types/permission/index.js @@ -1,7 +1,31 @@ 'use strict'; -const schema = require('./schema'); - module.exports = { - schema, + collectionName: 'up_permissions', + info: { + name: 'permission', + description: '', + singularName: 'permission', + pluralName: 'permissions', + displayName: 'Permission', + }, + pluginOptions: { + 'content-manager': { + visible: false, + }, + }, + attributes: { + action: { + type: 'string', + required: true, + configurable: false, + }, + role: { + type: 'relation', + relation: 'manyToOne', + target: 'plugin::users-permissions.role', + inversedBy: 'permissions', + configurable: false, + }, + }, }; diff --git a/packages/plugins/users-permissions/server/content-types/permission/schema.json b/packages/plugins/users-permissions/server/content-types/permission/schema.json deleted file mode 100644 index 9f640068d0..0000000000 --- a/packages/plugins/users-permissions/server/content-types/permission/schema.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "collectionName": "up_permissions", - "info": { - "name": "permission", - "description": "", - "singularName": "permission", - "pluralName": "permissions", - "displayName": "Permission" - }, - "pluginOptions": { - "content-manager": { - "visible": false - } - }, - "attributes": { - "type": { - "type": "string", - "required": true, - "configurable": false - }, - "controller": { - "type": "string", - "required": true, - "configurable": false - }, - "action": { - "type": "string", - "required": true, - "configurable": false - }, - "enabled": { - "type": "boolean", - "required": true, - "configurable": false - }, - "policy": { - "type": "string", - "configurable": false - }, - "role": { - "type": "relation", - "relation": "manyToOne", - "target": "plugin::users-permissions.role", - "inversedBy": "permissions", - "configurable": false - } - } -} diff --git a/packages/plugins/users-permissions/server/content-types/role/index.js b/packages/plugins/users-permissions/server/content-types/role/index.js index fd1640d89b..e6c2772679 100644 --- a/packages/plugins/users-permissions/server/content-types/role/index.js +++ b/packages/plugins/users-permissions/server/content-types/role/index.js @@ -1,7 +1,48 @@ 'use strict'; -const schema = require('./schema'); - module.exports = { - schema, + collectionName: 'up_roles', + info: { + name: 'role', + description: '', + singularName: 'role', + pluralName: 'roles', + displayName: 'Role', + }, + pluginOptions: { + 'content-manager': { + visible: false, + }, + }, + attributes: { + name: { + type: 'string', + minLength: 3, + required: true, + configurable: false, + }, + description: { + type: 'string', + configurable: false, + }, + type: { + type: 'string', + unique: true, + configurable: false, + }, + permissions: { + type: 'relation', + relation: 'oneToMany', + target: 'plugin::users-permissions.permission', + mappedBy: 'role', + configurable: false, + }, + users: { + type: 'relation', + relation: 'oneToMany', + target: 'plugin::users-permissions.user', + mappedBy: 'role', + configurable: false, + }, + }, }; diff --git a/packages/plugins/users-permissions/server/content-types/role/schema.json b/packages/plugins/users-permissions/server/content-types/role/schema.json deleted file mode 100644 index 5c9ff7a7af..0000000000 --- a/packages/plugins/users-permissions/server/content-types/role/schema.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "collectionName": "up_roles", - "info": { - "name": "role", - "description": "", - "singularName": "role", - "pluralName": "roles", - "displayName": "Role" - }, - "pluginOptions": { - "content-manager": { - "visible": false - } - }, - "attributes": { - "name": { - "type": "string", - "minLength": 3, - "required": true, - "configurable": false - }, - "description": { - "type": "string", - "configurable": false - }, - "type": { - "type": "string", - "unique": true, - "configurable": false - }, - "permissions": { - "type": "relation", - "relation": "oneToMany", - "target": "plugin::users-permissions.permission", - "mappedBy": "role", - "configurable": false - }, - "users": { - "type": "relation", - "relation": "oneToMany", - "target": "plugin::users-permissions.user", - "mappedBy": "role", - "configurable": false - } - } -} diff --git a/packages/plugins/users-permissions/server/content-types/user/index.js b/packages/plugins/users-permissions/server/content-types/user/index.js index 8b2c96a0ae..9f197f2a32 100644 --- a/packages/plugins/users-permissions/server/content-types/user/index.js +++ b/packages/plugins/users-permissions/server/content-types/user/index.js @@ -1,11 +1,72 @@ 'use strict'; -const schema = require('./schema'); const schemaConfig = require('./schema-config'); module.exports = { - schema: { - ...schema, - config: schemaConfig, // TODO: to handle differently for V4 + collectionName: 'up_users', + info: { + name: 'user', + description: '', + singularName: 'user', + pluralName: 'users', + displayName: 'User', }, + options: { + draftAndPublish: false, + timestamps: true, + }, + attributes: { + username: { + type: 'string', + minLength: 3, + unique: true, + configurable: false, + required: true, + }, + email: { + type: 'email', + minLength: 6, + configurable: false, + required: true, + }, + provider: { + type: 'string', + configurable: false, + }, + password: { + type: 'password', + minLength: 6, + configurable: false, + private: true, + }, + resetPasswordToken: { + type: 'string', + configurable: false, + private: true, + }, + confirmationToken: { + type: 'string', + configurable: false, + private: true, + }, + confirmed: { + type: 'boolean', + default: false, + configurable: false, + }, + blocked: { + type: 'boolean', + default: false, + configurable: false, + }, + role: { + type: 'relation', + relation: 'manyToOne', + target: 'plugin::users-permissions.role', + inversedBy: 'users', + configurable: false, + }, + }, + + config: schemaConfig, // TODO: to handle differently for V4 }; diff --git a/packages/plugins/users-permissions/server/content-types/user/schema.json b/packages/plugins/users-permissions/server/content-types/user/schema.json deleted file mode 100644 index 5c26f97ff6..0000000000 --- a/packages/plugins/users-permissions/server/content-types/user/schema.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "collectionName": "up_users", - "info": { - "name": "user", - "description": "", - "singularName": "user", - "pluralName": "users", - "displayName": "User" - }, - "options": { - "draftAndPublish": false, - "timestamps": true - }, - "attributes": { - "username": { - "type": "string", - "minLength": 3, - "unique": true, - "configurable": false, - "required": true - }, - "email": { - "type": "email", - "minLength": 6, - "configurable": false, - "required": true - }, - "provider": { - "type": "string", - "configurable": false - }, - "password": { - "type": "password", - "minLength": 6, - "configurable": false, - "private": true - }, - "resetPasswordToken": { - "type": "string", - "configurable": false, - "private": true - }, - "confirmationToken": { - "type": "string", - "configurable": false, - "private": true - }, - "confirmed": { - "type": "boolean", - "default": false, - "configurable": false - }, - "blocked": { - "type": "boolean", - "default": false, - "configurable": false - }, - "role": { - "type": "relation", - "relation": "manyToOne", - "target": "plugin::users-permissions.role", - "inversedBy": "users", - "configurable": false - } - } -} diff --git a/packages/plugins/users-permissions/server/controllers/auth.js b/packages/plugins/users-permissions/server/controllers/auth.js index 3c0b3775dc..d8d2a798b9 100644 --- a/packages/plugins/users-permissions/server/controllers/auth.js +++ b/packages/plugins/users-permissions/server/controllers/auth.js @@ -9,7 +9,6 @@ /* eslint-disable no-useless-escape */ const crypto = require('crypto'); const _ = require('lodash'); -const grant = require('grant-koa'); const { sanitizeEntity } = require('@strapi/utils'); const { getService } = require('../utils'); @@ -232,6 +231,8 @@ module.exports = { }, async connect(ctx, next) { + const grant = require('grant-koa'); + const grantConfig = await strapi .store({ environment: '', diff --git a/packages/plugins/users-permissions/server/controllers/index.js b/packages/plugins/users-permissions/server/controllers/index.js index bf9a6a5634..3cc4ba6bf5 100644 --- a/packages/plugins/users-permissions/server/controllers/index.js +++ b/packages/plugins/users-permissions/server/controllers/index.js @@ -1,11 +1,15 @@ 'use strict'; -const authController = require('./auth'); -const userController = require('./user'); -const usersPermissionsController = require('./users-permissions'); +const auth = require('./auth'); +const user = require('./user'); +const role = require('./role'); +const permissions = require('./permissions'); +const settings = require('./settings'); module.exports = { - auth: authController, - user: userController, - 'users-permissions': usersPermissionsController, + auth, + user, + role, + permissions, + settings, }; diff --git a/packages/plugins/users-permissions/server/controllers/permissions.js b/packages/plugins/users-permissions/server/controllers/permissions.js new file mode 100644 index 0000000000..3213215dd0 --- /dev/null +++ b/packages/plugins/users-permissions/server/controllers/permissions.js @@ -0,0 +1,26 @@ +'use strict'; + +const _ = require('lodash'); +const { getService } = require('../utils'); + +module.exports = { + async getPermissions(ctx) { + const permissions = await getService('users-permissions').getActions(); + + ctx.send({ permissions }); + }, + + async getPolicies(ctx) { + const policies = _.keys(strapi.plugin('users-permissions').policies); + + ctx.send({ + policies: _.without(policies, 'permissions'), + }); + }, + + async getRoutes(ctx) { + const routes = await getService('users-permissions').getRoutes(); + + ctx.send({ routes }); + }, +}; diff --git a/packages/plugins/users-permissions/server/controllers/role.js b/packages/plugins/users-permissions/server/controllers/role.js new file mode 100644 index 0000000000..edb235008b --- /dev/null +++ b/packages/plugins/users-permissions/server/controllers/role.js @@ -0,0 +1,77 @@ +'use strict'; + +const _ = require('lodash'); +const { getService } = require('../utils'); + +module.exports = { + /** + * Default action. + * + * @return {Object} + */ + async createRole(ctx) { + if (_.isEmpty(ctx.request.body)) { + return ctx.badRequest('Request body cannot be empty'); + } + + await getService('role').createRole(ctx.request.body); + + ctx.send({ ok: true }); + }, + + async getRole(ctx) { + const { id } = ctx.params; + const { lang } = ctx.query; + + const plugins = await getService('users-permissions').getPlugins(lang); + const role = await getService('role').getRole(id, plugins); + + if (!role) { + return ctx.notFound(); + } + + ctx.send({ role }); + }, + + async getRoles(ctx) { + const roles = await getService('role').getRoles(); + + ctx.send({ roles }); + }, + + async updateRole(ctx) { + const roleID = ctx.params.role; + + if (_.isEmpty(ctx.request.body)) { + return ctx.badRequest('Request body cannot be empty'); + } + + await getService('role').updateRole(roleID, ctx.request.body); + + ctx.send({ ok: true }); + }, + + async deleteRole(ctx) { + const roleID = ctx.params.role; + + if (!roleID) { + return ctx.badRequest(); + } + + // Fetch public role. + const publicRole = await strapi + .query('plugin::users-permissions.role') + .findOne({ where: { type: 'public' } }); + + const publicRoleID = publicRole.id; + + // Prevent from removing the public role. + if (roleID.toString() === publicRoleID.toString()) { + return ctx.badRequest('Cannot delete public role'); + } + + await getService('role').deleteRole(roleID, publicRoleID); + + ctx.send({ ok: true }); + }, +}; diff --git a/packages/plugins/users-permissions/server/controllers/settings.js b/packages/plugins/users-permissions/server/controllers/settings.js new file mode 100644 index 0000000000..603b6b806d --- /dev/null +++ b/packages/plugins/users-permissions/server/controllers/settings.js @@ -0,0 +1,118 @@ +'use strict'; + +const _ = require('lodash'); +const { getService } = require('../utils'); +const { isValidEmailTemplate } = require('./validation/email-template'); + +module.exports = { + async getEmailTemplate(ctx) { + ctx.send( + await strapi + .store({ + environment: '', + type: 'plugin', + name: 'users-permissions', + key: 'email', + }) + .get() + ); + }, + + async updateEmailTemplate(ctx) { + if (_.isEmpty(ctx.request.body)) { + return ctx.badRequest('Request body cannot be empty'); + } + + const emailTemplates = ctx.request.body['email-templates']; + + for (let key in emailTemplates) { + const template = emailTemplates[key].options.message; + + if (!isValidEmailTemplate(template)) { + return ctx.badRequest('Invalid template'); + } + } + + await strapi + .store({ + environment: '', + type: 'plugin', + name: 'users-permissions', + key: 'email', + }) + .set({ value: emailTemplates }); + + ctx.send({ ok: true }); + }, + + async getAdvancedSettings(ctx) { + const settings = await strapi + .store({ + environment: '', + type: 'plugin', + name: 'users-permissions', + key: 'advanced', + }) + .get(); + + const roles = await getService('role').getRoles(); + + ctx.send({ settings, roles }); + }, + + async updateAdvancedSettings(ctx) { + if (_.isEmpty(ctx.request.body)) { + return ctx.badRequest('Request body cannot be empty'); + } + + await strapi + .store({ + environment: '', + type: 'plugin', + name: 'users-permissions', + key: 'advanced', + }) + .set({ value: ctx.request.body }); + + ctx.send({ ok: true }); + }, + + async getProviders(ctx) { + const providers = await strapi + .store({ + environment: '', + type: 'plugin', + name: 'users-permissions', + key: 'grant', + }) + .get(); + + for (const provider in providers) { + if (provider !== 'email') { + providers[provider].redirectUri = strapi + .plugin('users-permissions') + .service('providers') + .buildRedirectUri(provider); + } + } + + ctx.send(providers); + }, + + async updateProviders(ctx) { + if (_.isEmpty(ctx.request.body)) { + return ctx.badRequest('Request body cannot be empty'); + } + + await strapi + .store({ + environment: '', + type: 'plugin', + name: 'users-permissions', + key: 'grant', + }) + .set({ value: ctx.request.body.providers }); + + ctx.send({ ok: true }); + }, +}; diff --git a/packages/plugins/users-permissions/server/controllers/user.js b/packages/plugins/users-permissions/server/controllers/user.js index 0198f58d91..7685f47271 100644 --- a/packages/plugins/users-permissions/server/controllers/user.js +++ b/packages/plugins/users-permissions/server/controllers/user.js @@ -62,7 +62,6 @@ module.exports = { data = sanitizeUser(data); } - // Send 200 `ok` ctx.body = data; }, @@ -86,21 +85,6 @@ module.exports = { ctx.send(sanitizeUser(data)); }, - async destroyAll(ctx) { - const { - request: { query }, - } = ctx; - - const toRemove = Object.values(_.omit(query, 'source')); - - // FIXME: delete many - const finalQuery = { id: toRemove }; - - const data = await getService('user').removeAll(finalQuery); - - ctx.send(data); - }, - /** * Retrieve authenticated user. * @return {Object|Array} @@ -109,7 +93,7 @@ module.exports = { const user = ctx.state.user; if (!user) { - return ctx.badRequest(null, [{ messages: [{ id: 'No authorization header was found' }] }]); + return ctx.badRequest('Unauthenticated request'); } ctx.body = sanitizeUser(user); diff --git a/packages/plugins/users-permissions/server/controllers/users-permissions.js b/packages/plugins/users-permissions/server/controllers/users-permissions.js deleted file mode 100644 index fbcf73e8ed..0000000000 --- a/packages/plugins/users-permissions/server/controllers/users-permissions.js +++ /dev/null @@ -1,271 +0,0 @@ -'use strict'; - -const _ = require('lodash'); -const { getService } = require('../utils'); -const { isValidEmailTemplate } = require('./validation/email-template'); - -module.exports = { - /** - * Default action. - * - * @return {Object} - */ - async createRole(ctx) { - if (_.isEmpty(ctx.request.body)) { - return ctx.badRequest(null, [{ messages: [{ id: 'Cannot be empty' }] }]); - } - - try { - await getService('users-permissions').createRole(ctx.request.body); - - ctx.send({ ok: true }); - } catch (err) { - strapi.log.error(err); - ctx.badRequest(null, [{ messages: [{ id: 'An error occured' }] }]); - } - }, - - async deleteRole(ctx) { - // Fetch public role. - const publicRole = await strapi - .query('plugin::users-permissions.role') - .findOne({ where: { type: 'public' } }); - - const publicRoleID = publicRole.id; - - const roleID = ctx.params.role; - - if (!roleID) { - return ctx.badRequest(null, [{ messages: [{ id: 'Bad request' }] }]); - } - - // Prevent from removing the public role. - if (roleID.toString() === publicRoleID.toString()) { - return ctx.badRequest(null, [{ messages: [{ id: 'Unauthorized' }] }]); - } - - try { - await getService('users-permissions').deleteRole(roleID, publicRoleID); - - ctx.send({ ok: true }); - } catch (err) { - strapi.log.error(err); - ctx.badRequest(null, [{ messages: [{ id: 'Bad request' }] }]); - } - }, - - async getPermissions(ctx) { - try { - const permissions = await getService('users-permissions').getActions(); - - ctx.send({ permissions }); - } catch (err) { - ctx.badRequest(null, [{ message: [{ id: 'Not Found' }] }]); - } - }, - - async getPolicies(ctx) { - const policies = _.keys(strapi.plugin('users-permissions').policies); - - ctx.send({ - policies: _.without(policies, 'permissions'), - }); - }, - - async getRole(ctx) { - const { id } = ctx.params; - const { lang } = ctx.query; - const plugins = await getService('users-permissions').getPlugins(lang); - const role = await getService('users-permissions').getRole(id, plugins); - - if (_.isEmpty(role)) { - return ctx.badRequest(null, [{ messages: [{ id: `Role don't exist` }] }]); - } - - ctx.send({ role }); - }, - - async getRoles(ctx) { - try { - const roles = await getService('users-permissions').getRoles(); - - ctx.send({ roles }); - } catch (err) { - ctx.badRequest(null, [{ messages: [{ id: 'Not found' }] }]); - } - }, - - async getRoutes(ctx) { - try { - const routes = await getService('users-permissions').getRoutes(); - - ctx.send({ routes }); - } catch (err) { - ctx.badRequest(null, [{ messages: [{ id: 'Not found' }] }]); - } - }, - - async index(ctx) { - // Send 200 `ok` - ctx.send({ message: 'ok' }); - }, - - async searchUsers(ctx) { - const { id } = ctx.params; - - const data = await strapi.query('plugin::users-permissions.user').custom(searchQueries)({ - id, - }); - - ctx.send(data); - }, - - async updateRole(ctx) { - const roleID = ctx.params.role; - - if (_.isEmpty(ctx.request.body)) { - return ctx.badRequest(null, [{ messages: [{ id: 'Bad request' }] }]); - } - - try { - await getService('users-permissions').updateRole(roleID, ctx.request.body); - - ctx.send({ ok: true }); - } catch (err) { - strapi.log.error(err); - ctx.badRequest(null, [{ messages: [{ id: 'An error occurred' }] }]); - } - }, - - async getEmailTemplate(ctx) { - ctx.send( - await strapi - .store({ - environment: '', - type: 'plugin', - name: 'users-permissions', - key: 'email', - }) - .get() - ); - }, - - async updateEmailTemplate(ctx) { - if (_.isEmpty(ctx.request.body)) { - return ctx.badRequest(null, [{ messages: [{ id: 'Cannot be empty' }] }]); - } - - const emailTemplates = ctx.request.body['email-templates']; - - for (let key in emailTemplates) { - const template = emailTemplates[key].options.message; - - if (!isValidEmailTemplate(template)) { - return ctx.badRequest(null, [{ messages: [{ id: 'Invalid template' }] }]); - } - } - - await strapi - .store({ - environment: '', - type: 'plugin', - name: 'users-permissions', - key: 'email', - }) - .set({ value: emailTemplates }); - - ctx.send({ ok: true }); - }, - - async getAdvancedSettings(ctx) { - ctx.send({ - settings: await strapi - .store({ - environment: '', - type: 'plugin', - name: 'users-permissions', - key: 'advanced', - }) - .get(), - roles: await getService('users-permissions').getRoles(), - }); - }, - - async updateAdvancedSettings(ctx) { - if (_.isEmpty(ctx.request.body)) { - return ctx.badRequest(null, [{ messages: [{ id: 'Cannot be empty' }] }]); - } - - await strapi - .store({ - environment: '', - type: 'plugin', - name: 'users-permissions', - key: 'advanced', - }) - .set({ value: ctx.request.body }); - - ctx.send({ ok: true }); - }, - - async getProviders(ctx) { - const providers = await strapi - .store({ - environment: '', - type: 'plugin', - name: 'users-permissions', - key: 'grant', - }) - .get(); - - for (const provider in providers) { - if (provider !== 'email') { - providers[provider].redirectUri = strapi - .plugin('users-permissions') - .service('providers') - .buildRedirectUri(provider); - } - } - - ctx.send(providers); - }, - - async updateProviders(ctx) { - if (_.isEmpty(ctx.request.body)) { - return ctx.badRequest(null, [{ messages: [{ id: 'Cannot be empty' }] }]); - } - - await strapi - .store({ - environment: '', - type: 'plugin', - name: 'users-permissions', - key: 'grant', - }) - .set({ value: ctx.request.body.providers }); - - ctx.send({ ok: true }); - }, -}; - -const searchQueries = { - bookshelf({ model }) { - return ({ id }) => { - return model - .query(function(qb) { - qb.where('username', 'LIKE', `%${id}%`).orWhere('email', 'LIKE', `%${id}%`); - }) - .fetchAll() - .then(results => results.toJSON()); - }; - }, - mongoose({ model }) { - return ({ id }) => { - const re = new RegExp(id); - - return model.find({ - $or: [{ username: re }, { email: re }], - }); - }; - }, -}; diff --git a/packages/plugins/users-permissions/server/index.js b/packages/plugins/users-permissions/server/index.js new file mode 100644 index 0000000000..620fa43a2a --- /dev/null +++ b/packages/plugins/users-permissions/server/index.js @@ -0,0 +1,21 @@ +'use strict'; + +const register = require('./register'); +const bootstrap = require('./bootstrap'); +const contentTypes = require('./content-types'); +const policies = require('./policies'); +const services = require('./services'); +const routes = require('./routes'); +const controllers = require('./controllers'); +const config = require('./config'); + +module.exports = () => ({ + register, + bootstrap, + config, + routes, + controllers, + contentTypes, + policies, + services, +}); diff --git a/packages/plugins/users-permissions/server/middlewares/index.js b/packages/plugins/users-permissions/server/middlewares/index.js deleted file mode 100644 index be5af2a555..0000000000 --- a/packages/plugins/users-permissions/server/middlewares/index.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -const usersPermissionsMiddleware = require('./users-permissions'); - -module.exports = { - 'users-permissions': usersPermissionsMiddleware, -}; diff --git a/packages/plugins/users-permissions/server/middlewares/users-permissions.js b/packages/plugins/users-permissions/server/middlewares/users-permissions.js deleted file mode 100644 index 872dbb7dfe..0000000000 --- a/packages/plugins/users-permissions/server/middlewares/users-permissions.js +++ /dev/null @@ -1,44 +0,0 @@ -'use strict'; - -const _ = require('lodash'); - -module.exports = { - defaults: { 'users-permissions': { enabled: true } }, - load: { - beforeInitialize() { - strapi.config.middleware.load.before.unshift('users-permissions'); - }, - - initialize() { - _.forEach(strapi.admin.routes, value => { - if (_.get(value.config, 'policies')) { - value.config.policies.unshift('plugin::users-permissions.permissions'); - } - }); - - _.forEach(strapi.api, api => { - _.forEach(api.routes, route => { - if (_.has(route, 'routes')) { - _.forEach(route.routes || [], route => { - if (_.get(route.config, 'policies')) { - route.config.policies.unshift('plugin::users-permissions.permissions'); - } - }); - } else if (_.get(route.config, 'policies')) { - route.config.policies.unshift('plugin::users-permissions.permissions'); - } - }); - }); - - if (strapi.plugins) { - _.forEach(strapi.plugins, plugin => { - _.forEach(plugin.routes, route => { - if (_.get(route.config, 'policies')) { - route.config.policies.unshift('plugin::users-permissions.permissions'); - } - }); - }); - } - }, - }, -}; diff --git a/packages/plugins/users-permissions/server/policies/index.js b/packages/plugins/users-permissions/server/policies/index.js index 332c5e182f..2abf96cbb1 100644 --- a/packages/plugins/users-permissions/server/policies/index.js +++ b/packages/plugins/users-permissions/server/policies/index.js @@ -1,11 +1,7 @@ 'use strict'; -const isAuthenticated = require('./isAuthenticated'); -const permissions = require('./permissions'); const rateLimit = require('./rateLimit'); module.exports = { - isAuthenticated, - permissions, rateLimit, }; diff --git a/packages/plugins/users-permissions/server/policies/isAuthenticated.js b/packages/plugins/users-permissions/server/policies/isAuthenticated.js deleted file mode 100644 index b0a9636817..0000000000 --- a/packages/plugins/users-permissions/server/policies/isAuthenticated.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -module.exports = async (ctx, next) => { - if (!ctx.state.user) { - return ctx.unauthorized(); - } - - await next(); -}; diff --git a/packages/plugins/users-permissions/server/policies/permissions.js b/packages/plugins/users-permissions/server/policies/permissions.js deleted file mode 100644 index 5fa495cb67..0000000000 --- a/packages/plugins/users-permissions/server/policies/permissions.js +++ /dev/null @@ -1,94 +0,0 @@ -'use strict'; - -const _ = require('lodash'); -const { getService } = require('../utils'); - -module.exports = async (ctx, next) => { - let role; - - if (ctx.state.user) { - // request is already authenticated in a different way - return next(); - } - - if (ctx.request && ctx.request.header && ctx.request.header.authorization) { - try { - const { id } = await getService('jwt').getToken(ctx); - - if (id === undefined) { - throw new Error('Invalid token: Token did not contain required fields'); - } - - // fetch authenticated user - ctx.state.user = await getService('user').fetchAuthenticatedUser(id); - } catch (err) { - return handleErrors(ctx, err, 'unauthorized'); - } - - if (!ctx.state.user) { - return handleErrors(ctx, 'User Not Found', 'unauthorized'); - } - - role = ctx.state.user.role; - - if (role.type === 'root') { - return await next(); - } - - const store = await strapi.store({ - environment: '', - type: 'plugin', - name: 'users-permissions', - }); - - if ( - _.get(await store.get({ key: 'advanced' }), 'email_confirmation') && - !ctx.state.user.confirmed - ) { - return handleErrors(ctx, 'Your account email is not confirmed.', 'unauthorized'); - } - - if (ctx.state.user.blocked) { - return handleErrors( - ctx, - 'Your account has been blocked by the administrator.', - 'unauthorized' - ); - } - } - - // Retrieve `public` role. - if (!role) { - role = await strapi - .query('plugin::users-permissions.role') - .findOne({ where: { type: 'public' } }); - } - - const route = ctx.request.route; - - const permission = await strapi.query('plugin::users-permissions.permission').findOne({ - where: { - role: { id: role.id }, - type: route.plugin || 'application', - controller: route.controller, - action: route.action, - enabled: true, - }, - }); - - if (!permission) { - return handleErrors(ctx, undefined, 'forbidden'); - } - - // Execute the policies. - if (permission.policy) { - return await strapi.plugin('users-permissions').policy(permission.policy)(ctx, next); - } - - // Execute the action. - await next(); -}; - -const handleErrors = (ctx, err = undefined, type) => { - throw strapi.errors[type](err); -}; diff --git a/packages/plugins/users-permissions/server/policies/rateLimit.js b/packages/plugins/users-permissions/server/policies/rateLimit.js index 29f23ab94e..6767225f75 100644 --- a/packages/plugins/users-permissions/server/policies/rateLimit.js +++ b/packages/plugins/users-permissions/server/policies/rateLimit.js @@ -1,12 +1,8 @@ 'use strict'; -const lazyRateLimit = { - get RateLimit() { - return require('koa2-ratelimit').RateLimit; - }, -}; - module.exports = async (ctx, next) => { + const ratelimit = require('koa2-ratelimit').RateLimit; + const message = [ { messages: [ @@ -18,7 +14,7 @@ module.exports = async (ctx, next) => { }, ]; - return lazyRateLimit.RateLimit.middleware( + return ratelimit.middleware( Object.assign( {}, { diff --git a/packages/plugins/users-permissions/server/register.js b/packages/plugins/users-permissions/server/register.js new file mode 100644 index 0000000000..c6454c832c --- /dev/null +++ b/packages/plugins/users-permissions/server/register.js @@ -0,0 +1,7 @@ +'use strict'; + +const authStrategy = require('./auth/strategy'); + +module.exports = strapi => { + strapi.container.get('content-api').auth.register(authStrategy); +}; diff --git a/packages/plugins/users-permissions/server/routes/admin/index.js b/packages/plugins/users-permissions/server/routes/admin/index.js new file mode 100644 index 0000000000..3ae910ebcb --- /dev/null +++ b/packages/plugins/users-permissions/server/routes/admin/index.js @@ -0,0 +1,10 @@ +'use strict'; + +const permissionsRoutes = require('./permissions'); +const settingsRoutes = require('./settings'); +const roleRoutes = require('./role'); + +module.exports = { + type: 'admin', + routes: [...roleRoutes, ...settingsRoutes, ...permissionsRoutes], +}; diff --git a/packages/plugins/users-permissions/server/routes/admin/permissions.js b/packages/plugins/users-permissions/server/routes/admin/permissions.js new file mode 100644 index 0000000000..7ad462f952 --- /dev/null +++ b/packages/plugins/users-permissions/server/routes/admin/permissions.js @@ -0,0 +1,20 @@ +'use strict'; + +module.exports = [ + { + method: 'GET', + path: '/permissions', + handler: 'permissions.getPermissions', + }, + { + method: 'GET', + path: '/policies', + handler: 'permissions.getPolicies', + }, + + { + method: 'GET', + path: '/routes', + handler: 'permissions.getRoutes', + }, +]; diff --git a/packages/plugins/users-permissions/server/routes/admin/role.js b/packages/plugins/users-permissions/server/routes/admin/role.js new file mode 100644 index 0000000000..e1b56d0d7b --- /dev/null +++ b/packages/plugins/users-permissions/server/routes/admin/role.js @@ -0,0 +1,79 @@ +'use strict'; + +module.exports = [ + { + method: 'GET', + path: '/roles/:id', + handler: 'role.getRole', + config: { + policies: [ + { + name: 'admin::hasPermissions', + options: { + actions: ['plugin::users-permissions.roles.read'], + }, + }, + ], + }, + }, + { + method: 'GET', + path: '/roles', + handler: 'role.getRoles', + config: { + policies: [ + { + name: 'admin::hasPermissions', + options: { + actions: ['plugin::users-permissions.roles.read'], + }, + }, + ], + }, + }, + { + method: 'POST', + path: '/roles', + handler: 'role.createRole', + config: { + policies: [ + { + name: 'admin::hasPermissions', + options: { + actions: ['plugin::users-permissions.roles.create'], + }, + }, + ], + }, + }, + { + method: 'PUT', + path: '/roles/:role', + handler: 'role.updateRole', + config: { + policies: [ + { + name: 'admin::hasPermissions', + options: { + actions: ['plugin::users-permissions.roles.update'], + }, + }, + ], + }, + }, + { + method: 'DELETE', + path: '/roles/:role', + handler: 'role.deleteRole', + config: { + policies: [ + { + name: 'admin::hasPermissions', + options: { + actions: ['plugin::users-permissions.roles.delete'], + }, + }, + ], + }, + }, +]; diff --git a/packages/plugins/users-permissions/server/routes/admin/settings.js b/packages/plugins/users-permissions/server/routes/admin/settings.js new file mode 100644 index 0000000000..6af9a4f99d --- /dev/null +++ b/packages/plugins/users-permissions/server/routes/admin/settings.js @@ -0,0 +1,95 @@ +'use strict'; + +module.exports = [ + { + method: 'GET', + path: '/email-templates', + handler: 'settings.getEmailTemplate', + config: { + policies: [ + { + name: 'admin::hasPermissions', + options: { + actions: ['plugin::users-permissions.email-templates.read'], + }, + }, + ], + }, + }, + { + method: 'PUT', + path: '/email-templates', + handler: 'settings.updateEmailTemplate', + config: { + policies: [ + { + name: 'admin::hasPermissions', + options: { + actions: ['plugin::users-permissions.email-templates.update'], + }, + }, + ], + }, + }, + { + method: 'GET', + path: '/advanced', + handler: 'settings.getAdvancedSettings', + config: { + policies: [ + { + name: 'admin::hasPermissions', + options: { + actions: ['plugin::users-permissions.advanced-settings.read'], + }, + }, + ], + }, + }, + { + method: 'PUT', + path: '/advanced', + handler: 'settings.updateAdvancedSettings', + config: { + policies: [ + { + name: 'admin::hasPermissions', + options: { + actions: ['plugin::users-permissions.advanced-settings.update'], + }, + }, + ], + }, + }, + { + method: 'GET', + path: '/providers', + handler: 'settings.getProviders', + config: { + policies: [ + { + name: 'admin::hasPermissions', + options: { + actions: ['plugin::users-permissions.providers.read'], + }, + }, + ], + }, + }, + + { + method: 'PUT', + path: '/providers', + handler: 'settings.updateProviders', + config: { + policies: [ + { + name: 'admin::hasPermissions', + options: { + actions: ['plugin::users-permissions.providers.update'], + }, + }, + ], + }, + }, +]; diff --git a/packages/plugins/users-permissions/server/routes/content-api/auth.js b/packages/plugins/users-permissions/server/routes/content-api/auth.js new file mode 100644 index 0000000000..43a09dca5b --- /dev/null +++ b/packages/plugins/users-permissions/server/routes/content-api/auth.js @@ -0,0 +1,73 @@ +'use strict'; + +module.exports = [ + { + method: 'GET', + path: '/connect/(.*)', + handler: 'auth.connect', + config: { + policies: ['plugin::users-permissions.rateLimit'], + prefix: '', + }, + }, + { + method: 'POST', + path: '/auth/local', + handler: 'auth.callback', + config: { + policies: ['plugin::users-permissions.rateLimit'], + prefix: '', + }, + }, + { + method: 'POST', + path: '/auth/local/register', + handler: 'auth.register', + config: { + policies: ['plugin::users-permissions.rateLimit'], + prefix: '', + }, + }, + { + method: 'GET', + path: '/auth/:provider/callback', + handler: 'auth.callback', + config: { + prefix: '', + }, + }, + { + method: 'POST', + path: '/auth/forgot-password', + handler: 'auth.forgotPassword', + config: { + policies: ['plugin::users-permissions.rateLimit'], + prefix: '', + }, + }, + { + method: 'POST', + path: '/auth/reset-password', + handler: 'auth.resetPassword', + config: { + policies: ['plugin::users-permissions.rateLimit'], + prefix: '', + }, + }, + { + method: 'GET', + path: '/auth/email-confirmation', + handler: 'auth.emailConfirmation', + config: { + prefix: '', + }, + }, + { + method: 'POST', + path: '/auth/send-email-confirmation', + handler: 'auth.sendEmailConfirmation', + config: { + prefix: '', + }, + }, +]; diff --git a/packages/plugins/users-permissions/server/routes/content-api/index.js b/packages/plugins/users-permissions/server/routes/content-api/index.js new file mode 100644 index 0000000000..b23adf5176 --- /dev/null +++ b/packages/plugins/users-permissions/server/routes/content-api/index.js @@ -0,0 +1,11 @@ +'use strict'; + +const authRoutes = require('./auth'); +const userRoutes = require('./user'); +const roleRoutes = require('./role'); +const permissionsRoutes = require('./permissions'); + +module.exports = { + type: 'content-api', + routes: [...authRoutes, ...userRoutes, ...roleRoutes, ...permissionsRoutes], +}; diff --git a/packages/plugins/users-permissions/server/routes/content-api/permissions.js b/packages/plugins/users-permissions/server/routes/content-api/permissions.js new file mode 100644 index 0000000000..f6cf4a0e25 --- /dev/null +++ b/packages/plugins/users-permissions/server/routes/content-api/permissions.js @@ -0,0 +1,9 @@ +'use strict'; + +module.exports = [ + { + method: 'GET', + path: '/permissions', + handler: 'permissions.getPermissions', + }, +]; diff --git a/packages/plugins/users-permissions/server/routes/content-api/role.js b/packages/plugins/users-permissions/server/routes/content-api/role.js new file mode 100644 index 0000000000..1434dae4f6 --- /dev/null +++ b/packages/plugins/users-permissions/server/routes/content-api/role.js @@ -0,0 +1,29 @@ +'use strict'; + +module.exports = [ + { + method: 'GET', + path: '/roles/:id', + handler: 'role.getRole', + }, + { + method: 'GET', + path: '/roles', + handler: 'role.getRoles', + }, + { + method: 'POST', + path: '/roles', + handler: 'role.createRole', + }, + { + method: 'PUT', + path: '/roles/:role', + handler: 'role.updateRole', + }, + { + method: 'DELETE', + path: '/roles/:role', + handler: 'role.deleteRole', + }, +]; diff --git a/packages/plugins/users-permissions/server/routes/content-api/user.js b/packages/plugins/users-permissions/server/routes/content-api/user.js new file mode 100644 index 0000000000..fa8dc00269 --- /dev/null +++ b/packages/plugins/users-permissions/server/routes/content-api/user.js @@ -0,0 +1,61 @@ +'use strict'; + +module.exports = [ + { + method: 'GET', + path: '/users/count', + handler: 'user.count', + config: { + prefix: '', + }, + }, + { + method: 'GET', + path: '/users', + handler: 'user.find', + config: { + auth: {}, + prefix: '', + }, + }, + { + method: 'GET', + path: '/users/me', + handler: 'user.me', + config: { + prefix: '', + }, + }, + { + method: 'GET', + path: '/users/:id', + handler: 'user.findOne', + config: { + prefix: '', + }, + }, + { + method: 'POST', + path: '/users', + handler: 'user.create', + config: { + prefix: '', + }, + }, + { + method: 'PUT', + path: '/users/:id', + handler: 'user.update', + config: { + prefix: '', + }, + }, + { + method: 'DELETE', + path: '/users/:id', + handler: 'user.destroy', + config: { + prefix: '', + }, + }, +]; diff --git a/packages/plugins/users-permissions/server/routes/index.js b/packages/plugins/users-permissions/server/routes/index.js index 0f84ab0a8c..6939f2d012 100644 --- a/packages/plugins/users-permissions/server/routes/index.js +++ b/packages/plugins/users-permissions/server/routes/index.js @@ -1,430 +1,6 @@ 'use strict'; -module.exports = [ - { - method: 'GET', - path: '/', - handler: 'users-permissions.index', - config: { - policies: [], - }, - }, - { - method: 'GET', - path: '/search/:id', - handler: 'users-permissions.searchUsers', - config: { - policies: [], - description: 'Search for users', - tag: { - plugin: 'users-permissions', - name: 'User', - actionType: 'find', - }, - }, - }, - { - method: 'GET', - path: '/policies', - handler: 'users-permissions.getPolicies', - config: { - policies: [], - }, - }, - { - method: 'GET', - path: '/roles/:id', - handler: 'users-permissions.getRole', - config: { - policies: [ - { - name: 'admin::hasPermissions', - options: { actions: ['plugin::users-permissions.roles.read'] }, - }, - ], - description: 'Retrieve a role depending on its id', - tag: { - plugin: 'users-permissions', - name: 'Role', - actionType: 'findOne', - }, - }, - }, - { - method: 'GET', - path: '/roles', - handler: 'users-permissions.getRoles', - config: { - policies: [ - { - name: 'admin::hasPermissions', - options: { actions: ['plugin::users-permissions.roles.read'] }, - }, - ], - description: 'Retrieve all role documents', - tag: { - plugin: 'users-permissions', - name: 'Role', - actionType: 'find', - }, - }, - }, - { - method: 'GET', - path: '/routes', - handler: 'users-permissions.getRoutes', - config: { - policies: [], - }, - }, - { - method: 'GET', - path: '/email-templates', - handler: 'users-permissions.getEmailTemplate', - config: { - policies: [ - { - name: 'admin::hasPermissions', - options: { actions: ['plugin::users-permissions.email-templates.read'] }, - }, - ], - }, - }, - { - method: 'PUT', - path: '/email-templates', - handler: 'users-permissions.updateEmailTemplate', - config: { - policies: [ - { - name: 'admin::hasPermissions', - options: { actions: ['plugin::users-permissions.email-templates.update'] }, - }, - ], - }, - }, - { - method: 'GET', - path: '/advanced', - handler: 'users-permissions.getAdvancedSettings', - config: { - policies: [ - { - name: 'admin::hasPermissions', - options: { actions: ['plugin::users-permissions.advanced-settings.read'] }, - }, - ], - }, - }, - { - method: 'PUT', - path: '/advanced', - handler: 'users-permissions.updateAdvancedSettings', - config: { - policies: [ - { - name: 'admin::hasPermissions', - options: { actions: ['plugin::users-permissions.advanced-settings.update'] }, - }, - ], - }, - }, - { - method: 'GET', - path: '/permissions', - handler: 'users-permissions.getPermissions', - config: { - policies: [], - }, - }, - { - method: 'GET', - path: '/providers', - handler: 'users-permissions.getProviders', - config: { - policies: [ - { - name: 'admin::hasPermissions', - options: { actions: ['plugin::users-permissions.providers.read'] }, - }, - ], - }, - }, - - { - method: 'PUT', - path: '/providers', - handler: 'users-permissions.updateProviders', - config: { - policies: [ - { - name: 'admin::hasPermissions', - options: { actions: ['plugin::users-permissions.providers.update'] }, - }, - ], - }, - }, - { - method: 'POST', - path: '/roles', - handler: 'users-permissions.createRole', - config: { - policies: [ - { - name: 'admin::hasPermissions', - options: { actions: ['plugin::users-permissions.roles.create'] }, - }, - ], - description: 'Create a new role', - tag: { - plugin: 'users-permissions', - name: 'Role', - actionType: 'create', - }, - }, - }, - { - method: 'PUT', - path: '/roles/:role', - handler: 'users-permissions.updateRole', - config: { - policies: [ - { - name: 'admin::hasPermissions', - options: { actions: ['plugin::users-permissions.roles.update'] }, - }, - ], - description: 'Update a role', - tag: { - plugin: 'users-permissions', - name: 'Role', - actionType: 'update', - }, - }, - }, - { - method: 'DELETE', - path: '/roles/:role', - handler: 'users-permissions.deleteRole', - config: { - policies: [ - { - name: 'admin::hasPermissions', - options: { actions: ['plugin::users-permissions.roles.delete'] }, - }, - ], - description: 'Delete a role', - tag: { - plugin: 'users-permissions', - name: 'Role', - actionType: 'destroy', - }, - }, - }, - { - method: 'GET', - path: '/connect/*', - handler: 'auth.connect', - config: { - policies: ['plugin::users-permissions.rateLimit'], - prefix: '', - description: 'Connect a provider', - tag: { - plugin: 'users-permissions', - name: 'User', - }, - }, - }, - { - method: 'POST', - path: '/auth/local', - handler: 'auth.callback', - config: { - policies: ['plugin::users-permissions.rateLimit'], - prefix: '', - description: 'Login a user using the identifiers email and password', - tag: { - plugin: 'users-permissions', - name: 'User', - }, - }, - }, - { - method: 'POST', - path: '/auth/local/register', - handler: 'auth.register', - config: { - policies: ['plugin::users-permissions.rateLimit'], - prefix: '', - description: 'Register a new user with the default role', - tag: { - plugin: 'users-permissions', - name: 'User', - actionType: 'create', - }, - }, - }, - { - method: 'GET', - path: '/auth/:provider/callback', - handler: 'auth.callback', - config: { - policies: [], - prefix: '', - description: 'Successfull redirection after approving a provider', - tag: { - plugin: 'users-permissions', - name: 'User', - }, - }, - }, - { - method: 'POST', - path: '/auth/forgot-password', - handler: 'auth.forgotPassword', - config: { - policies: ['plugin::users-permissions.rateLimit'], - prefix: '', - description: 'Send the reset password email link', - tag: { - plugin: 'users-permissions', - name: 'User', - }, - }, - }, - { - method: 'POST', - path: '/auth/reset-password', - handler: 'auth.resetPassword', - config: { - policies: ['plugin::users-permissions.rateLimit'], - prefix: '', - description: 'Reset user password with a code (resetToken)', - tag: { - plugin: 'users-permissions', - name: 'User', - }, - }, - }, - { - method: 'GET', - path: '/auth/email-confirmation', - handler: 'auth.emailConfirmation', - config: { - policies: [], - prefix: '', - description: 'Validate a user account', - tag: { - plugin: 'users-permissions', - name: 'User', - }, - }, - }, - { - method: 'POST', - path: '/auth/send-email-confirmation', - handler: 'auth.sendEmailConfirmation', - config: { - policies: [], - prefix: '', - description: 'Send a confirmation email to user', - tag: { - plugin: 'users-permissions', - name: 'User', - }, - }, - }, - { - method: 'GET', - path: '/users/count', - handler: 'user.count', - config: { - prefix: '', - policies: [], - }, - }, - { - method: 'GET', - path: '/users', - handler: 'user.find', - config: { - policies: [], - prefix: '', - description: 'Retrieve all user documents', - tag: { - plugin: 'users-permissions', - name: 'User', - actionType: 'find', - }, - }, - }, - { - method: 'GET', - path: '/users/me', - handler: 'user.me', - config: { - policies: [], - prefix: '', - description: 'Retrieve the logged in user information', - tag: { - plugin: 'users-permissions', - name: 'User', - actionType: 'findOne', - }, - }, - }, - { - method: 'GET', - path: '/users/:id', - handler: 'user.findOne', - config: { - policies: [], - prefix: '', - description: 'Retrieve a single user depending on his id', - tag: { - plugin: 'users-permissions', - name: 'User', - actionType: 'findOne', - }, - }, - }, - { - method: 'POST', - path: '/users', - handler: 'user.create', - config: { - policies: [], - prefix: '', - }, - }, - { - method: 'PUT', - path: '/users/:id', - handler: 'user.update', - config: { - policies: [], - prefix: '', - description: 'Update an existing user', - tag: { - plugin: 'users-permissions', - name: 'User', - actionType: 'update', - }, - }, - }, - { - method: 'DELETE', - path: '/users/:id', - handler: 'user.destroy', - config: { - policies: [], - prefix: '', - description: 'Delete an existing user', - tag: { - plugin: 'users-permissions', - name: 'User', - actionType: 'destroy', - }, - }, - }, -]; +module.exports = { + admin: require('./admin'), + 'content-api': require('./content-api'), +}; diff --git a/packages/plugins/users-permissions/server/services/index.js b/packages/plugins/users-permissions/server/services/index.js index bd504ca6ff..c26169bc56 100644 --- a/packages/plugins/users-permissions/server/services/index.js +++ b/packages/plugins/users-permissions/server/services/index.js @@ -1,13 +1,15 @@ 'use strict'; -const jwtService = require('./jwt'); -const providersService = require('./providers'); -const userService = require('./user'); -const usersPermissionsService = require('./users-permissions'); +const jwt = require('./jwt'); +const providers = require('./providers'); +const user = require('./user'); +const role = require('./role'); +const usersPermissions = require('./users-permissions'); module.exports = { - jwt: jwtService, - providers: providersService, - user: userService, - 'users-permissions': usersPermissionsService, + jwt, + providers, + role, + user, + 'users-permissions': usersPermissions, }; diff --git a/packages/plugins/users-permissions/server/services/providers.js b/packages/plugins/users-permissions/server/services/providers.js index a831ee0d8f..14fd54d493 100644 --- a/packages/plugins/users-permissions/server/services/providers.js +++ b/packages/plugins/users-permissions/server/services/providers.js @@ -6,15 +6,17 @@ // Public node modules. const _ = require('lodash'); -const request = require('request'); - -// Purest strategies. -const purest = require('purest')({ request }); -const purestConfig = require('@purest/providers'); -const { getAbsoluteServerUrl } = require('@strapi/utils'); const jwt = require('jsonwebtoken'); +const { getAbsoluteServerUrl } = require('@strapi/utils'); + module.exports = ({ strapi }) => { + // lazy load heavy dependencies + const request = require('request'); + // Purest strategies. + const purest = require('purest')({ request }); + const purestConfig = require('@purest/providers'); + /** * Helper to get profiles * diff --git a/packages/plugins/users-permissions/server/services/role.js b/packages/plugins/users-permissions/server/services/role.js new file mode 100644 index 0000000000..8531ea11b9 --- /dev/null +++ b/packages/plugins/users-permissions/server/services/role.js @@ -0,0 +1,182 @@ +'use strict'; + +const _ = require('lodash'); +const { getService } = require('../utils'); + +module.exports = ({ strapi }) => ({ + async createRole(params) { + if (!params.type) { + params.type = _.snakeCase(_.deburr(_.toLower(params.name))); + } + + const role = await strapi + .query('plugin::users-permissions.role') + .create({ data: _.omit(params, ['users', 'permissions']) }); + + const createPromises = _.flatMap(params.permissions, (type, typeName) => { + return _.flatMap(type.controllers, (controller, controllerName) => { + return _.reduce( + controller, + (acc, action, actionName) => { + const { enabled /* policy */ } = action; + + if (enabled) { + const actionID = `${typeName}.${controllerName}.${actionName}`; + + acc.push( + strapi + .query('plugin::users-permissions.permission') + .create({ data: { action: actionID, role: role.id } }) + ); + } + + return acc; + }, + [] + ); + }); + }); + + await Promise.all(createPromises); + }, + + async getRole(roleID, plugins) { + const role = await strapi + .query('plugin::users-permissions.role') + .findOne({ where: { id: roleID }, populate: ['permissions'] }); + + if (!role) { + throw new Error('Role not found'); + } + + const allActions = getService('users-permissions').getActions(); + + // Group by `type`. + role.permissions.forEach(permission => { + const [type, controller, action] = permission.action.split('.'); + + _.set(allActions, `${type}.controllers.${controller}.${action}`, { + enabled: true, + policy: '', + }); + + if (permission.action.startsWith('plugin')) { + const [, pluginName] = type.split('::'); + + allActions[type].information = plugins.find(plugin => plugin.id === pluginName) || {}; + } + }); + + return { + ...role, + permissions: allActions, + }; + }, + + async getRoles() { + const roles = await strapi.query('plugin::users-permissions.role').findMany({ sort: ['name'] }); + + for (const role of roles) { + roles.nb_users = await strapi + .query('plugin::users-permissions.user') + .count({ where: { role: { id: role.id } } }); + } + + return roles; + }, + + async updateRole(roleID, data) { + const role = await strapi + .query('plugin::users-permissions.role') + .findOne({ where: { id: roleID }, populate: ['permissions'] }); + + if (!role) { + throw new Error('Role not found'); + } + + await strapi.query('plugin::users-permissions.role').update({ + where: { id: roleID }, + data: _.pick(data, ['name', 'description']), + }); + + const { permissions } = data; + + const newActions = _.flatMap(permissions, (type, typeName) => { + return _.flatMap(type.controllers, (controller, controllerName) => { + return _.reduce( + controller, + (acc, action, actionName) => { + const { enabled /* policy */ } = action; + + if (enabled) { + acc.push(`${typeName}.${controllerName}.${actionName}`); + } + + return acc; + }, + [] + ); + }); + }); + + const oldActions = role.permissions.map(({ action }) => action); + + const toDelete = role.permissions.reduce((acc, permission) => { + if (!newActions.includes(permission.action)) { + acc.push(permission); + } + return acc; + }, []); + + const toCreate = newActions + .filter(action => !oldActions.includes(action)) + .map(action => ({ action, role: role.id })); + + await Promise.all( + toDelete.map(permission => + strapi + .query('plugin::users-permissions.permission') + .delete({ where: { id: permission.id } }) + ) + ); + + await Promise.all( + toCreate.map(permissionInfo => + strapi.query('plugin::users-permissions.permission').create({ data: permissionInfo }) + ) + ); + }, + + async deleteRole(roleID, publicRoleID) { + const role = await strapi + .query('plugin::users-permissions.role') + .findOne({ where: { id: roleID }, populate: ['users', 'permissions'] }); + + if (!role) { + throw new Error('Role not found'); + } + + // Move users to guest role. + await Promise.all( + role.users.map(user => { + return strapi.query('plugin::users-permissions.user').update({ + where: { id: user.id }, + data: { role: publicRoleID }, + }); + }) + ); + + // Remove permissions related to this role. + // TODO: use delete many + await Promise.all( + role.permissions.map(permission => { + return strapi.query('plugin::users-permissions.permission').delete({ + where: { id: permission.id }, + }); + }) + ); + + // Delete the role. + await strapi.query('plugin::users-permissions.role').delete({ where: { id: roleID } }); + }, +}); diff --git a/packages/plugins/users-permissions/server/services/user.js b/packages/plugins/users-permissions/server/services/user.js index cb6039e83e..bd405a8543 100644 --- a/packages/plugins/users-permissions/server/services/user.js +++ b/packages/plugins/users-permissions/server/services/user.js @@ -114,10 +114,6 @@ module.exports = ({ strapi }) => ({ return strapi.query('plugin::users-permissions.user').delete({ where: params }); }, - async removeAll(params) { - return strapi.query('plugin::users-permissions.user').delete({ where: params }); - }, - validatePassword(password, hash) { return bcrypt.compare(password, hash); }, diff --git a/packages/plugins/users-permissions/server/services/users-permissions.js b/packages/plugins/users-permissions/server/services/users-permissions.js index df73d42605..9fecf37c33 100644 --- a/packages/plugins/users-permissions/server/services/users-permissions.js +++ b/packages/plugins/users-permissions/server/services/users-permissions.js @@ -1,123 +1,35 @@ 'use strict'; const _ = require('lodash'); -const request = require('request'); +const { filter, map, pipe, prop } = require('lodash/fp'); + const { getService } = require('../utils'); const DEFAULT_PERMISSIONS = [ - { action: 'admincallback', controller: 'auth', type: 'users-permissions', roleType: 'public' }, - { action: 'adminregister', controller: 'auth', type: 'users-permissions', roleType: 'public' }, - { action: 'callback', controller: 'auth', type: 'users-permissions', roleType: 'public' }, - { action: 'connect', controller: 'auth', type: 'users-permissions', roleType: null }, - { action: 'forgotpassword', controller: 'auth', type: 'users-permissions', roleType: 'public' }, - { action: 'resetpassword', controller: 'auth', type: 'users-permissions', roleType: 'public' }, - { action: 'register', controller: 'auth', type: 'users-permissions', roleType: 'public' }, - { - action: 'emailconfirmation', - controller: 'auth', - type: 'users-permissions', - roleType: 'public', - }, - { action: 'me', controller: 'user', type: 'users-permissions', roleType: null }, + { action: 'plugin::users-permissions.auth.admincallback', roleType: 'public' }, + { action: 'plugin::users-permissions.auth.adminregister', roleType: 'public' }, + { action: 'plugin::users-permissions.auth.callback', roleType: 'public' }, + { action: 'plugin::users-permissions.auth.connect', roleType: null }, + { action: 'plugin::users-permissions.auth.forgotpassword', roleType: 'public' }, + { action: 'plugin::users-permissions.auth.resetpassword', roleType: 'public' }, + { action: 'plugin::users-permissions.auth.register', roleType: 'public' }, + { action: 'plugin::users-permissions.auth.emailconfirmation', roleType: 'public' }, + { action: 'plugin::users-permissions.user.me', roleType: null }, ]; -const isEnabledByDefault = (permission, role) => { - return DEFAULT_PERMISSIONS.some( - defaultPerm => - (defaultPerm.action === null || permission.action === defaultPerm.action) && - (defaultPerm.controller === null || permission.controller === defaultPerm.controller) && - (defaultPerm.type === null || permission.type === defaultPerm.type) && - (defaultPerm.roleType === null || role.type === defaultPerm.roleType) - ); +const transformRoutePrefixFor = pluginName => route => { + const prefix = route.config && route.config.prefix; + const path = prefix !== undefined ? `${prefix}${route.path}` : `/${pluginName}${route.path}`; + + return { + ...route, + path, + }; }; module.exports = ({ strapi }) => ({ - async createRole(params) { - if (!params.type) { - params.type = _.snakeCase(_.deburr(_.toLower(params.name))); - } - - const role = await strapi - .query('plugin::users-permissions.role') - .create({ data: _.omit(params, ['users', 'permissions']) }); - - const arrayOfPromises = Object.keys(params.permissions || {}).reduce((acc, type) => { - Object.keys(params.permissions[type].controllers).forEach(controller => { - Object.keys(params.permissions[type].controllers[controller]).forEach(action => { - acc.push( - strapi.query('plugin::users-permissions.permission').create({ - data: { - role: role.id, - type, - controller, - action: action.toLowerCase(), - ...params.permissions[type].controllers[controller][action], - }, - }) - ); - }); - }); - - return acc; - }, []); - - // Use Content Manager business logic to handle relation. - if (params.users && params.users.length > 0) - arrayOfPromises.push( - strapi.query('plugin::users-permissions.role').update({ - where: { - id: role.id, - }, - data: { users: params.users }, - }) - ); - - return await Promise.all(arrayOfPromises); - }, - - async deleteRole(roleID, publicRoleID) { - const role = await strapi - .query('plugin::users-permissions.role') - .findOne({ where: { id: roleID }, populate: ['users', 'permissions'] }); - - if (!role) { - throw new Error('Cannot find this role'); - } - - // Move users to guest role. - const arrayOfPromises = role.users.reduce((acc, user) => { - acc.push( - strapi.query('plugin::users-permissions.user').update({ - where: { - id: user.id, - }, - data: { - role: publicRoleID, - }, - }) - ); - - return acc; - }, []); - - // Remove permissions related to this role. - role.permissions.forEach(permission => { - arrayOfPromises.push( - strapi.query('plugin::users-permissions.permission').delete({ - where: { id: permission.id }, - }) - ); - }); - - // Delete the role. - arrayOfPromises.push( - strapi.query('plugin::users-permissions.role').delete({ where: { id: roleID } }) - ); - - return await Promise.all(arrayOfPromises); - }, - getPlugins(lang = 'en') { + const request = require('request'); return new Promise(resolve => { request( { @@ -139,208 +51,131 @@ module.exports = ({ strapi }) => ({ }); }, - getActions() { - const generateActions = data => - Object.keys(data).reduce((acc, key) => { - if (_.isFunction(data[key])) { - acc[key] = { enabled: false, policy: '' }; - } + // TODO: Filter on content-api only + getActions({ defaultEnable = false } = {}) { + const actionMap = {}; - return acc; - }, {}); - - const appControllers = Object.keys(strapi.api || {}) - .filter(key => !!strapi.api[key].controllers) - .reduce( - (acc, key) => { - Object.keys(strapi.api[key].controllers).forEach(controller => { - acc.controllers[controller] = generateActions(strapi.api[key].controllers[controller]); - }); - - return acc; - }, - { controllers: {} } - ); - - const pluginsPermissions = Object.keys(strapi.plugins).reduce((acc, key) => { - const initialState = { - controllers: {}, - }; - - const pluginControllers = strapi.plugin(key).controllers; - acc[key] = Object.keys(pluginControllers).reduce((obj, k) => { - obj.controllers[k] = generateActions(pluginControllers[k]); - - return obj; - }, initialState); - - return acc; - }, {}); - - const permissions = { - application: { - controllers: appControllers.controllers, - }, - }; - - return _.merge(permissions, pluginsPermissions); - }, - - async getRole(roleID, plugins) { - const role = await strapi - .query('plugin::users-permissions.role') - .findOne({ where: { id: roleID }, populate: ['permissions'] }); - - if (!role) { - throw new Error('Cannot find this role'); - } - - // Group by `type`. - const permissions = role.permissions.reduce((acc, permission) => { - _.set(acc, `${permission.type}.controllers.${permission.controller}.${permission.action}`, { - enabled: _.toNumber(permission.enabled) == true, - policy: permission.policy, + _.forEach(strapi.api, (api, apiName) => { + const controllers = _.mapValues(api.controllers, controller => { + return _.mapValues(controller, () => { + return { + enabled: defaultEnable, + policy: '', + }; + }); }); - if (permission.type !== 'application' && !acc[permission.type].information) { - acc[permission.type].information = - plugins.find(plugin => plugin.id === permission.type) || {}; - } - - return acc; - }, {}); - - return { - ...role, - permissions, - }; - }, - - async getRoles() { - const roles = await strapi.query('plugin::users-permissions.role').findMany({ sort: ['name'] }); - - for (let i = 0; i < roles.length; ++i) { - roles[i].nb_users = await strapi - .query('plugin::users-permissions.user') - .count({ where: { role: { id: roles[i].id } } }); - } - - return roles; - }, - - async getRoutes() { - const routes = Object.keys(strapi.api || {}).reduce((acc, current) => { - return acc.concat(_.get(strapi.api[current].config, 'routes', [])); - }, []); - const pluginsRoutes = Object.keys(strapi.plugins).reduce((acc, current) => { - const routes = strapi.plugin(current).routes.reduce((acc, curr) => { - const prefix = curr.config.prefix; - const path = prefix !== undefined ? `${prefix}${curr.path}` : `/${current}${curr.path}`; - _.set(curr, 'path', path); - - return acc.concat(curr); - }, []); - - acc[current] = routes; - - return acc; - }, {}); - - return _.merge({ application: routes }, pluginsRoutes); - }, - - async updatePermissions() { - const roles = await strapi.query('plugin::users-permissions.role').findMany(); - - const rolesMap = _.keyBy(roles, 'id'); - - const dbPermissions = await strapi - .query('plugin::users-permissions.permission') - .findMany({ populate: ['role'] }); - - let permissionsFoundInDB = dbPermissions.map(permission => { - const { type, controller, action, role } = permission; - return `${type}.${controller}.${action}.${role.id}`; + actionMap[`api::${apiName}`] = { controllers }; }); - permissionsFoundInDB = _.uniq(permissionsFoundInDB); - - // Aggregate first level actions. - const appActions = Object.keys(strapi.api || {}).reduce((acc, api) => { - Object.keys(_.get(strapi.api[api], 'controllers', {})).forEach(controller => { - const actions = Object.keys(strapi.api[api].controllers[controller]) - .filter(action => _.isFunction(strapi.api[api].controllers[controller][action])) - .map(action => `application.${controller}.${action.toLowerCase()}`); - - acc = acc.concat(actions); + _.forEach(strapi.plugins, (plugin, pluginName) => { + const controllers = _.mapValues(plugin.controllers, controller => { + return _.mapValues(controller, () => { + return { + enabled: defaultEnable, + policy: '', + }; + }); }); - return acc; - }, []); + actionMap[`plugin::${pluginName}`] = { controllers }; + }); - // Aggregate plugins' actions. - const pluginsActions = Object.keys(strapi.plugins).reduce((acc, plugin) => { - const pluginControllers = strapi.plugin(plugin).controllers; + return actionMap; + }, - Object.keys(pluginControllers).forEach(controller => { - const controllerActions = pluginControllers[controller]; + // TODO: Filter on content-api only + async getRoutes() { + const routesMap = {}; - const actions = Object.keys(controllerActions) - .filter(action => _.isFunction(controllerActions[action])) - .map(action => `${plugin}.${controller}.${action.toLowerCase()}`); + _.forEach(strapi.api, (api, apiName) => { + const routes = _.flatMap(api.routes, route => { + if (_.has(route, 'routes')) { + return route.routes; + } - acc = acc.concat(actions); + return route; }); - return acc; - }, []); + if (routes.length === 0) { + return; + } - const actionsFoundInFiles = appActions.concat(pluginsActions); + routesMap[`api::${apiName}`] = routes; + }); - const permissionsFoundInFiles = []; + _.forEach(strapi.plugins, (plugin, pluginName) => { + const transformPrefix = transformRoutePrefixFor(pluginName); - for (const role of roles) { - actionsFoundInFiles.forEach(action => { - permissionsFoundInFiles.push(`${action}.${role.id}`); + const routes = _.flatMap(plugin.routes, route => { + if (_.has(route, 'routes')) { + return route.routes.map(transformPrefix); + } + + return transformPrefix(route); }); - } - // Compare to know if actions have been added or removed from controllers. - if (!_.isEqual(permissionsFoundInDB.sort(), permissionsFoundInFiles.sort())) { - const splitted = str => { - const [type, controller, action, roleId] = str.split('.'); + if (routes.length === 0) { + return; + } - return { type, controller, action, roleId }; - }; + routesMap[`plugin::${pluginName}`] = routes; + }); - // We have to know the difference to add or remove the permissions entries in the database. - const toRemove = _.difference(permissionsFoundInDB, permissionsFoundInFiles).map(splitted); - const toAdd = _.difference(permissionsFoundInFiles, permissionsFoundInDB).map(splitted); + return routesMap; + }, - const query = strapi.query('plugin::users-permissions.permission'); + async syncPermissions() { + const roles = await strapi.query('plugin::users-permissions.role').findMany(); + const dbPermissions = await strapi.query('plugin::users-permissions.permission').findMany(); - // Execute request to update entries in database for each role. - await Promise.all( - toAdd.map(permission => { - return query.create({ - data: { - type: permission.type, - controller: permission.controller, - action: permission.action, - enabled: isEnabledByDefault(permission, rolesMap[permission.roleId]), - policy: '', - role: permission.roleId, - }, - }); - }) - ); + const permissionsFoundInDB = _.uniq(_.map(dbPermissions, 'action')); - await Promise.all( - toRemove.map(permission => { - const { type, controller, action, roleId } = permission; - return query.delete({ where: { type, controller, action, role: { id: roleId } } }); - }) - ); + const appActions = _.flatMap(strapi.api, (api, apiName) => { + return _.flatMap(api.controllers, (controller, controllerName) => { + return _.keys(controller).map(actionName => { + return `api::${apiName}.${controllerName}.${_.toLower(actionName)}`; + }); + }); + }); + + const pluginsActions = _.flatMap(strapi.plugins, (plugin, pluginName) => { + return _.flatMap(plugin.controllers, (controller, controllerName) => { + return _.keys(controller).map(actionName => { + return `plugin::${pluginName}.${controllerName}.${_.toLower(actionName)}`; + }); + }); + }); + + const allActions = [...appActions, ...pluginsActions]; + + const toDelete = _.difference(permissionsFoundInDB, allActions); + + await Promise.all( + toDelete.map(action => { + return strapi.query('plugin::users-permissions.permission').delete({ where: { action } }); + }) + ); + + if (permissionsFoundInDB.length === 0) { + // create default permissions + for (const role of roles) { + const toCreate = pipe( + filter(({ roleType }) => roleType === role.type || roleType === null), + map(prop('action')) + )(DEFAULT_PERMISSIONS); + + await Promise.all( + toCreate.map(action => { + return strapi.query('plugin::users-permissions.permission').create({ + data: { + action, + role: role.id, + }, + }); + }) + ); + } } }, @@ -365,57 +200,7 @@ module.exports = ({ strapi }) => ({ }); } - return getService('users-permissions').updatePermissions(); - }, - - async updateRole(roleID, body) { - const [role, authenticated] = await Promise.all([ - this.getRole(roleID, []), - strapi.query('plugin::users-permissions.role').findOne({ where: { type: 'authenticated' } }), - ]); - - await strapi.query('plugin::users-permissions.role').update({ - where: { id: roleID }, - data: _.pick(body, ['name', 'description']), - }); - - await Promise.all( - Object.keys(body.permissions || {}).reduce((acc, type) => { - Object.keys(body.permissions[type].controllers).forEach(controller => { - Object.keys(body.permissions[type].controllers[controller]).forEach(action => { - const bodyAction = body.permissions[type].controllers[controller][action]; - const currentAction = _.get( - role.permissions, - `${type}.controllers.${controller}.${action}`, - {} - ); - - if (!_.isEqual(bodyAction, currentAction)) { - acc.push( - strapi.query('plugin::users-permissions.permission').update({ - where: { - role: roleID, - type, - controller, - action: action.toLowerCase(), - }, - data: bodyAction, - }) - ); - } - }); - }); - - return acc; - }, []) - ); - - // Add user to this role. - const newUsers = _.differenceBy(body.users, role.users, 'id'); - await Promise.all(newUsers.map(user => this.updateUserRole(user, roleID))); - - const oldUsers = _.differenceBy(role.users, body.users, 'id'); - await Promise.all(oldUsers.map(user => this.updateUserRole(user, authenticated.id))); + return getService('users-permissions').syncPermissions(); }, async updateUserRole(user, role) { diff --git a/packages/plugins/users-permissions/server/utils/index.d.ts b/packages/plugins/users-permissions/server/utils/index.d.ts index 1686a6c262..a9f95e108a 100644 --- a/packages/plugins/users-permissions/server/utils/index.d.ts +++ b/packages/plugins/users-permissions/server/utils/index.d.ts @@ -1,11 +1,13 @@ import * as usersPermissions from '../services/users-permissions'; import * as user from '../services/user'; +import * as role from '../services/role'; import * as jwt from '../services/jwt'; import * as providers from '../services/providers'; type S = { ['users-permissions']: typeof usersPermissions; + ['role']: typeof role; user: typeof user; jwt: typeof jwt; providers: typeof providers; diff --git a/packages/plugins/users-permissions/strapi-server.js b/packages/plugins/users-permissions/strapi-server.js index 407591ed17..8a908be91d 100644 --- a/packages/plugins/users-permissions/strapi-server.js +++ b/packages/plugins/users-permissions/strapi-server.js @@ -1,21 +1,3 @@ 'use strict'; -const bootstrap = require('./server/bootstrap'); -const contentTypes = require('./server/content-types'); -const policies = require('./server/policies'); -const services = require('./server/services'); -const routes = require('./server/routes'); -const controllers = require('./server/controllers'); -const middlewares = require('./server/middlewares'); -const config = require('./server/config'); - -module.exports = () => ({ - bootstrap, - config, - routes, - controllers, - middlewares, - contentTypes, - policies, - services, -}); +module.exports = require('./server'); diff --git a/packages/plugins/users-permissions/tests/roles-api.test.e2e.js b/packages/plugins/users-permissions/tests/admin/roles-api.test.e2e.js similarity index 67% rename from packages/plugins/users-permissions/tests/roles-api.test.e2e.js rename to packages/plugins/users-permissions/tests/admin/roles-api.test.e2e.js index 864c54a29e..68f4c10a87 100644 --- a/packages/plugins/users-permissions/tests/roles-api.test.e2e.js +++ b/packages/plugins/users-permissions/tests/admin/roles-api.test.e2e.js @@ -2,40 +2,19 @@ // Test a simple default API with no relations -const { createStrapiInstance } = require('../../../../test/helpers/strapi'); -const { createAuthRequest } = require('../../../../test/helpers/request'); +const { createStrapiInstance } = require('../../../../../test/helpers/strapi'); +const { createAuthRequest } = require('../../../../../test/helpers/request'); let strapi; let rq; let data = {}; let internals = { - user: { - username: 'User 1', - email: 'user1@strapi.io', - password: 'test1234', - }, role: { name: 'Test Role', description: 'Some random test role', }, }; -/** - * Utils for this test files - */ -const createTestUser = () => - rq({ - method: 'POST', - url: '/auth/local/register', - body: internals.user, - }); - -const deleteTestUser = () => - rq({ - method: 'DELETE', - url: `/users/${data.user.id}`, - }); - /***************************** * TESTS *****************************/ @@ -43,13 +22,9 @@ describe('Roles API', () => { beforeAll(async () => { strapi = await createStrapiInstance(); rq = await createAuthRequest({ strapi }); - - const res = await createTestUser(); - data.user = res.body.user; }); afterAll(async () => { - await deleteTestUser(); await strapi.destroy(); }); @@ -60,7 +35,6 @@ describe('Roles API', () => { body: { ...internals.role, permissions: [], - users: [data.user.id], }, }); diff --git a/packages/plugins/users-permissions/tests/users-api.test.e2e.js b/packages/plugins/users-permissions/tests/content-api/users-api.test.e2e.js similarity index 63% rename from packages/plugins/users-permissions/tests/users-api.test.e2e.js rename to packages/plugins/users-permissions/tests/content-api/users-api.test.e2e.js index 8390d62734..38951270a2 100644 --- a/packages/plugins/users-permissions/tests/users-api.test.e2e.js +++ b/packages/plugins/users-permissions/tests/content-api/users-api.test.e2e.js @@ -2,17 +2,18 @@ // Test a simple default API with no relations -const { createStrapiInstance } = require('../../../../test/helpers/strapi'); -const { createAuthRequest } = require('../../../../test/helpers/request'); +const { createStrapiInstance } = require('../../../../../test/helpers/strapi'); +const { createContentAPIRequest } = require('../../../../../test/helpers/request'); let strapi; let rq; + let data = {}; describe('Users API', () => { beforeAll(async () => { strapi = await createStrapiInstance(); - rq = await createAuthRequest({ strapi }); + rq = await createContentAPIRequest({ strapi }); }); afterAll(async () => { @@ -28,19 +29,17 @@ describe('Users API', () => { const res = await rq({ method: 'POST', - url: '/auth/local/register', + url: '/users', body: user, }); - expect(res.statusCode).toBe(200); + expect(res.statusCode).toBe(201); expect(res.body).toMatchObject({ - jwt: expect.any(String), - user: { - username: user.username, - email: user.email, - }, + username: user.username, + email: user.email, }); - data.user = res.body.user; + + data.user = res.body; }); test('Delete user', async () => { diff --git a/packages/strapi-middleware-views/lib/index.js b/packages/strapi-middleware-views/lib/index.js index a787b5577e..7c6e542dba 100644 --- a/packages/strapi-middleware-views/lib/index.js +++ b/packages/strapi-middleware-views/lib/index.js @@ -43,12 +43,8 @@ module.exports = strapi => { engine )); } catch (err) { - strapi.log.error( - '`' + engine + '` template engine not installed.' - ); - strapi.log.error( - 'Execute `$ npm install ' + engine + ' --save` to install it.' - ); + strapi.log.error('`' + engine + '` template engine not installed.'); + strapi.log.error('Execute `$ npm install ' + engine + ' --save` to install it.'); process.exit(1); } } @@ -57,11 +53,8 @@ module.exports = strapi => { consolidate[engine]; }); - strapi.app.use( - koaViews( - path.resolve(strapi.config.appPath, strapi.config.paths.views), - opts - ) + strapi.server.use( + koaViews(path.resolve(strapi.config.appPath, strapi.config.paths.views), opts) ); } }, diff --git a/test/helpers/agent.js b/test/helpers/agent.js index cb6df9d557..5f4ea115e7 100644 --- a/test/helpers/agent.js +++ b/test/helpers/agent.js @@ -11,7 +11,7 @@ const createAgent = (strapi, initialState = {}) => { const agent = options => { const { method, url, body, formData, qs: queryString } = options; - const supertestAgent = request.agent(strapi.server); + const supertestAgent = request.agent(strapi.server.httpServer); if (has('token', state)) { supertestAgent.auth(state.token, { type: 'bearer' }); diff --git a/test/helpers/request.js b/test/helpers/request.js index 82692b0d84..70056a7e28 100644 --- a/test/helpers/request.js +++ b/test/helpers/request.js @@ -4,8 +4,14 @@ const { createAgent } = require('./agent'); const { superAdmin } = require('./strapi'); const createRequest = ({ strapi } = {}) => createAgent(strapi); -const createAuthRequest = ({ strapi, userInfo = superAdmin.credentials }) => - createAgent(strapi).login(userInfo); + +const createContentAPIRequest = ({ strapi } = {}) => { + return createAgent(strapi, { urlPrefix: '/api', token: 'test-token' }); +}; + +const createAuthRequest = ({ strapi, userInfo = superAdmin.credentials }) => { + return createAgent(strapi).login(userInfo); +}; const transformToRESTResource = input => { if (Array.isArray(input)) { @@ -18,6 +24,7 @@ const transformToRESTResource = input => { module.exports = { createRequest, + createContentAPIRequest, createAuthRequest, transformToRESTResource, }; diff --git a/test/helpers/strapi.js b/test/helpers/strapi.js index cabf98b5fe..6dbdf5955f 100644 --- a/test/helpers/strapi.js +++ b/test/helpers/strapi.js @@ -22,13 +22,19 @@ const createStrapiInstance = async ({ ensureSuperAdmin = true, logLevel = 'fatal await instance.load(); + instance.container.get('content-api').auth.register({ + name: 'test-strategy', + authenticate() { + return { authenticated: true }; + }, + verify() { + return; + }, + }); + instance.log.level = logLevel; - await instance.app - // Populate Koa routes - .use(instance.router.routes()) - // Populate Koa methods - .use(instance.router.allowedMethods()); + instance.server.mount(); const utils = createUtils(instance); diff --git a/test/helpers/test-app-generator.js b/test/helpers/test-app-generator.js index 121f489b75..0ea4372ed9 100644 --- a/test/helpers/test-app-generator.js +++ b/test/helpers/test-app-generator.js @@ -44,13 +44,7 @@ const generateTestApp = async ({ appName, database }) => { installDependencies: false, strapiDependencies: [ '@strapi/strapi', - '@strapi/admin', - '@strapi/utils', - '@strapi/plugin-content-type-builder', - '@strapi/plugin-content-manager', '@strapi/plugin-users-permissions', - '@strapi/plugin-email', - '@strapi/plugin-upload', '@strapi/plugin-graphql', '@strapi/plugin-documentation', '@strapi/plugin-i18n', diff --git a/yarn.lock b/yarn.lock index 4a8b1cc33b..cd987321c2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1932,6 +1932,17 @@ dependencies: vary "^1.1.2" +"@koa/router@10.1.1": + version "10.1.1" + resolved "https://registry.yarnpkg.com/@koa/router/-/router-10.1.1.tgz#8e5a85c9b243e0bc776802c0de564561e57a5f78" + integrity sha512-ORNjq5z4EmQPriKbR0ER3k4Gh7YGNhWDL7JBW+8wXDrHLbWYKYSJaOJ9aN06npF5tbTxe2JBOsurpJDAvjiXKw== + dependencies: + debug "^4.1.1" + http-errors "^1.7.3" + koa-compose "^4.1.0" + methods "^1.1.2" + path-to-regexp "^6.1.0" + "@lerna/add@3.21.0": version "3.21.0" resolved "https://registry.yarnpkg.com/@lerna/add/-/add-3.21.0.tgz#27007bde71cc7b0a2969ab3c2f0ae41578b4577b" @@ -10778,7 +10789,7 @@ http-errors@1.7.3, http-errors@~1.7.2: statuses ">= 1.5.0 < 2" toidentifier "1.0.0" -http-errors@^1.3.1, http-errors@^1.6.3, http-errors@^1.7.3: +http-errors@^1.6.3, http-errors@^1.7.3: version "1.8.0" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.8.0.tgz#75d1bbe497e1044f51e4ee9e704a62f28d336507" integrity sha512-4I8r0C5JDhT5VkvI47QktDW75rNlGVsUf/8hzjCC/wkWI/jdTRmBb9aI7erSG82r1bjKY3F6k28WnsVxB1C73A== @@ -12895,6 +12906,14 @@ koa-locale@~1.3.0: dependencies: delegates "1.0.0" +koa-mount@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/koa-mount/-/koa-mount-4.0.0.tgz#e0265e58198e1a14ef889514c607254ff386329c" + integrity sha512-rm71jaA/P+6HeCpoRhmCv8KVBIi0tfGuO/dMKicbQnQW/YJntJ6MnnspkodoA4QstMVEZArsCphmd0bJEtoMjQ== + dependencies: + debug "^4.0.1" + koa-compose "^4.1.0" + koa-passport@4.1.4: version "4.1.4" resolved "https://registry.yarnpkg.com/koa-passport/-/koa-passport-4.1.4.tgz#5f1665c1c2a37ace79af9f970b770885ca30ccfa" @@ -12909,18 +12928,6 @@ koa-range@0.3.0: dependencies: stream-slice "^0.1.2" -koa-router@^7.4.0: - version "7.4.0" - resolved "https://registry.yarnpkg.com/koa-router/-/koa-router-7.4.0.tgz#aee1f7adc02d5cb31d7d67465c9eacc825e8c5e0" - integrity sha512-IWhaDXeAnfDBEpWS6hkGdZ1ablgr6Q6pGdXCyK38RbzuH4LkUOpPqPw+3f8l8aTDrQmBQ7xJc0bs2yV4dzcO+g== - dependencies: - debug "^3.1.0" - http-errors "^1.3.1" - koa-compose "^3.0.0" - methods "^1.0.1" - path-to-regexp "^1.1.1" - urijs "^1.19.0" - koa-send@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/koa-send/-/koa-send-5.0.1.tgz#39dceebfafb395d0d60beaffba3a70b4f543fe79" @@ -14065,7 +14072,7 @@ merge2@^1.2.3, merge2@^1.3.0: resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== -methods@1.1.2, methods@^1.0.1, methods@^1.1.2, methods@~1.1.2: +methods@1.1.2, methods@^1.1.2, methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= @@ -15893,7 +15900,7 @@ path-to-regexp@0.1.7: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= -path-to-regexp@^1.1.1, path-to-regexp@^1.7.0: +path-to-regexp@^1.7.0: version "1.8.0" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== @@ -15905,6 +15912,11 @@ path-to-regexp@^3.1.0: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-3.2.0.tgz#fa7877ecbc495c601907562222453c43cc204a5f" integrity sha512-jczvQbCUS7XmS7o+y1aEO9OBVFeZBQ1MDSEqmO7xSoPgOPoowY/SxLpZ6Vh97/8qHZOteiCKb7gkG9gA2ZUxJA== +path-to-regexp@^6.1.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.2.0.tgz#f7b3803336104c346889adece614669230645f38" + integrity sha512-f66KywYG6+43afgE/8j/GoiNyygk/bnoCbps++3ErRKsIYkGGupyv07R2Ok5m9i67Iqc+T2g1eAUGUPzWhYTyg== + path-type@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" @@ -20976,11 +20988,6 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" -urijs@^1.19.0: - version "1.19.7" - resolved "https://registry.yarnpkg.com/urijs/-/urijs-1.19.7.tgz#4f594e59113928fea63c00ce688fb395b1168ab9" - integrity sha512-Id+IKjdU0Hx+7Zx717jwLPsPeUqz7rAtuVBRLLs+qn+J2nf9NGITWVCxcijgYxBqe83C7sqsQPs6H1pyz3x9gA== - urix@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72"