diff --git a/packages/core/admin/admin/src/pages/Admin/index.js b/packages/core/admin/admin/src/pages/Admin/index.js index f7dd1ecb2c..4960112fbd 100644 --- a/packages/core/admin/admin/src/pages/Admin/index.js +++ b/packages/core/admin/admin/src/pages/Admin/index.js @@ -45,15 +45,6 @@ const SettingsPage = lazy(() => import(/* webpackChunkName: "Admin_settingsPage" */ '../SettingsPage') ); -const CTB = lazy(() => - import( - /* webpackChunkName: "content-type-builder" */ '@strapi/plugin-content-type-builder/admin/src/pages/App' - ) -); -const Upload = lazy(() => - import(/* webpackChunkName: "upload" */ '@strapi/plugin-upload/admin/src/pages/App') -); - // Simple hook easier for testing const useTrackUsage = () => { const { trackUsage } = useTracking(); @@ -100,8 +91,6 @@ const Admin = () => { - - {routes} diff --git a/packages/core/admin/admin/src/plugins.js b/packages/core/admin/admin/src/plugins.js index 981437e347..b5e257e446 100644 --- a/packages/core/admin/admin/src/plugins.js +++ b/packages/core/admin/admin/src/plugins.js @@ -9,14 +9,14 @@ import emailPlugin from '../../../email/admin/src'; import uploadPlugin from '../../../upload/admin/src'; const plugins = { - '@strapi/plugin-content-type-builder': ctbPlugin, - '@strapi/plugin-documentation': documentationPlugin, - '@strapi/plugin-i18n': i18nPlugin, - '@strapi/plugin-email': emailPlugin, - '@strapi/plugin-upload': uploadPlugin, - '@strapi/plugin-graphql': graphqlPlugin, - '@strapi/plugin-sentry': sentryPlugin, - '@strapi/plugin-users-permissions': usersPermissionsPlugin, + 'content-type-builder': ctbPlugin, + documentation: documentationPlugin, + i18n: i18nPlugin, + email: emailPlugin, + upload: uploadPlugin, + graphql: graphqlPlugin, + sentry: sentryPlugin, + 'users-permissions': usersPermissionsPlugin, }; export default plugins; diff --git a/packages/core/admin/ee/server/routes/features-routes.js b/packages/core/admin/ee/server/routes/features-routes.js index 1612012426..b44a1b9cff 100644 --- a/packages/core/admin/ee/server/routes/features-routes.js +++ b/packages/core/admin/ee/server/routes/features-routes.js @@ -6,16 +6,19 @@ module.exports = { method: 'GET', path: '/providers', handler: 'authentication.getProviders', + config: { auth: false }, }, { method: 'GET', path: '/connect/:provider', handler: 'authentication.providerLogin', + config: { auth: false }, }, { method: 'POST', path: '/connect/:provider', handler: 'authentication.providerLogin', + config: { auth: false }, }, { method: 'GET', diff --git a/packages/core/admin/index.js b/packages/core/admin/index.js index 30d04cde20..74ddf13fdf 100644 --- a/packages/core/admin/index.js +++ b/packages/core/admin/index.js @@ -1,5 +1,5 @@ 'use strict'; -/* eslint-disable no-useless-escape */ + const path = require('path'); const _ = require('lodash'); const fs = require('fs-extra'); @@ -34,9 +34,9 @@ function getCustomWebpackConfig(dir, config) { return webpackConfig; } -async function build({ dir, env, options, optimize }) { +async function build({ plugins, dir, env, options, optimize }) { // Create the cache dir containing the front-end files. - await createCacheDir(dir); + await createCacheDir({ dir, plugins }); const cacheDir = path.resolve(dir, '.cache'); const entry = path.resolve(cacheDir, 'admin', 'src'); @@ -48,7 +48,18 @@ async function build({ dir, env, options, optimize }) { ceRoot: path.resolve(cacheDir, 'admin', 'src'), }; - const config = getCustomWebpackConfig(dir, { entry, dest, env, options, optimize, roots }); + const pluginsPath = Object.keys(plugins).map(pluginName => plugins[pluginName].pathToPlugin); + + const config = getCustomWebpackConfig(dir, { + entry, + pluginsPath, + cacheDir, + dest, + env, + options, + optimize, + roots, + }); const compiler = webpack(config); @@ -78,34 +89,28 @@ async function build({ dir, env, options, optimize }) { }); } -async function createPluginsJs(plugins, localPlugins, dest) { - const createPluginsArray = plugins => - plugins.map(name => { - const shortName = _.camelCase(name.replace(/^@strapi\/plugin-/i, '')); - - return { name, shortName }; - }); - const appPluginsArray = createPluginsArray(plugins); - const localPluginsArray = createPluginsArray(localPlugins); +async function createPluginsJs(plugins, dest) { + const pluginsArray = plugins.map(({ pathToPlugin, name }) => { + const shortName = _.camelCase(name); + return { + name, + pathToPlugin: path.relative(path.resolve(dest, 'admin', 'src'), pathToPlugin), + shortName, + }; + }); const content = ` -${appPluginsArray - .map(({ name, shortName }) => { - const req = `'../../plugins/${name}/admin/src'`; +${pluginsArray + .map(({ pathToPlugin, shortName }) => { + const req = `'${pathToPlugin}/admin/src'`; return `import ${shortName} from ${req};`; }) .join('\n')} -${localPluginsArray - .map(({ name, shortName }) => { - const req = `'../../../plugins/${name}/admin/src'`; - return `import ${shortName} from ${req};`; - }) - .join('\n')} const plugins = { -${[...appPluginsArray, ...localPluginsArray] +${[...pluginsArray] .map(({ name, shortName }) => { return ` '${name}': ${shortName},`; }) @@ -126,21 +131,6 @@ async function clean({ dir }) { fs.removeSync(cacheDir); } -async function copyPlugin(name, dest) { - const pkgFilePath = getPkgPath(name); - - const resolveDepPath = (...args) => path.resolve(pkgFilePath, ...args); - const resolveDest = (...args) => path.resolve(dest, 'plugins', name, ...args); - - const copy = (...args) => { - return fs.copy(resolveDepPath(...args), resolveDest(...args)); - }; - - // Copy the entire admin folder - await copy('admin'); - await copy('package.json'); -} - async function copyAdmin(dest) { const adminPath = getPkgPath('@strapi/admin'); @@ -154,25 +144,16 @@ async function copyAdmin(dest) { await fs.copy(path.resolve(adminPath, 'package.json'), path.resolve(dest, 'package.json')); } -async function createCacheDir(dir) { +async function createCacheDir({ dir, plugins }) { const cacheDir = path.resolve(dir, '.cache'); - const pkgJSON = require(path.join(dir, 'package.json')); - - const pluginsToCopy = Object.keys(pkgJSON.dependencies).filter( - dep => - dep.startsWith('@strapi/plugin') && - fs.existsSync(path.resolve(getPkgPath(dep), 'admin', 'src', 'index.js')) - ); - - let localPluginsToCopy = []; - if (fs.existsSync(path.join(dir, 'plugins'))) { - localPluginsToCopy = fs - .readdirSync(path.join(dir, 'plugins')) - .filter(plugin => - fs.existsSync(path.resolve(dir, 'plugins', plugin, 'admin', 'src', 'index.js')) - ); - } + const pluginsWithFront = Object.keys(plugins) + .filter(pluginName => { + const pluginInfo = plugins[pluginName]; + // TODO: use strapi-admin + return fs.existsSync(path.resolve(pluginInfo.pathToPlugin, 'admin', 'src', 'index.js')); + }) + .map(name => ({ name, ...plugins[name] })); // create .cache dir await fs.emptyDir(cacheDir); @@ -180,9 +161,6 @@ async function createCacheDir(dir) { // copy admin core code await copyAdmin(cacheDir); - // copy plugins code - await Promise.all(pluginsToCopy.map(name => copyPlugin(name, cacheDir))); - // Copy app.js const customAdminConfigFilePath = path.join(dir, 'admin', 'app.js'); @@ -198,27 +176,30 @@ async function createCacheDir(dir) { } // create plugins.js with plugins requires - await createPluginsJs(pluginsToCopy, localPluginsToCopy, cacheDir); + await createPluginsJs(pluginsWithFront, cacheDir); } -async function watchAdmin({ dir, host, port, browser, options }) { +async function watchAdmin({ plugins, dir, host, port, browser, options }) { // Create the cache dir containing the front-end files. - await createCacheDir(dir); + const cacheDir = path.join(dir, '.cache'); + await createCacheDir({ dir, plugins }); - const entry = path.join(dir, '.cache', 'admin', 'src'); + const entry = path.join(cacheDir, 'admin', 'src'); const dest = path.join(dir, 'build'); const env = 'development'; - const cacheDir = path.join(dir, '.cache'); - // Roots for the @strapi/babel-plugin-switch-ee-ce const roots = { eeRoot: path.resolve(cacheDir, 'ee', 'admin'), ceRoot: path.resolve(cacheDir, 'admin', 'src'), }; + const pluginsPath = Object.keys(plugins).map(pluginName => plugins[pluginName].pathToPlugin); + const args = { entry, + cacheDir, + pluginsPath, dest, env, port, diff --git a/packages/core/admin/scripts/build.js b/packages/core/admin/scripts/build.js index f9f7056f8c..04f4e4b00c 100644 --- a/packages/core/admin/scripts/build.js +++ b/packages/core/admin/scripts/build.js @@ -11,6 +11,8 @@ const buildAdmin = async () => { const args = { entry, dest, + cacheDir: __dirname, + pluginsPath: [path.resolve(__dirname, '../../../..')], env: 'production', optimize: true, options: { diff --git a/packages/core/admin/server/policies/hasPermissions.js b/packages/core/admin/server/policies/hasPermissions.js index 6b77c47de3..4fda9bee29 100644 --- a/packages/core/admin/server/policies/hasPermissions.js +++ b/packages/core/admin/server/policies/hasPermissions.js @@ -31,9 +31,9 @@ module.exports = createPolicyFactory( ); return (ctx, next) => { - const { userAbility: ability, isAuthenticatedAdmin } = ctx.state; + const { userAbility: ability, isAuthenticated } = ctx.state; - if (!isAuthenticatedAdmin || !ability) { + if (!isAuthenticated || !ability) { return next(); } diff --git a/packages/core/admin/server/policies/isAuthenticatedAdmin.js b/packages/core/admin/server/policies/isAuthenticatedAdmin.js index 90dc786865..3d3bc8b7b6 100644 --- a/packages/core/admin/server/policies/isAuthenticatedAdmin.js +++ b/packages/core/admin/server/policies/isAuthenticatedAdmin.js @@ -1,7 +1,7 @@ 'use strict'; module.exports = (ctx, next) => { - if (!ctx.state.isAuthenticatedAdmin) { + if (!ctx.state.isAuthenticated) { return ctx.unauthorized(); } diff --git a/packages/core/admin/server/register.js b/packages/core/admin/server/register.js index e2d7d9c86b..23ca2dc11c 100644 --- a/packages/core/admin/server/register.js +++ b/packages/core/admin/server/register.js @@ -3,56 +3,51 @@ // 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(); - } +const adminAuthStrategy = { + name: 'admin', + async authenticate(ctx) { + const { authorization } = ctx.request.header; - if ( - ctx.request.header.authorization && - ctx.request.header.authorization.split(' ')[0] === 'Bearer' - ) { - const token = ctx.request.header.authorization.split(' ')[1]; + if (!authorization) { + return { authenticated: false }; + } + const parts = authorization.split(/\s+/); + + if (parts[0] !== 'Bearer' || parts.length !== 2) { + return { authenticated: false }; + } + + const token = parts[1]; const { payload, isValid } = strapi.admin.services.token.decodeJwtToken(token); if (isValid) { - const admin = await strapi + const user = await strapi .query('admin::user') .findOne({ where: { id: payload.id }, populate: ['roles'] }); - if (!admin || !(admin.isActive === true)) { - return ctx.unauthorized('Invalid credentials'); + if (!user || !(user.isActive === true)) { + return { error: 'Invalid credentials' }; } - // TODO: use simple user & isAuthenticated + const userAbility = await strapi.admin.services.permission.engine.generateUserAbility(user); - ctx.state.admin = admin; - ctx.state.user = admin; - ctx.state.userAbility = await strapi.admin.services.permission.engine.generateUserAbility( - admin - ); + ctx.state.userAbility = userAbility; + ctx.state.user = user; - ctx.state.isAuthenticatedAdmin = true; - - return next(); + return { authenticated: true, credentials: user }; } - } - ctx.unauthorized('Invalid credentials'); + return { error: 'Invalid credentials' }; + }, + // async verify() {}, }; module.exports = () => { const passportMiddleware = strapi.admin.services.passport.init(); strapi.server.api('admin').use(passportMiddleware); - strapi.server.api('admin').use(authMiddleware); + strapi.container.get('auth').register('admin', adminAuthStrategy); // FIXME: to implement // strapi.db.migrations.register(permissionsFieldsToPropertiesMigration); diff --git a/packages/core/admin/server/routes/admin.js b/packages/core/admin/server/routes/admin.js new file mode 100644 index 0000000000..35ab8a9027 --- /dev/null +++ b/packages/core/admin/server/routes/admin.js @@ -0,0 +1,63 @@ +'use strict'; + +module.exports = [ + { + method: 'GET', + path: '/init', + handler: 'admin.init', + config: { auth: false }, + }, + { + method: 'GET', + path: '/project-type', + handler: 'admin.getProjectType', + config: { auth: false }, + }, + { + method: 'GET', + path: '/information', + handler: 'admin.information', + config: { + 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', + handler: 'admin.installPlugin', + config: { + policies: [ + 'admin::isAuthenticatedAdmin', + { + name: 'admin::hasPermissions', + options: { actions: ['admin::marketplace.plugins.install'] }, + }, + ], + }, + }, + { + method: 'DELETE', + path: '/plugins/uninstall/:plugin', + handler: 'admin.uninstallPlugin', + config: { + policies: [ + 'admin::isAuthenticatedAdmin', + { + name: 'admin::hasPermissions', + options: { actions: ['admin::marketplace.plugins.uninstall'] }, + }, + ], + }, + }, +]; diff --git a/packages/core/admin/server/routes/authentication.js b/packages/core/admin/server/routes/authentication.js new file mode 100644 index 0000000000..9b031e07ca --- /dev/null +++ b/packages/core/admin/server/routes/authentication.js @@ -0,0 +1,46 @@ +'use strict'; + +module.exports = [ + { + method: 'POST', + path: '/login', + handler: 'authentication.login', + config: { auth: false }, + }, + { + method: 'POST', + path: '/renew-token', + handler: 'authentication.renewToken', + config: { auth: false }, + }, + { + method: 'POST', + path: '/register-admin', + handler: 'authentication.registerAdmin', + config: { auth: false }, + }, + { + method: 'GET', + path: '/registration-info', + handler: 'authentication.registrationInfo', + config: { auth: false }, + }, + { + method: 'POST', + path: '/register', + handler: 'authentication.register', + config: { auth: false }, + }, + { + method: 'POST', + path: '/forgot-password', + handler: 'authentication.forgotPassword', + config: { auth: false }, + }, + { + method: 'POST', + path: '/reset-password', + handler: 'authentication.resetPassword', + config: { auth: false }, + }, +]; diff --git a/packages/core/admin/server/routes/index.js b/packages/core/admin/server/routes/index.js index f559f0946b..10fdf88795 100644 --- a/packages/core/admin/server/routes/index.js +++ b/packages/core/admin/server/routes/index.js @@ -1,325 +1,10 @@ 'use strict'; -module.exports = [ - { - method: 'GET', - path: '/init', - handler: 'admin.init', - }, - { - method: 'GET', - path: '/project-type', - handler: 'admin.getProjectType', - }, - { - method: 'GET', - path: '/information', - handler: 'admin.information', - config: { - 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', - handler: 'admin.installPlugin', - config: { - policies: [ - 'admin::isAuthenticatedAdmin', - { - name: 'admin::hasPermissions', - options: { actions: ['admin::marketplace.plugins.install'] }, - }, - ], - }, - }, - { - method: 'DELETE', - path: '/plugins/uninstall/:plugin', - handler: 'admin.uninstallPlugin', - config: { - policies: [ - 'admin::isAuthenticatedAdmin', - { - name: 'admin::hasPermissions', - options: { actions: ['admin::marketplace.plugins.uninstall'] }, - }, - ], - }, - }, - { - method: 'POST', - path: '/login', - handler: 'authentication.login', - }, - { - method: 'POST', - path: '/renew-token', - handler: 'authentication.renewToken', - }, - { - method: 'POST', - path: '/register-admin', - handler: 'authentication.registerAdmin', - }, - { - method: 'GET', - path: '/registration-info', - handler: 'authentication.registrationInfo', - }, - { - method: 'POST', - path: '/register', - handler: 'authentication.register', - }, - { - method: 'POST', - path: '/forgot-password', - handler: 'authentication.forgotPassword', - }, - { - method: 'POST', - path: '/reset-password', - handler: 'authentication.resetPassword', - }, - { - method: 'GET', - path: '/webhooks', - handler: 'Webhooks.listWebhooks', - config: { - policies: [ - 'admin::isAuthenticatedAdmin', - { name: 'admin::hasPermissions', options: { actions: ['admin::webhooks.read'] } }, - ], - }, - }, - { - method: 'POST', - path: '/webhooks', - handler: 'Webhooks.createWebhook', - config: { - policies: [ - 'admin::isAuthenticatedAdmin', - { name: 'admin::hasPermissions', options: { actions: ['admin::webhooks.create'] } }, - ], - }, - }, - { - method: 'GET', - path: '/webhooks/:id', - handler: 'Webhooks.getWebhook', - config: { - policies: [ - 'admin::isAuthenticatedAdmin', - { name: 'admin::hasPermissions', options: { actions: ['admin::webhooks.read'] } }, - ], - }, - }, - { - method: 'PUT', - path: '/webhooks/:id', - handler: 'Webhooks.updateWebhook', - config: { - policies: [ - 'admin::isAuthenticatedAdmin', - { name: 'admin::hasPermissions', options: { actions: ['admin::webhooks.update'] } }, - ], - }, - }, - { - method: 'DELETE', - path: '/webhooks/:id', - handler: 'Webhooks.deleteWebhook', - config: { - policies: [ - 'admin::isAuthenticatedAdmin', - { name: 'admin::hasPermissions', options: { actions: ['admin::webhooks.delete'] } }, - ], - }, - }, - { - method: 'POST', - path: '/webhooks/batch-delete', - handler: 'Webhooks.deleteWebhooks', - config: { - policies: [ - 'admin::isAuthenticatedAdmin', - { name: 'admin::hasPermissions', options: { actions: ['admin::webhooks.delete'] } }, - ], - }, - }, - { - method: 'POST', - path: '/webhooks/:id/trigger', - handler: 'Webhooks.triggerWebhook', - config: { - policies: [], - }, - }, - { - method: 'GET', - path: '/users/me', - handler: 'authenticated-user.getMe', - config: { - policies: ['admin::isAuthenticatedAdmin'], - }, - }, - { - method: 'PUT', - path: '/users/me', - handler: 'authenticated-user.updateMe', - config: { - policies: ['admin::isAuthenticatedAdmin'], - }, - }, - { - method: 'GET', - path: '/users/me/permissions', - handler: 'authenticated-user.getOwnPermissions', - config: { - policies: ['admin::isAuthenticatedAdmin'], - }, - }, - { - method: 'POST', - path: '/users', - handler: 'user.create', - config: { - policies: [ - 'admin::isAuthenticatedAdmin', - { name: 'admin::hasPermissions', options: { actions: ['admin::users.create'] } }, - ], - }, - }, - { - method: 'GET', - path: '/users', - handler: 'user.find', - config: { - policies: [ - 'admin::isAuthenticatedAdmin', - { name: 'admin::hasPermissions', options: { actions: ['admin::users.read'] } }, - ], - }, - }, - { - method: 'GET', - path: '/users/:id', - handler: 'user.findOne', - config: { - policies: [ - 'admin::isAuthenticatedAdmin', - { name: 'admin::hasPermissions', options: { actions: ['admin::users.read'] } }, - ], - }, - }, - { - method: 'PUT', - path: '/users/:id', - handler: 'user.update', - config: { - policies: [ - 'admin::isAuthenticatedAdmin', - { name: 'admin::hasPermissions', options: { actions: ['admin::users.update'] } }, - ], - }, - }, - { - method: 'DELETE', - path: '/users/:id', - handler: 'user.deleteOne', - config: { - policies: [{ name: 'admin::hasPermissions', options: { actions: ['admin::users.delete'] } }], - }, - }, - { - method: 'POST', - path: '/users/batch-delete', - handler: 'user.deleteMany', - config: { - policies: [{ name: 'admin::hasPermissions', options: { actions: ['admin::users.delete'] } }], - }, - }, - { - method: 'GET', - path: '/roles/:id/permissions', - handler: 'role.getPermissions', - config: { - policies: [ - 'admin::isAuthenticatedAdmin', - { name: 'admin::hasPermissions', options: { actions: ['admin::roles.read'] } }, - ], - }, - }, - { - method: 'PUT', - path: '/roles/:id/permissions', - handler: 'role.updatePermissions', - config: { - policies: [ - 'admin::isAuthenticatedAdmin', - { name: 'admin::hasPermissions', options: { actions: ['admin::roles.update'] } }, - ], - }, - }, - { - method: 'GET', - path: '/roles/:id', - handler: 'role.findOne', - config: { - policies: [ - 'admin::isAuthenticatedAdmin', - { name: 'admin::hasPermissions', options: { actions: ['admin::roles.read'] } }, - ], - }, - }, - { - method: 'GET', - path: '/roles', - handler: 'role.findAll', - config: { - policies: [ - 'admin::isAuthenticatedAdmin', - { name: 'admin::hasPermissions', options: { actions: ['admin::roles.read'] } }, - ], - }, - }, - { - method: 'PUT', - path: '/roles/:id', - handler: 'role.update', - config: { - policies: [ - 'admin::isAuthenticatedAdmin', - { name: 'admin::hasPermissions', options: { actions: ['admin::roles.update'] } }, - ], - }, - }, - { - method: 'GET', - path: '/permissions', - handler: 'permission.getAll', - config: { - policies: ['admin::isAuthenticatedAdmin'], - }, - }, - { - method: 'POST', - path: '/permissions/check', - handler: 'permission.check', - config: { - policies: ['admin::isAuthenticatedAdmin'], - }, - }, -]; +const admin = require('./admin'); +const authentication = require('./authentication'); +const permissions = require('./permissions'); +const users = require('./users'); +const roles = require('./roles'); +const webhooks = require('./webhooks'); + +module.exports = [...admin, ...authentication, ...permissions, ...users, ...roles, ...webhooks]; diff --git a/packages/core/admin/server/routes/permissions.js b/packages/core/admin/server/routes/permissions.js new file mode 100644 index 0000000000..5e279338e2 --- /dev/null +++ b/packages/core/admin/server/routes/permissions.js @@ -0,0 +1,20 @@ +'use strict'; + +module.exports = [ + { + method: 'GET', + path: '/permissions', + handler: 'permission.getAll', + config: { + policies: ['admin::isAuthenticatedAdmin'], + }, + }, + { + method: 'POST', + path: '/permissions/check', + handler: 'permission.check', + config: { + policies: ['admin::isAuthenticatedAdmin'], + }, + }, +]; diff --git a/packages/core/admin/server/routes/roles.js b/packages/core/admin/server/routes/roles.js new file mode 100644 index 0000000000..e181adac75 --- /dev/null +++ b/packages/core/admin/server/routes/roles.js @@ -0,0 +1,67 @@ +'use strict'; + +module.exports = [ + { + method: 'POST', + path: '/users/batch-delete', + handler: 'user.deleteMany', + config: { + policies: [{ name: 'admin::hasPermissions', options: { actions: ['admin::users.delete'] } }], + }, + }, + { + method: 'GET', + path: '/roles/:id/permissions', + handler: 'role.getPermissions', + config: { + policies: [ + 'admin::isAuthenticatedAdmin', + { name: 'admin::hasPermissions', options: { actions: ['admin::roles.read'] } }, + ], + }, + }, + { + method: 'PUT', + path: '/roles/:id/permissions', + handler: 'role.updatePermissions', + config: { + policies: [ + 'admin::isAuthenticatedAdmin', + { name: 'admin::hasPermissions', options: { actions: ['admin::roles.update'] } }, + ], + }, + }, + { + method: 'GET', + path: '/roles/:id', + handler: 'role.findOne', + config: { + policies: [ + 'admin::isAuthenticatedAdmin', + { name: 'admin::hasPermissions', options: { actions: ['admin::roles.read'] } }, + ], + }, + }, + { + method: 'GET', + path: '/roles', + handler: 'role.findAll', + config: { + policies: [ + 'admin::isAuthenticatedAdmin', + { name: 'admin::hasPermissions', options: { actions: ['admin::roles.read'] } }, + ], + }, + }, + { + method: 'PUT', + path: '/roles/:id', + handler: 'role.update', + config: { + policies: [ + 'admin::isAuthenticatedAdmin', + { name: 'admin::hasPermissions', options: { actions: ['admin::roles.update'] } }, + ], + }, + }, +]; diff --git a/packages/core/admin/server/routes/users.js b/packages/core/admin/server/routes/users.js new file mode 100644 index 0000000000..272fddc470 --- /dev/null +++ b/packages/core/admin/server/routes/users.js @@ -0,0 +1,88 @@ +'use strict'; + +module.exports = [ + { + method: 'GET', + path: '/users/me', + handler: 'authenticated-user.getMe', + config: { + policies: ['admin::isAuthenticatedAdmin'], + }, + }, + { + method: 'PUT', + path: '/users/me', + handler: 'authenticated-user.updateMe', + config: { + policies: ['admin::isAuthenticatedAdmin'], + }, + }, + { + method: 'GET', + path: '/users/me/permissions', + handler: 'authenticated-user.getOwnPermissions', + config: { + policies: ['admin::isAuthenticatedAdmin'], + }, + }, + { + method: 'POST', + path: '/users', + handler: 'user.create', + config: { + policies: [ + 'admin::isAuthenticatedAdmin', + { name: 'admin::hasPermissions', options: { actions: ['admin::users.create'] } }, + ], + }, + }, + { + method: 'GET', + path: '/users', + handler: 'user.find', + config: { + policies: [ + 'admin::isAuthenticatedAdmin', + { name: 'admin::hasPermissions', options: { actions: ['admin::users.read'] } }, + ], + }, + }, + { + method: 'GET', + path: '/users/:id', + handler: 'user.findOne', + config: { + policies: [ + 'admin::isAuthenticatedAdmin', + { name: 'admin::hasPermissions', options: { actions: ['admin::users.read'] } }, + ], + }, + }, + { + method: 'PUT', + path: '/users/:id', + handler: 'user.update', + config: { + policies: [ + 'admin::isAuthenticatedAdmin', + { name: 'admin::hasPermissions', options: { actions: ['admin::users.update'] } }, + ], + }, + }, + { + method: 'DELETE', + path: '/users/:id', + handler: 'user.deleteOne', + config: { + policies: [{ name: 'admin::hasPermissions', options: { actions: ['admin::users.delete'] } }], + }, + }, + { + method: 'POST', + path: '/users/batch-delete', + handler: 'user.deleteMany', + config: { + policies: [{ name: 'admin::hasPermissions', options: { actions: ['admin::users.delete'] } }], + }, + }, +]; diff --git a/packages/core/admin/server/routes/webhooks.js b/packages/core/admin/server/routes/webhooks.js new file mode 100644 index 0000000000..6e5eaa6574 --- /dev/null +++ b/packages/core/admin/server/routes/webhooks.js @@ -0,0 +1,78 @@ +'use strict'; + +module.exports = [ + { + method: 'GET', + path: '/webhooks', + handler: 'Webhooks.listWebhooks', + config: { + policies: [ + 'admin::isAuthenticatedAdmin', + { name: 'admin::hasPermissions', options: { actions: ['admin::webhooks.read'] } }, + ], + }, + }, + { + method: 'POST', + path: '/webhooks', + handler: 'Webhooks.createWebhook', + config: { + policies: [ + 'admin::isAuthenticatedAdmin', + { name: 'admin::hasPermissions', options: { actions: ['admin::webhooks.create'] } }, + ], + }, + }, + { + method: 'GET', + path: '/webhooks/:id', + handler: 'Webhooks.getWebhook', + config: { + policies: [ + 'admin::isAuthenticatedAdmin', + { name: 'admin::hasPermissions', options: { actions: ['admin::webhooks.read'] } }, + ], + }, + }, + { + method: 'PUT', + path: '/webhooks/:id', + handler: 'Webhooks.updateWebhook', + config: { + policies: [ + 'admin::isAuthenticatedAdmin', + { name: 'admin::hasPermissions', options: { actions: ['admin::webhooks.update'] } }, + ], + }, + }, + { + method: 'DELETE', + path: '/webhooks/:id', + handler: 'Webhooks.deleteWebhook', + config: { + policies: [ + 'admin::isAuthenticatedAdmin', + { name: 'admin::hasPermissions', options: { actions: ['admin::webhooks.delete'] } }, + ], + }, + }, + { + method: 'POST', + path: '/webhooks/batch-delete', + handler: 'Webhooks.deleteWebhooks', + config: { + policies: [ + 'admin::isAuthenticatedAdmin', + { name: 'admin::hasPermissions', options: { actions: ['admin::webhooks.delete'] } }, + ], + }, + }, + { + method: 'POST', + path: '/webhooks/:id/trigger', + handler: 'Webhooks.triggerWebhook', + config: { + policies: [], + }, + }, +]; diff --git a/packages/core/admin/webpack.config.dev.js b/packages/core/admin/webpack.config.dev.js index d2a4f9255c..e30d25008f 100644 --- a/packages/core/admin/webpack.config.dev.js +++ b/packages/core/admin/webpack.config.dev.js @@ -22,6 +22,8 @@ module.exports = () => { const args = { entry, + cacheDir: __dirname, + pluginsPath: [path.resolve(__dirname, '../../..')], dest, env, options, diff --git a/packages/core/admin/webpack.config.js b/packages/core/admin/webpack.config.js index 6285c61c6f..edac4c7cfe 100644 --- a/packages/core/admin/webpack.config.js +++ b/packages/core/admin/webpack.config.js @@ -13,6 +13,8 @@ const getClientEnvironment = require('./env'); module.exports = ({ entry, + cacheDir, + pluginsPath, dest, env, optimize, @@ -98,7 +100,7 @@ module.exports = ({ { test: /\.m?js$/, // TODO remove when plugins are built separately - exclude: /node_modules\/(?!(@strapi\/plugin-content-type-builder|@strapi\/plugin-upload)\/).*/, + include: [cacheDir, ...pluginsPath], use: { loader: require.resolve('babel-loader'), options: { diff --git a/packages/core/content-type-builder/admin/src/index.js b/packages/core/content-type-builder/admin/src/index.js index f233b48f32..1f4676bc2d 100644 --- a/packages/core/content-type-builder/admin/src/index.js +++ b/packages/core/content-type-builder/admin/src/index.js @@ -19,14 +19,22 @@ const name = pluginPkg.strapi.name; export default { register(app) { app.addReducers(reducers); - app.addCorePluginMenuLink({ + + app.addMenuLink({ to: `/plugins/${pluginId}`, icon, intlLabel: { id: `${pluginId}.plugin.name`, - defaultMessage: 'Content-Types Builder', + defaultMessage: 'Content Types Builder', }, permissions: pluginPermissions.main, + Component: async () => { + const component = await import( + /* webpackChunkName: "content-type-builder" */ './pages/App' + ); + + return component; + }, }); app.registerPlugin({ diff --git a/packages/core/email/server/routes/admin.js b/packages/core/email/server/routes/admin.js index 341550bb48..70b94e589b 100644 --- a/packages/core/email/server/routes/admin.js +++ b/packages/core/email/server/routes/admin.js @@ -7,7 +7,9 @@ module.exports = { method: 'POST', path: '/', handler: 'email.send', - config: {}, + config: { + policies: ['admin::isAuthenticatedAdmin'], + }, }, { method: 'POST', diff --git a/packages/core/strapi/lib/Strapi.js b/packages/core/strapi/lib/Strapi.js index 40c0699f96..e2dbba44cc 100644 --- a/packages/core/strapi/lib/Strapi.js +++ b/packages/core/strapi/lib/Strapi.js @@ -18,7 +18,7 @@ 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 createAuth = require('./services/auth'); const createUpdateNotifier = require('./utils/update-notifier'); const createStartupLogger = require('./utils/startup-logger'); const ee = require('./utils/ee'); @@ -53,7 +53,7 @@ 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.container.register('auth', createAuth(this)); this.isLoaded = false; this.reload = this.reload(); diff --git a/packages/core/strapi/lib/commands/build.js b/packages/core/strapi/lib/commands/build.js index 256d7f1e78..79545a76eb 100644 --- a/packages/core/strapi/lib/commands/build.js +++ b/packages/core/strapi/lib/commands/build.js @@ -3,20 +3,30 @@ const { green } = require('chalk'); const strapiAdmin = require('@strapi/admin'); const { getConfigUrls } = require('@strapi/utils'); -const loadConfiguration = require('../core/app-configuration'); -const ee = require('../utils/ee'); +const ee = require('../utils/ee'); const addSlash = require('../utils/addSlash'); +const strapi = require('../index'); +const getEnabledPlugins = require('../core/loaders/plugins/get-enabled-plugins'); + /** * `$ strapi build` */ module.exports = async ({ clean, optimization }) => { const dir = process.cwd(); - const config = loadConfiguration(dir); - const { serverUrl, adminPath } = getConfigUrls(config.server, true); + const strapiInstance = strapi({ + dir, + autoReload: true, + serveAdminPanel: false, + }); - console.log(`Building your admin UI with ${green(config.environment)} configuration ...`); + const plugins = await getEnabledPlugins(strapiInstance); + + const env = strapiInstance.config.get('environment'); + const { serverUrl, adminPath } = getConfigUrls(strapiInstance.config.get('server'), true); + + console.log(`Building your admin UI with ${green(env)} configuration ...`); if (clean) { await strapiAdmin.clean({ dir }); @@ -27,6 +37,7 @@ module.exports = async ({ clean, optimization }) => { return strapiAdmin .build({ dir, + plugins, // front end build env is always production for now env: 'production', optimize: optimization, diff --git a/packages/core/strapi/lib/commands/watchAdmin.js b/packages/core/strapi/lib/commands/watchAdmin.js index 9a68367b6a..b259316913 100644 --- a/packages/core/strapi/lib/commands/watchAdmin.js +++ b/packages/core/strapi/lib/commands/watchAdmin.js @@ -1,32 +1,42 @@ 'use strict'; const strapiAdmin = require('@strapi/admin'); -const { getOr } = require('lodash/fp'); const { getConfigUrls, getAbsoluteServerUrl } = require('@strapi/utils'); -const loadConfiguration = require('../core/app-configuration'); + const ee = require('../utils/ee'); const addSlash = require('../utils/addSlash'); +const strapi = require('../index'); +const getEnabledPlugins = require('../core/loaders/plugins/get-enabled-plugins'); module.exports = async function({ browser }) { const dir = process.cwd(); - const config = loadConfiguration(dir); + const strapiInstance = strapi({ + dir, + autoReload: true, + serveAdminPanel: false, + }); - const { adminPath } = getConfigUrls(config.server, true); + const plugins = await getEnabledPlugins(strapiInstance); - const adminPort = getOr(8000, 'server.admin.port')(config); - const adminHost = getOr('localhost', 'server.admin.host')(config); - const adminWatchIgnoreFiles = getOr([], 'server.admin.watchIgnoreFiles')(config); + const { adminPath } = getConfigUrls(strapiInstance.config.get('server'), true); + + const adminPort = strapiInstance.config.get('server.admin.port', 8000); + const adminHost = strapiInstance.config.get('server.admin.host', 'localhost'); + const adminWatchIgnoreFiles = strapiInstance.config.get('server.admin.watchIgnoreFiles', []); + + const backendURL = getAbsoluteServerUrl(strapiInstance.config, true); ee({ dir }); strapiAdmin.watchAdmin({ dir, + plugins, port: adminPort, host: adminHost, browser, options: { - backend: getAbsoluteServerUrl(config, true), + backend: backendURL, adminPath: addSlash(adminPath), watchIgnoreFiles: adminWatchIgnoreFiles, features: ee.isEE ? ee.features.getEnabled() : [], diff --git a/packages/core/strapi/lib/middlewares/index.js b/packages/core/strapi/lib/middlewares/index.js index 506ed3734e..6aab681925 100644 --- a/packages/core/strapi/lib/middlewares/index.js +++ b/packages/core/strapi/lib/middlewares/index.js @@ -3,6 +3,7 @@ const { uniq, difference, get, isUndefined, merge } = require('lodash'); const requiredMiddlewares = [ + 'auth', 'responses', 'router', 'logger', diff --git a/packages/core/strapi/lib/middlewares/public/index.js b/packages/core/strapi/lib/middlewares/public/index.js index e273850d5b..25479a8fcc 100644 --- a/packages/core/strapi/lib/middlewares/public/index.js +++ b/packages/core/strapi/lib/middlewares/public/index.js @@ -67,11 +67,13 @@ module.exports = strapi => { method: 'GET', path: '/', handler: serveIndexPage, + config: { auth: false }, }, { method: 'GET', path: '/index.html', handler: serveIndexPage, + config: { auth: false }, }, { method: 'GET', @@ -80,6 +82,7 @@ module.exports = strapi => { maxage: maxAge, defer: true, }), + config: { auth: false }, }, { method: 'GET', @@ -88,6 +91,7 @@ module.exports = strapi => { maxage: maxAge, defer: true, }), + config: { auth: false }, }, ]); } @@ -118,6 +122,7 @@ module.exports = strapi => { serveAdmin, serveStatic(buildDir, { maxage: maxAge, defer: false, index: 'index.html' }), ], + config: { auth: false }, }, ]); }, diff --git a/packages/core/strapi/lib/services/auth/index.js b/packages/core/strapi/lib/services/auth/index.js new file mode 100644 index 0000000000..093ad98de0 --- /dev/null +++ b/packages/core/strapi/lib/services/auth/index.js @@ -0,0 +1,92 @@ +'use strict'; + +const { strict: assert } = require('assert'); +const { has, prop } = 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('authenticate', strategy), INVALID_STRATEGY_MSG); + assert(typeof strategy.authenticate === 'function', INVALID_STRATEGY_MSG); + + if (has('verify', strategy)) { + assert(typeof strategy.verify === 'function', INVALID_STRATEGY_MSG); + } +}; + +const createAuthentication = () => { + const strategies = {}; + + return { + errors: { + UnauthorizedError, + ForbiddenError, + }, + register(type, strategy) { + validStrategy(strategy); + + if (!strategies[type]) { + strategies[type] = []; + } + + strategies[type].push(strategy); + + return this; + }, + async authenticate(ctx, next) { + const { route } = ctx.state; + + // use route strategy + const config = prop('config.auth', route); + + if (config === false) { + return next(); + } + + const strategiesToUse = strategies[route.info.type]; + + for (const strategy of strategiesToUse) { + const result = await strategy.authenticate(ctx); + + const { authenticated = false, error = null, credentials } = result || {}; + + if (error !== null) { + return ctx.unauthorized(error); + } + + if (authenticated) { + ctx.state.isAuthenticated = true; + ctx.state.auth = { + strategy, + credentials, + }; + + return next(); + } + } + + return ctx.unauthorized('Missing credentials'); + }, + async verify(auth, config = {}) { + if (config === false) { + return; + } + + if (!auth) { + throw new UnauthorizedError(); + } + + if (typeof auth.strategy.verify === 'function') { + return await auth.strategy.verify(auth, config); + } + + return; + }, + }; +}; + +module.exports = createAuthentication; diff --git a/packages/core/strapi/lib/services/content-api/index.js b/packages/core/strapi/lib/services/content-api/index.js deleted file mode 100644 index e500ea53d8..0000000000 --- a/packages/core/strapi/lib/services/content-api/index.js +++ /dev/null @@ -1,71 +0,0 @@ -'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/server/admin-api.js b/packages/core/strapi/lib/services/server/admin-api.js index 23f5b6e7b8..86c915a023 100644 --- a/packages/core/strapi/lib/services/server/admin-api.js +++ b/packages/core/strapi/lib/services/server/admin-api.js @@ -5,6 +5,7 @@ const { createAPI } = require('./api'); const createAdminAPI = strapi => { const opts = { prefix: '', // '/admin'; + type: 'admin', }; return createAPI(strapi, opts); diff --git a/packages/core/strapi/lib/services/server/api.js b/packages/core/strapi/lib/services/server/api.js index 02fadb0f1d..52c5dcab83 100644 --- a/packages/core/strapi/lib/services/server/api.js +++ b/packages/core/strapi/lib/services/server/api.js @@ -5,11 +5,11 @@ const Router = require('@koa/router'); const { createRouteManager } = require('./routing'); const createAPI = (strapi, opts = {}) => { - const { prefix, defaultPolicies } = opts; + const { prefix, type } = opts; const api = new Router({ prefix }); - const routeManager = createRouteManager(strapi, { defaultPolicies }); + const routeManager = createRouteManager(strapi, { type }); return { use(fn) { diff --git a/packages/core/strapi/lib/services/server/compose-endpoint.js b/packages/core/strapi/lib/services/server/compose-endpoint.js index 97e65ba5d8..73777b4419 100644 --- a/packages/core/strapi/lib/services/server/compose-endpoint.js +++ b/packages/core/strapi/lib/services/server/compose-endpoint.js @@ -1,6 +1,6 @@ 'use strict'; -const { toLower, castArray, trim } = require('lodash/fp'); +const { toLower, castArray, trim, prop } = require('lodash/fp'); const compose = require('koa-compose'); const { resolveMiddlewares } = require('./middleware'); @@ -9,24 +9,64 @@ const { resolvePolicies } = require('./policy'); const getMethod = route => trim(toLower(route.method)); const getPath = route => trim(route.path); -const routeInfoMiddleware = route => (ctx, next) => { +const createRouteInfoMiddleware = routeInfo => (ctx, next) => { + const route = { + ...routeInfo, + config: routeInfo.config || {}, + }; + ctx.state.route = route; return next(); }; +const getAuthConfig = prop('config.auth'); + +const createAuthorizeMiddleware = strapi => async (ctx, next) => { + const { auth, route } = ctx.state; + + const authService = strapi.container.get('auth'); + + try { + await authService.verify(auth, getAuthConfig(route)); + + return next(); + } catch (error) { + const { UnauthorizedError, ForbiddenError } = authService.errors; + + if (error instanceof UnauthorizedError) { + return ctx.unauthorized(); + } + + if (error instanceof ForbiddenError) { + return ctx.forbidden(); + } + + throw error; + } +}; + +const createAuthenticateMiddleware = strapi => async (ctx, next) => { + return strapi.container.get('auth').authenticate(ctx, next); +}; + module.exports = strapi => { - return (route, { pluginName, router, apiName }) => { + const authenticate = createAuthenticateMiddleware(strapi); + const authorize = createAuthorizeMiddleware(strapi); + + return (route, { router }) => { try { const method = getMethod(route); const path = getPath(route); const middlewares = resolveMiddlewares(route); - const policies = resolvePolicies(route, { pluginName, apiName }); + const policies = resolvePolicies(route); - const action = getAction(route, { pluginName, apiName }, strapi); + const action = getAction(route, strapi); const routeHandler = compose([ - routeInfoMiddleware(route), + createRouteInfoMiddleware(route), + authenticate, + authorize, ...policies, ...middlewares, ...castArray(action), @@ -52,7 +92,10 @@ const getController = (name, { pluginName, apiName }, strapi) => { return strapi.controller(name); }; -const getAction = ({ handler }, { pluginName, apiName }, strapi) => { +const getAction = (route, strapi) => { + const { handler, info = {} } = route; + const { pluginName, apiName } = info; + if (Array.isArray(handler) || typeof handler === 'function') { return handler; } diff --git a/packages/core/strapi/lib/services/server/content-api.js b/packages/core/strapi/lib/services/server/content-api.js index e1ec4a6fda..201ebb6c6b 100644 --- a/packages/core/strapi/lib/services/server/content-api.js +++ b/packages/core/strapi/lib/services/server/content-api.js @@ -1,50 +1,14 @@ '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)], + type: 'content-api', }; - const api = createAPI(strapi, opts); - - // implement auth providers - api.use((ctx, next) => { - return strapi.container.get('content-api').auth.authenticate(ctx, next); - }); - - return api; + return createAPI(strapi, opts); }; module.exports = { diff --git a/packages/core/strapi/lib/services/server/index.js b/packages/core/strapi/lib/services/server/index.js index d5ae9453cb..9d549f28d8 100644 --- a/packages/core/strapi/lib/services/server/index.js +++ b/packages/core/strapi/lib/services/server/index.js @@ -8,13 +8,9 @@ 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(); - } +const healthCheck = async ctx => { + ctx.set('strapi', 'You are so French!'); + ctx.status = 204; }; /** @@ -49,7 +45,7 @@ const createServer = strapi => { }; // init health check - app.use(healthCheck); + router.all('/_health', healthCheck); const state = { mounted: false, diff --git a/packages/core/strapi/lib/services/server/policy.js b/packages/core/strapi/lib/services/server/policy.js index 984f24bda1..f35668dda1 100644 --- a/packages/core/strapi/lib/services/server/policy.js +++ b/packages/core/strapi/lib/services/server/policy.js @@ -7,10 +7,14 @@ const { bodyPolicy } = policy; const getPoliciesConfig = propOr([], 'config.policies'); -const resolvePolicies = (route, opts = {}) => { +const resolvePolicies = route => { + const { pluginName, apiName } = route.info || {}; const policiesConfig = getPoliciesConfig(route); - const policies = policiesConfig.map(policyName => policy.get(policyName, opts)); + const policies = policiesConfig.map(policyName => { + return policy.get(policyName, { pluginName, apiName }); + }); + return [...policies, bodyPolicy]; }; diff --git a/packages/core/strapi/lib/services/server/routing.js b/packages/core/strapi/lib/services/server/routing.js index e696063845..7f7c442644 100644 --- a/packages/core/strapi/lib/services/server/routing.js +++ b/packages/core/strapi/lib/services/server/routing.js @@ -69,20 +69,16 @@ const validateRouteConfig = routeConfig => { }; const createRouteManager = (strapi, opts = {}) => { + const { type } = 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]); - } - } + _.set(route, 'info.type', type || 'admin'); - composeEndpoint(route, { ...route.info, router }); + composeEndpoint(route, { router }); }; const addRoutes = (routes, router) => { diff --git a/packages/core/upload/admin/src/index.js b/packages/core/upload/admin/src/index.js index f784d02912..e743075f7f 100644 --- a/packages/core/upload/admin/src/index.js +++ b/packages/core/upload/admin/src/index.js @@ -24,7 +24,7 @@ export default { // TODO update doc and guides app.addComponents({ name: 'media-library', Component: InputModalStepper }); - app.addCorePluginMenuLink({ + app.addMenuLink({ to: `/plugins/${pluginId}`, icon, intlLabel: { @@ -32,6 +32,11 @@ export default { defaultMessage: 'Media Library', }, permissions: pluginPermissions.main, + Component: async () => { + const component = await import(/* webpackChunkName: "upload" */ './pages/App'); + + return component; + }, }); // TODO update guide diff --git a/packages/core/upload/server/middlewares/upload.js b/packages/core/upload/server/middlewares/upload.js index 8756833ccd..a96bf57db3 100644 --- a/packages/core/upload/server/middlewares/upload.js +++ b/packages/core/upload/server/middlewares/upload.js @@ -32,6 +32,7 @@ module.exports = { method: 'GET', path: '/uploads/(.*)', handler: [range, koaStatic(staticDir, { defer: true, ...localServerConfig })], + config: { auth: false }, }, ]); }, diff --git a/packages/generators/app/lib/index.js b/packages/generators/app/lib/index.js index 3857b53949..f5d2f03458 100644 --- a/packages/generators/app/lib/index.js +++ b/packages/generators/app/lib/index.js @@ -50,8 +50,6 @@ module.exports = (projectDirectory, cliArguments) => { installDependencies: true, strapiDependencies: [ '@strapi/strapi', - '@strapi/admin', - '@strapi/utils', '@strapi/plugin-users-permissions', '@strapi/plugin-i18n', ], diff --git a/packages/plugins/users-permissions/server/auth/strategy.js b/packages/plugins/users-permissions/server/auth/strategy.js index 9f261f313b..01304e66b3 100644 --- a/packages/plugins/users-permissions/server/auth/strategy.js +++ b/packages/plugins/users-permissions/server/auth/strategy.js @@ -20,34 +20,34 @@ const authenticate = async ctx => { const { id } = await getService('jwt').getToken(ctx); if (id === undefined) { - throw new Error('Invalid token: Token did not contain required fields'); + return { 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, - }; + if (!user) { + return { error: 'Invalid credentials' }; } + + const advancedSettings = await getAdvancedSettings(); + + if (advancedSettings.email_confirmation && !user.confirmed) { + return { error: 'Invalid credentials' }; + } + + if (user.blocked) { + return { error: 'Invalid credentials' }; + } + + ctx.state.user = 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 }; + return { error: 'Invalid credentials' }; } } @@ -68,7 +68,7 @@ const authenticate = async ctx => { }; const verify = async (auth, config) => { - const { errors } = strapi.container.get('content-api'); + const { errors } = strapi.container.get('auth'); const { credentials: user } = auth; diff --git a/packages/plugins/users-permissions/server/register.js b/packages/plugins/users-permissions/server/register.js index c6454c832c..dd1a060fa1 100644 --- a/packages/plugins/users-permissions/server/register.js +++ b/packages/plugins/users-permissions/server/register.js @@ -3,5 +3,5 @@ const authStrategy = require('./auth/strategy'); module.exports = strapi => { - strapi.container.get('content-api').auth.register(authStrategy); + strapi.container.get('auth').register('content-api', authStrategy); }; diff --git a/test/helpers/strapi.js b/test/helpers/strapi.js index 6dbdf5955f..528d10c662 100644 --- a/test/helpers/strapi.js +++ b/test/helpers/strapi.js @@ -20,10 +20,8 @@ const createStrapiInstance = async ({ ensureSuperAdmin = true, logLevel = 'fatal const options = { dir: TEST_APP_URL }; const instance = strapi(options); - await instance.load(); - - instance.container.get('content-api').auth.register({ - name: 'test-strategy', + instance.container.get('auth').register('content-api', { + name: 'test-auth', authenticate() { return { authenticated: true }; }, @@ -32,6 +30,8 @@ const createStrapiInstance = async ({ ensureSuperAdmin = true, logLevel = 'fatal }, }); + await instance.load(); + instance.log.level = logLevel; instance.server.mount();