From 273c883eed3ba8a0554e4ab8d4817c54842f6a9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Vaux?= Date: Mon, 15 Jun 2020 09:58:49 +0200 Subject: [PATCH 01/19] docs: Fix a few typos in the 3rd party client guide (#6656) --- docs/v3.x/guides/client.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/v3.x/guides/client.md b/docs/v3.x/guides/client.md index 72027c737e..9ef2c3db4c 100644 --- a/docs/v3.x/guides/client.md +++ b/docs/v3.x/guides/client.md @@ -8,7 +8,7 @@ This guide could also be used to setup an Axios client instance. ## Installation -First you will have to install the client package in your application by running one of the following command. +First you will have to install the client package in your application by running one of the following commands: :::: tabs @@ -26,7 +26,7 @@ First you will have to install the client package in your application by running To init the client, we will use the [hooks system](../concepts/hooks.md). Hooks let you add new features in your Strapi application. -Hooks are loaded one time, at the server start. +Hooks are loaded once at server start. Lets create our GitHub hook. @@ -107,7 +107,7 @@ And here it is. You can now use `strapi.services.github` everywhere in your code to use the GitHub client. -To simply test if it works, lets update the `bootstrap.js` function to log your GitHub profile. +To simply test if it works, let's update the `bootstrap.js` function to log your GitHub profile. **Path —** `./config/functions/bootstrap.js` From f419e326d950c75146d96649d4f37ded0f26a1c0 Mon Sep 17 00:00:00 2001 From: Akash Gupta Date: Mon, 15 Jun 2020 13:38:54 +0530 Subject: [PATCH 02/19] Typo mistake (#6657) --- docs/v3.x/guides/custom-data-response.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/v3.x/guides/custom-data-response.md b/docs/v3.x/guides/custom-data-response.md index 7b342b043c..2235e81c4e 100644 --- a/docs/v3.x/guides/custom-data-response.md +++ b/docs/v3.x/guides/custom-data-response.md @@ -21,7 +21,7 @@ Let's consider you don't want to expose the chef's email for privacy reasons. To enforce this rule we will customize the action that fetches all restaurants and remove the email from the returned data. -To follow the example your will have to create a content type `restaurant` and add the following field definition: +To follow the example you will have to create a content type `restaurant` and add the following field definition: - `string` attribute named `name` - `text` attribute named `description` From 48a818041ece8930202b43d8327ff2c27e4c5870 Mon Sep 17 00:00:00 2001 From: Alexandre BODIN Date: Mon, 15 Jun 2020 10:34:59 +0200 Subject: [PATCH 03/19] Fix some user permission issue (#6629) * Fix some security issue Signed-off-by: Alexandre Bodin * compt node 10 Signed-off-by: Alexandre Bodin --- .../config/functions/bootstrap.js | 1 - .../config/policies/rateLimit.js | 2 +- .../controllers/Auth.js | 32 ++++----------- .../controllers/UsersPermissions.js | 13 +++++- .../__tests__/email-template.test.js | 28 +++++++++++++ .../controllers/validation/email-template.js | 40 +++++++++++++++++++ 6 files changed, 89 insertions(+), 27 deletions(-) create mode 100644 packages/strapi-plugin-users-permissions/controllers/validation/__tests__/email-template.test.js create mode 100644 packages/strapi-plugin-users-permissions/controllers/validation/email-template.js diff --git a/packages/strapi-plugin-users-permissions/config/functions/bootstrap.js b/packages/strapi-plugin-users-permissions/config/functions/bootstrap.js index cb220ab21f..f6d5bb58b6 100644 --- a/packages/strapi-plugin-users-permissions/config/functions/bootstrap.js +++ b/packages/strapi-plugin-users-permissions/config/functions/bootstrap.js @@ -122,7 +122,6 @@ module.exports = async () => { message: `

We heard that you lost your password. Sorry about that!

But don’t worry! You can use the following link to reset your password:

-

<%= URL %>?code=<%= TOKEN %>

Thanks.

`, diff --git a/packages/strapi-plugin-users-permissions/config/policies/rateLimit.js b/packages/strapi-plugin-users-permissions/config/policies/rateLimit.js index 92a0d20b1f..5db7634cfa 100644 --- a/packages/strapi-plugin-users-permissions/config/policies/rateLimit.js +++ b/packages/strapi-plugin-users-permissions/config/policies/rateLimit.js @@ -22,7 +22,7 @@ module.exports = async (ctx, next) => { { interval: 1 * 60 * 1000, max: 5, - prefixKey: `${ctx.request.url}:${ctx.request.ip}`, + prefixKey: `${ctx.request.path}:${ctx.request.ip}`, message, }, strapi.plugins['users-permissions'].config.ratelimit diff --git a/packages/strapi-plugin-users-permissions/controllers/Auth.js b/packages/strapi-plugin-users-permissions/controllers/Auth.js index ab25092442..c99419f8bc 100644 --- a/packages/strapi-plugin-users-permissions/controllers/Auth.js +++ b/packages/strapi-plugin-users-permissions/controllers/Auth.js @@ -315,16 +315,13 @@ module.exports = { key: 'advanced', }); + const userInfo = _.omit(user, ['password', 'resetPasswordToken', 'role', 'provider']); + settings.message = await strapi.plugins['users-permissions'].services.userspermissions.template( settings.message, { URL: advanced.email_reset_password, - USER: _.omit(user.toJSON ? user.toJSON() : user, [ - 'password', - 'resetPasswordToken', - 'role', - 'provider', - ]), + USER: userInfo, TOKEN: resetPasswordToken, } ); @@ -332,12 +329,7 @@ module.exports = { settings.object = await strapi.plugins['users-permissions'].services.userspermissions.template( settings.object, { - USER: _.omit(user.toJSON ? user.toJSON() : user, [ - 'password', - 'resetPasswordToken', - 'role', - 'provider', - ]), + USER: userInfo, } ); @@ -647,16 +639,13 @@ module.exports = { } }); + const userInfo = _.omit(user, ['password', 'resetPasswordToken', 'role', 'provider']); + settings.message = await strapi.plugins['users-permissions'].services.userspermissions.template( settings.message, { URL: `${strapi.config.server.url}/auth/email-confirmation`, - USER: _.omit(user.toJSON ? user.toJSON() : user, [ - 'password', - 'resetPasswordToken', - 'role', - 'provider', - ]), + USER: userInfo, CODE: jwt, } ); @@ -664,12 +653,7 @@ module.exports = { settings.object = await strapi.plugins['users-permissions'].services.userspermissions.template( settings.object, { - USER: _.omit(user.toJSON ? user.toJSON() : user, [ - 'password', - 'resetPasswordToken', - 'role', - 'provider', - ]), + USER: userInfo, } ); diff --git a/packages/strapi-plugin-users-permissions/controllers/UsersPermissions.js b/packages/strapi-plugin-users-permissions/controllers/UsersPermissions.js index 2d98c4529d..ba57d6570e 100644 --- a/packages/strapi-plugin-users-permissions/controllers/UsersPermissions.js +++ b/packages/strapi-plugin-users-permissions/controllers/UsersPermissions.js @@ -7,6 +7,7 @@ */ const _ = require('lodash'); +const { isValidEmailTemplate } = require('./validation/email-template'); module.exports = { /** @@ -196,6 +197,16 @@ module.exports = { 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: '', @@ -203,7 +214,7 @@ module.exports = { name: 'users-permissions', key: 'email', }) - .set({ value: ctx.request.body['email-templates'] }); + .set({ value: emailTemplates }); ctx.send({ ok: true }); }, diff --git a/packages/strapi-plugin-users-permissions/controllers/validation/__tests__/email-template.test.js b/packages/strapi-plugin-users-permissions/controllers/validation/__tests__/email-template.test.js new file mode 100644 index 0000000000..f4e6b054f1 --- /dev/null +++ b/packages/strapi-plugin-users-permissions/controllers/validation/__tests__/email-template.test.js @@ -0,0 +1,28 @@ +'use strict'; + +const { isValidEmailTemplate } = require('../email-template'); + +describe('isValidEmailTemplate', () => { + test('Accepts one valid pattern', () => { + expect(isValidEmailTemplate('<%= CODE %>')).toBe(true); + expect(isValidEmailTemplate('<%=CODE%>')).toBe(true); + }); + + test('Refuses invalid patterns', () => { + expect(isValidEmailTemplate('<%- CODE %>')).toBe(false); + expect(isValidEmailTemplate('<% CODE %>')).toBe(false); + expect(isValidEmailTemplate('<%= <% CODE %> %>')).toBe(false); + expect(isValidEmailTemplate('<%- <% CODE %> %>')).toBe(false); + expect(isValidEmailTemplate('${ <% CODE %> }')).toBe(false); + expect(isValidEmailTemplate('<%CODE%>')).toBe(false); + expect(isValidEmailTemplate('${CODE}')).toBe(false); + expect(isValidEmailTemplate('${ CODE }')).toBe(false); + }); + + test('Fails on non authorized keys', () => { + expect(isValidEmailTemplate('<% random expression %>')).toBe(false); + expect(isValidEmailTemplate('<% random expression }%>')).toBe(false); + expect(isValidEmailTemplate('<% some.var.azdazd %>')).toBe(false); + expect(isValidEmailTemplate('<% function() %>')).toBe(false); + }); +}); diff --git a/packages/strapi-plugin-users-permissions/controllers/validation/email-template.js b/packages/strapi-plugin-users-permissions/controllers/validation/email-template.js new file mode 100644 index 0000000000..ce455b59b8 --- /dev/null +++ b/packages/strapi-plugin-users-permissions/controllers/validation/email-template.js @@ -0,0 +1,40 @@ +'use strict'; + +const _ = require('lodash'); + +const invalidPatternsRegexes = [/<%[^=]([^<>%]*)%>/m, /\${([^{}]*)}/m]; +const authorizedKeys = ['URL', 'CODE', 'USER', 'USER.email', 'USER.username', 'TOKEN']; + +const matchAll = (pattern, src) => { + const matches = []; + let match; + + const regexPatternWithGlobal = RegExp(pattern, 'g'); + while ((match = regexPatternWithGlobal.exec(src))) { + const [, group] = match; + + matches.push(_.trim(group)); + } + return matches; +}; + +const isValidEmailTemplate = template => { + for (let reg of invalidPatternsRegexes) { + if (reg.test(template)) { + return false; + } + } + + const matches = matchAll(/<%=([^<>%=]*)%>/, template); + for (const match of matches) { + if (!authorizedKeys.includes(match)) { + return false; + } + } + + return true; +}; + +module.exports = { + isValidEmailTemplate, +}; From 88bc05f2dc36a5d3fa3140b50646dd47402b0436 Mon Sep 17 00:00:00 2001 From: bazzou789 Date: Mon, 15 Jun 2020 04:39:05 -0400 Subject: [PATCH 04/19] Modified admin.path to admin.url (#6601) --- docs/v3.x/admin-panel/customization.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/v3.x/admin-panel/customization.md b/docs/v3.x/admin-panel/customization.md index 82f531eac6..b026acd09e 100644 --- a/docs/v3.x/admin-panel/customization.md +++ b/docs/v3.x/admin-panel/customization.md @@ -20,7 +20,7 @@ module.exports = { enabled: false, }, admin: { - path: '/dashboard', + url: '/dashboard', }, }; ``` From 3e118da1476e45e4aa953cffabe43940bc676f89 Mon Sep 17 00:00:00 2001 From: Simon Date: Mon, 15 Jun 2020 10:39:50 +0200 Subject: [PATCH 05/19] Update slug system to new lifecycle hooks (#6572) --- docs/v3.x/guides/slug.md | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/docs/v3.x/guides/slug.md b/docs/v3.x/guides/slug.md index 7bf800b362..57a11f3848 100644 --- a/docs/v3.x/guides/slug.md +++ b/docs/v3.x/guides/slug.md @@ -84,12 +84,20 @@ module.exports = { const slugify = require('slugify'); module.exports = { - beforeSave: async (model, attrs, options) => { - if (options.method === 'insert' && attrs.title) { - model.set('slug', slugify(attrs.title)); - } else if (options.method === 'update' && attrs.title) { - attrs.slug = slugify(attrs.title); - } + /** + * Triggered before user creation. + */ + lifecycles: { + async beforeCreate(data) { + if (data.title) { + data.slug = slugify(data.title, {lower: true}); + } + }, + async beforeUpdate(params, data) { + if (data.title) { + data.slug = slugify(data.title, {lower: true}); + } + }, }, }; ``` From ba6695a87c89c81cde9acd926d13334a2ed2f74f Mon Sep 17 00:00:00 2001 From: Gomez23 <44360249+Gomez23@users.noreply.github.com> Date: Mon, 15 Jun 2020 11:00:18 +0200 Subject: [PATCH 06/19] Remove platform check (#6546) --- packages/strapi-plugin-users-permissions/controllers/Auth.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/strapi-plugin-users-permissions/controllers/Auth.js b/packages/strapi-plugin-users-permissions/controllers/Auth.js index c99419f8bc..375b9d3ec6 100644 --- a/packages/strapi-plugin-users-permissions/controllers/Auth.js +++ b/packages/strapi-plugin-users-permissions/controllers/Auth.js @@ -249,8 +249,7 @@ module.exports = { .get(); const [requestPath] = ctx.request.url.split('?'); - const provider = - process.platform === 'win32' ? requestPath.split('\\')[2] : requestPath.split('/')[2]; + const provider = requestPath.split('/')[2]; if (!_.get(grantConfig[provider], 'enabled')) { return ctx.badRequest(null, 'This provider is disabled.'); From d58a55dce611726c9670e91e67b5b8dd4253ff4c Mon Sep 17 00:00:00 2001 From: Fadhil Ahmad <15516786+fadhilx@users.noreply.github.com> Date: Mon, 15 Jun 2020 20:10:12 +0800 Subject: [PATCH 07/19] Update translation support on 'users-permissions' , 'upload' and 'content-manager' plugin (#6439) * fix: add Enable, Disable, and Done trad on email plugin Signed-off-by: Fadhil Ahmad * fix: add translation for dinamic zone required component error Signed-off-by: Fadhil Ahmad * fix: back to form.button.done, turns out other dont have form.button.done on strapi-admin Signed-off-by: Fadhil Ahmad * feat: add replace media translation support Signed-off-by: Fadhil Ahmad --- .../admin/src/components/DynamicZone/index.js | 4 +++- .../admin/src/translations/en.json | 1 + .../containers/InputModalStepper/InputModalStepper.js | 2 +- .../admin/src/containers/ModalStepper/index.js | 2 +- .../admin/src/translations/en.json | 3 ++- .../admin/src/components/ListRow/index.js | 10 +++++++--- .../admin/src/translations/en.json | 2 ++ 7 files changed, 17 insertions(+), 7 deletions(-) diff --git a/packages/strapi-plugin-content-manager/admin/src/components/DynamicZone/index.js b/packages/strapi-plugin-content-manager/admin/src/components/DynamicZone/index.js index 069e8c828a..3fae542312 100644 --- a/packages/strapi-plugin-content-manager/admin/src/components/DynamicZone/index.js +++ b/packages/strapi-plugin-content-manager/admin/src/components/DynamicZone/index.js @@ -142,7 +142,9 @@ const DynamicZone = ({ max, min, name }) => { }} /> {hasRequiredError && !isOpen && !hasMaxError && ( -
Component is required
+
+ +
)} {hasMaxError && !isOpen && (
diff --git a/packages/strapi-plugin-content-manager/admin/src/translations/en.json b/packages/strapi-plugin-content-manager/admin/src/translations/en.json index cfcc3dc941..163ca145b5 100644 --- a/packages/strapi-plugin-content-manager/admin/src/translations/en.json +++ b/packages/strapi-plugin-content-manager/admin/src/translations/en.json @@ -10,6 +10,7 @@ "components.DraggableAttr.edit": "Click to edit", "components.DynamicZone.add-compo": "Add to {componentName}", "components.DynamicZone.pick-compo": "Pick one component", + "components.DynamicZone.required": "Component is required", "components.DynamicZone.missing.singular": "There is {count} missing component", "components.DynamicZone.missing.plural": "There is {count} missing components", "components.EmptyAttributesBlock.button": "Go to settings page", diff --git a/packages/strapi-plugin-upload/admin/src/containers/InputModalStepper/InputModalStepper.js b/packages/strapi-plugin-upload/admin/src/containers/InputModalStepper/InputModalStepper.js index 8fb5f73c50..14b9e5dda3 100644 --- a/packages/strapi-plugin-upload/admin/src/containers/InputModalStepper/InputModalStepper.js +++ b/packages/strapi-plugin-upload/admin/src/containers/InputModalStepper/InputModalStepper.js @@ -385,7 +385,7 @@ const InputModalStepper = ({ isOpen, onToggle, noNavigation, onInputMediaChange onClick={handleReplaceMedia} style={{ marginRight: 10 }} > - Replace media + {formatMessage({ id: getTrad('control-card.replace-media') })}