Merge pull request #3822 from strapi/chore/routing-x-forwarded

Remove x-forwarded-host headers in admin panel and set default security options for development
This commit is contained in:
Alexandre BODIN 2019-09-24 10:04:31 +02:00 committed by GitHub
commit 6ed183d59d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
53 changed files with 591 additions and 809 deletions

View File

@ -97,11 +97,11 @@ This project is currently in **Beta**. Significant breaking changes are unlikely
## Features
- **Modern Admin Panel:** Elegant, entirely customizable, and a fully extensible admin panel.
- **Secure by default:** Reusable policies, CSRF, CORS, P3P, Xframe, XSS, and more.
- **Plugins Oriented:** Install auth systems, content management, custom plugins, and more, in seconds.
- **Blazing Fast:** Built on top of Node.js, Strapi delivers impressive performance.
- **Front-end Agnostic:** Use any front-end framework, for example, React, Vue, Angular, mobile apps, or even IoT.
- **Modern Admin Panel:** Elegant, entirely customizable and a fully extensible admin panel.
- **Secure by default:** Reusable policies, CORS, CSP, P3P, Xframe, XSS, and more.
- **Plugins Oriented:** Install auth system, content management, custom plugins, and more, in seconds.
- **Blazing Fast:** Built on top of Node.js, Strapi delivers amazing performance.
- **Front-end Agnostic:** Use any front-end framework (React, Vue, Angular, etc.), mobile apps or even IoT.
- **Powerful CLI:** Scaffold projects and APIs on the fly.
- **SQL & NoSQL databases:** Works with MongoDB, PostgreSQL, MySQL, MariaDB, and SQLite.

View File

@ -44,7 +44,6 @@ The core of Strapi embraces a small list of middlewares for performances, securi
- cors
- cron
- csp
- csrf
- favicon
- gzip
- hsts

View File

@ -396,10 +396,6 @@ The session doesn't work with `mongo` as a client. The package that we should us
**Path —** `./config/environments/**/security.json`.
- [`csrf`](https://en.wikipedia.org/wiki/Cross-site_request_forgery)
- `enabled` (boolean): Enable or disable CSRF. Default value: depends on the environment.
- `key` (string): The name of the CSRF token added to the model. Default value: `_csrf`.
- `secret` (string): The key to place on the session object which maps to the server side token. Default value: `_csrfSecret`.
- [`csp`](https://en.wikipedia.org/wiki/Content_Security_Policy)
- `enabled` (boolean): Enable or disable CSP to avoid Cross Site Scripting (XSS) and data injection attacks.
- [`p3p`](https://en.wikipedia.org/wiki/P3P)

View File

@ -15,6 +15,17 @@ You can simply copy and paste this code in your own controller file to customize
In the following example we will consider your controller, service and model is named `product`
:::
#### Utils
First require the utility functions
```js
const { parseMultipartData, sanitizeEntity } = require('strapi-utils');
```
- `parseMultipartData`: This function parses strapi's formData format.
- `sanitizeEntity`: This function removes all private fields from the model and its relations.
#### `find`
```js
@ -25,11 +36,15 @@ module.exports = {
* @return {Array}
*/
find(ctx) {
async find(ctx) {
let entities;
if (ctx.query._q) {
return strapi.services.product.search(ctx.query);
entities = await service.search(ctx.query);
} else {
entities = await service.find(ctx.query);
}
return strapi.services.product.find(ctx.query);
return entities.map(entity => sanitizeEntity(entity, { model }));
},
};
```
@ -44,8 +59,9 @@ module.exports = {
* @return {Object}
*/
findOne(ctx) {
return strapi.services.product.findOne(ctx.params);
async findOne(ctx) {
const entity = await service.findOne(ctx.params);
return sanitizeEntity(entity, { model });
},
};
```
@ -62,9 +78,9 @@ module.exports = {
count(ctx) {
if (ctx.query._q) {
return strapi.services.product.countSearch(ctx.query);
return service.countSearch(ctx.query);
}
return strapi.services.product.count(ctx.query);
return service.count(ctx.query);
},
};
```
@ -79,14 +95,15 @@ module.exports = {
* @return {Object}
*/
create(ctx) {
async create(ctx) {
let entity;
if (ctx.is('multipart')) {
// Parses strapi's formData format
const { data, files } = this.parseMultipartData(ctx);
return service.create(data, { files });
const { data, files } = parseMultipartData(ctx);
entity = await service.create(data, { files });
} else {
entity = await service.create(ctx.request.body);
}
return service.create(ctx.request.body);
return sanitizeEntity(entity, { model });
},
};
```
@ -101,14 +118,16 @@ module.exports = {
* @return {Object}
*/
update(ctx) {
async update(ctx) {
let entity;
if (ctx.is('multipart')) {
// Parses strapi's formData format
const { data, files } = this.parseMultipartData(ctx);
return service.update(ctx.params, data, { files });
const { data, files } = parseMultipartData(ctx);
entity = await service.update(ctx.params, data, { files });
} else {
entity = await service.update(ctx.params, ctx.request.body);
}
return service.update(ctx.params, ctx.request.body);
return sanitizeEntity(entity, { model });
},
};
```
@ -123,8 +142,9 @@ module.exports = {
* @return {Object}
*/
delete(ctx) {
return strapi.services.product.delete(ctx.params);
async delete(ctx) {
const entity = await service.delete(ctx.params);
return sanitizeEntity(entity, { model });
},
};
```

View File

@ -1,30 +1,28 @@
{
"csrf": {
"enabled": false,
"key": "_csrf",
"secret": "_csrfSecret"
},
"csp": {
"enabled": false,
"policy": {
"default-src": "'self'"
}
"enabled": true,
"policy": [
{
"img-src": "'self' http:"
},
"block-all-mixed-content"
]
},
"p3p": {
"enabled": false,
"value": ""
},
"hsts": {
"enabled": false,
"enabled": true,
"maxAge": 31536000,
"includeSubDomains": true
},
"xframe": {
"enabled": false,
"enabled": true,
"value": "SAMEORIGIN"
},
"xss": {
"enabled": false,
"enabled": true,
"mode": "block"
},
"cors": {

View File

@ -1,9 +1,4 @@
{
"csrf": {
"enabled": false,
"key": "_csrf",
"secret": "_csrfSecret"
},
"csp": {
"enabled": true,
"policy": [

View File

@ -1,9 +1,4 @@
{
"csrf": {
"enabled": false,
"key": "_csrf",
"secret": "_csrfSecret"
},
"csp": {
"enabled": true,
"policy": [

View File

@ -3,6 +3,10 @@
const execa = require('execa');
const _ = require('lodash');
const formatError = error => [
{ messages: [{ id: error.id, message: error.message, field: error.field }] },
];
/**
* A set of functions called "actions" for `Admin`
*/
@ -121,9 +125,38 @@ module.exports = {
async create(ctx) {
const { email, username, password, blocked } = ctx.request.body;
if (!email) return ctx.badRequest('missing.email');
if (!username) return ctx.badRequest('missing.username');
if (!password) return ctx.badRequest('missing.password');
if (!email) {
return ctx.badRequest(
null,
formatError({
id: 'missing.email',
message: 'Missing email',
field: ['email'],
})
);
}
if (!username) {
return ctx.badRequest(
null,
formatError({
id: 'missing.username',
message: 'Missing username',
field: ['username'],
})
);
}
if (!password) {
return ctx.badRequest(
null,
formatError({
id: 'missing.password',
message: 'Missing password',
field: ['password'],
})
);
}
const adminsWithSameEmail = await strapi
.query('administrator', 'admin')
@ -136,33 +169,22 @@ module.exports = {
if (adminsWithSameEmail) {
return ctx.badRequest(
null,
ctx.request.admin
? [
{
messages: [
{ id: 'Auth.form.error.email.taken', field: ['email'] },
],
},
]
: 'email.alreadyTaken'
formatError({
id: 'Auth.form.error.email.taken',
message: 'Email already taken',
field: ['email'],
})
);
}
if (adminsWithSameUsername) {
return ctx.badRequest(
null,
ctx.request.admin
? [
{
messages: [
{
id: 'Auth.form.error.username.taken',
field: ['username'],
},
],
},
]
: 'username.alreadyTaken.'
formatError({
id: 'Auth.form.error.username.taken',
message: 'Username already taken',
field: ['username'],
})
);
}
@ -189,10 +211,38 @@ module.exports = {
const { id } = ctx.params;
const { email, username, password, blocked } = ctx.request.body;
if (!email) return ctx.badRequest('Missing email');
if (!username) return ctx.badRequest('Missing username');
if (!password) return ctx.badRequest('Missing password');
if (!email) {
return ctx.badRequest(
null,
formatError({
id: 'missing.email',
message: 'Missing email',
field: ['email'],
})
);
}
if (!username) {
return ctx.badRequest(
null,
formatError({
id: 'missing.username',
message: 'Missing username',
field: ['username'],
})
);
}
if (!password) {
return ctx.badRequest(
null,
formatError({
id: 'missing.password',
message: 'Missing password',
field: ['password'],
})
);
}
const admin = await strapi
.query('administrator', 'admin')
.findOne(ctx.params);
@ -209,15 +259,11 @@ module.exports = {
if (adminsWithSameEmail && adminsWithSameEmail.id !== admin.id) {
return ctx.badRequest(
null,
ctx.request.admin
? [
{
messages: [
{ id: 'Auth.form.error.email.taken', field: ['email'] },
],
},
]
: 'Email is already taken.'
formatError({
id: 'Auth.form.error.email.taken',
message: 'Email already taken',
field: ['email'],
})
);
}
}
@ -231,18 +277,11 @@ module.exports = {
if (adminsWithSameUsername && adminsWithSameUsername.id !== admin.id) {
return ctx.badRequest(
null,
ctx.request.admin
? [
{
messages: [
{
id: 'Auth.form.error.username.taken',
field: ['username'],
},
],
},
]
: 'Username is already taken.'
formatError({
id: 'Auth.form.error.username.taken',
message: 'Username already taken',
field: ['username'],
})
);
}
}

View File

@ -11,6 +11,9 @@ const crypto = require('crypto');
const _ = require('lodash');
const emailRegExp = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
const formatError = error => [
{ messages: [{ id: error.id, message: error.message, field: error.field }] },
];
module.exports = {
async callback(ctx) {
@ -20,9 +23,10 @@ module.exports = {
if (!params.identifier) {
return ctx.badRequest(
null,
ctx.request.admin
? [{ messages: [{ id: 'Auth.form.error.email.provide' }] }]
: 'Please provide your username or your e-mail.'
formatError({
id: 'Auth.form.error.email.provide',
message: 'Please provide your username or your e-mail.',
})
);
}
@ -30,9 +34,10 @@ module.exports = {
if (!params.password) {
return ctx.badRequest(
null,
ctx.request.admin
? [{ messages: [{ id: 'Auth.form.error.password.provide' }] }]
: 'Please provide your password.'
formatError({
id: 'Auth.form.error.password.provide',
message: 'Please provide your password.',
})
);
}
@ -54,18 +59,20 @@ module.exports = {
if (!admin) {
return ctx.badRequest(
null,
ctx.request.admin
? [{ messages: [{ id: 'Auth.form.error.invalid' }] }]
: 'Identifier or password invalid.'
formatError({
id: 'Auth.form.error.invalid',
message: 'Identifier or password invalid.',
})
);
}
if (admin.blocked === true) {
return ctx.badRequest(
null,
ctx.request.admin
? [{ messages: [{ id: 'Auth.form.error.blocked' }] }]
: 'Your account has been blocked by the administrator.'
formatError({
id: 'Auth.form.error.blocked',
message: 'Your account has been blocked by the administrator.',
})
);
}
@ -77,9 +84,10 @@ module.exports = {
if (!validPassword) {
return ctx.badRequest(
null,
ctx.request.admin
? [{ messages: [{ id: 'Auth.form.error.invalid' }] }]
: 'Identifier or password invalid.'
formatError({
id: 'Auth.form.error.invalid',
message: 'Identifier or password invalid.',
})
);
} else {
admin.isAdmin = true;
@ -98,9 +106,10 @@ module.exports = {
if (!params.username) {
return ctx.badRequest(
null,
ctx.request.admin
? [{ messages: [{ id: 'Auth.form.error.username.provide' }] }]
: 'Please provide your username.'
formatError({
id: 'Auth.form.error.username.provide',
message: 'Please provide your username.',
})
);
}
@ -108,9 +117,10 @@ module.exports = {
if (!params.email) {
return ctx.badRequest(
null,
ctx.request.admin
? [{ messages: [{ id: 'Auth.form.error.email.provide' }] }]
: 'Please provide your email.'
formatError({
id: 'Auth.form.error.email.provide',
message: 'Please provide your email.',
})
);
}
@ -127,9 +137,10 @@ module.exports = {
if (!params.password) {
return ctx.badRequest(
null,
ctx.request.admin
? [{ messages: [{ id: 'Auth.form.error.password.provide' }] }]
: 'Please provide your password.'
formatError({
id: 'Auth.form.error.password.provide',
message: 'Please provide your password.',
})
);
}
@ -141,9 +152,10 @@ module.exports = {
if (admins.length > 0) {
return ctx.badRequest(
null,
ctx.request.admin
? [{ messages: [{ id: 'Auth.form.error.admin.exist' }] }]
: "You can't register a new admin."
formatError({
id: 'Auth.form.error.admin.exist',
message: "You can't register a new admin",
})
);
}
@ -158,9 +170,10 @@ module.exports = {
if (admin) {
return ctx.badRequest(
null,
ctx.request.admin
? [{ messages: [{ id: 'Auth.form.error.email.taken' }] }]
: 'Email is already taken.'
formatError({
id: 'Auth.form.error.email.taken',
message: 'Email is already taken',
})
);
}
@ -180,13 +193,13 @@ module.exports = {
} catch (err) {
strapi.log.error(err);
const adminError = _.includes(err.message, 'username')
? 'Auth.form.error.username.taken'
: 'Auth.form.error.email.taken';
? {
id: 'Auth.form.error.username.taken',
message: 'Username already taken',
}
: { id: 'Auth.form.error.email.taken', message: 'Email already taken' };
ctx.badRequest(
null,
ctx.request.admin ? [{ messages: [{ id: adminError }] }] : err.message
);
ctx.badRequest(null, formatError(adminError));
}
},
@ -196,17 +209,42 @@ module.exports = {
...ctx.params,
};
if (!password) return ctx.badRequest('Missing password');
if (!passwordConfirmation)
return ctx.badRequest('Missing passwordConfirmation');
if (!code) return ctx.badRequest('Missing code');
if (!password) {
return ctx.badRequest(
null,
formatError({
id: 'missing.password',
message: 'Missing password',
})
);
}
if (!passwordConfirmation) {
return ctx.badRequest(
formatError({
id: 'missing.passwordConfirmation',
message: 'Missing passwordConfirmation',
})
);
}
if (!code) {
return ctx.badRequest(
null,
formatError({
id: 'missing.code',
message: 'Missing code',
})
);
}
if (password !== passwordConfirmation) {
return ctx.badRequest(
null,
ctx.request.admin
? [{ messages: [{ id: 'Auth.form.error.password.matching' }] }]
: 'Passwords do not match.'
formatError({
id: 'Auth.form.error.password.matching',
message: 'Passwords do not match.',
})
);
}
@ -217,9 +255,10 @@ module.exports = {
if (!admin) {
return ctx.badRequest(
null,
ctx.request.admin
? [{ messages: [{ id: 'Auth.form.error.code.provide' }] }]
: 'Incorrect code provided.'
formatError({
id: 'Auth.form.error.code.provide',
message: 'Incorrect code provided.',
})
);
}
@ -241,8 +280,24 @@ module.exports = {
async forgotPassword(ctx) {
const { email, url } = ctx.request.body;
if (!email) return ctx.badRequest('Missing email');
if (!url) return ctx.badRequest('Missing url');
if (!email) {
return ctx.badRequest(
null,
formatError({
id: 'missing.email',
message: 'Missing email',
})
);
}
if (!url) {
return ctx.badRequest(
null,
formatError({
id: 'missing.url',
message: 'Missing url',
})
);
}
// Find the admin thanks to his email.
const admin = await strapi
@ -253,9 +308,10 @@ module.exports = {
if (!admin) {
return ctx.badRequest(
null,
ctx.request.admin
? [{ messages: [{ id: 'Auth.form.error.user.not-exist' }] }]
: 'This email does not exist.'
formatError({
id: 'Auth.form.error.user.not-exist',
message: 'This email does not exit',
})
);
}

View File

@ -1,30 +1,28 @@
{
"csrf": {
"enabled": false,
"key": "_csrf",
"secret": "_csrfSecret"
},
"csp": {
"enabled": false,
"policy": {
"default-src": "'self'"
}
"enabled": true,
"policy": [
{
"img-src": "'self' http:"
},
"block-all-mixed-content"
]
},
"p3p": {
"enabled": false,
"value": ""
},
"hsts": {
"enabled": false,
"enabled": true,
"maxAge": 31536000,
"includeSubDomains": true
},
"xframe": {
"enabled": false,
"enabled": true,
"value": "SAMEORIGIN"
},
"xss": {
"enabled": false,
"enabled": true,
"mode": "block"
},
"cors": {

View File

@ -1,9 +1,4 @@
{
"csrf": {
"enabled": false,
"key": "_csrf",
"secret": "_csrfSecret"
},
"csp": {
"enabled": true,
"policy": [

View File

@ -1,9 +1,4 @@
{
"csrf": {
"enabled": false,
"key": "_csrf",
"secret": "_csrfSecret"
},
"csp": {
"enabled": true,
"policy": [

View File

@ -135,10 +135,7 @@ export default function request(...args) {
{
'Content-Type': 'application/json',
},
options.headers,
{
'X-Forwarded-Host': 'strapi',
}
options.headers
);
}

View File

@ -178,7 +178,7 @@ module.exports = function createQueryBuilder({ model, modelKey, strapi }) {
const runDelete = async trx => {
await deleteGroups(entry, { transacting: trx });
await model.forge(params).destroy({ transacting: trx, require: false });
return entry;
return entry.toJSON();
};
return wrapTransaction(runDelete, { transacting });
@ -217,7 +217,8 @@ module.exports = function createQueryBuilder({ model, modelKey, strapi }) {
})
.fetchAll({
withRelated: populate,
});
})
.then(results => results.toJSON());
}
function countSearch(params) {

View File

@ -199,9 +199,9 @@ module.exports = ({ model, modelKey, strapi }) => {
model,
filters,
populate: populateOpt,
}).then(results => {
return results.map(result => (result ? result.toObject() : null));
});
}).then(results =>
results.map(result => (result ? result.toObject() : null))
);
}
async function findOne(params, populate) {
@ -326,7 +326,7 @@ module.exports = ({ model, modelKey, strapi }) => {
})
);
return entry;
return entry.toObject ? entry.toObject() : null;
}
function search(params, populate) {
@ -340,7 +340,10 @@ module.exports = ({ model, modelKey, strapi }) => {
.sort(filters.sort)
.skip(filters.start)
.limit(filters.limit)
.populate(populate || defaultPopulate);
.populate(populate || defaultPopulate)
.then(results =>
results.map(result => (result ? result.toObject() : null))
);
}
function countSearch(params) {

View File

@ -673,9 +673,7 @@ class Wysiwyg extends React.Component {
uploadFile = files => {
const formData = new FormData();
formData.append('files', files[0]);
const headers = {
'X-Forwarded-Host': 'strapi',
};
const headers = {};
let newEditorState = this.getEditorState();

View File

@ -265,7 +265,7 @@ function EditView({
});
// Change the request helper default headers so we can pass a FormData
const headers = { 'X-Forwarded-Host': 'strapi' };
const headers = {};
const method = isCreatingEntry ? 'POST' : 'PUT';
const endPoint = isCreatingEntry ? slug : `${slug}/${id}`;

View File

@ -95,12 +95,13 @@ module.exports = {
strapi.emit('didCreateFirstContentTypeEntry', ctx.params, source);
} catch (error) {
strapi.log.error(error);
ctx.badRequest(
null,
ctx.request.admin
? [{ messages: [{ id: error.message, field: error.field }] }]
: error.message
);
ctx.badRequest(null, [
{
messages: [
{ id: error.message, message: error.message, field: error.field },
],
},
]);
}
},
@ -131,12 +132,13 @@ module.exports = {
}
} catch (error) {
strapi.log.error(error);
ctx.badRequest(
null,
ctx.request.admin
? [{ messages: [{ id: error.message, field: error.field }] }]
: error.message
);
ctx.badRequest(null, [
{
messages: [
{ id: error.message, message: error.message, field: error.field },
],
},
]);
}
},

View File

@ -9,18 +9,26 @@
const _ = require('lodash');
module.exports = {
send: async (ctx) => {
send: async ctx => {
// Retrieve provider configuration.
const config = await strapi.store({
environment: strapi.config.environment,
type: 'plugin',
name: 'email'
}).get({ key: 'provider' });
const config = await strapi
.store({
environment: strapi.config.environment,
type: 'plugin',
name: 'email',
})
.get({ key: 'provider' });
// Verify if the file email is enable.
if (config.enabled === false) {
strapi.log.error('Email is disabled');
return ctx.badRequest(null, ctx.request.admin ? [{ messages: [{ id: 'Email.status.disabled' }] }] : 'Emailis disabled');
return ctx.badRequest(null, [
{
messages: [
{ id: 'Email.status.disabled', message: 'Emails disabled' },
],
},
]);
}
// Something is wrong
@ -36,33 +44,40 @@ module.exports = {
ctx.send({});
},
getEnvironments: async (ctx) => {
const environments = _.map(_.keys(strapi.config.environments), environment => {
return {
name: environment,
active: (strapi.config.environment === environment)
};
});
getEnvironments: async ctx => {
const environments = _.map(
_.keys(strapi.config.environments),
environment => {
return {
name: environment,
active: strapi.config.environment === environment,
};
}
);
ctx.send({ environments });
},
getSettings: async (ctx) => {
let config = await strapi.plugins.email.services.email.getProviderConfig(ctx.params.environment);
getSettings: async ctx => {
let config = await strapi.plugins.email.services.email.getProviderConfig(
ctx.params.environment
);
ctx.send({
providers: strapi.plugins.email.config.providers,
config
config,
});
},
updateSettings: async (ctx) => {
await strapi.store({
environment: ctx.params.environment,
type: 'plugin',
name: 'email'
}).set({key: 'provider', value: ctx.request.body});
updateSettings: async ctx => {
await strapi
.store({
environment: ctx.params.environment,
type: 'plugin',
name: 'email',
})
.set({ key: 'provider', value: ctx.request.body });
ctx.send({ok: true});
ctx.send({ ok: true });
},
};

View File

@ -54,11 +54,6 @@
"form.security.description": "تكوين إعدادات الأمان الخاصة بك.",
"form.security.item.cors": "Cors",
"form.security.item.cors.origin": "Origin",
"form.security.item.csrf": "CSRF",
"form.security.item.csrf.angular": "الزاوي",
"form.security.item.csrf.cookie": "الكعكات",
"form.security.item.csrf.key": "المفتاح",
"form.security.item.csrf.secret": "الحماية",
"form.security.item.hsts": "المضيفين",
"form.security.item.hsts.includeSubDomains": "تضمين المجال الفرعي",
"form.security.item.hsts.maxAge": "أقصى عمر",
@ -629,4 +624,4 @@
"strapi.notification.success.languageAdd": "تمت إضافة اللغة بنجاح.",
"strapi.notification.success.languageDelete": "تم حذف اللغة بنجاح.",
"strapi.notification.success.settingsEdit": "تم تحديث الإعدادات بنجاح."
}
}

View File

@ -54,11 +54,6 @@
"form.security.description": "Verwalte deine Sicherheitseinstellungen.",
"form.security.item.cors": "Cors",
"form.security.item.cors.origin": "Origin",
"form.security.item.csrf": "CSRF",
"form.security.item.csrf.angular": "Angular",
"form.security.item.csrf.cookie": "Cookie",
"form.security.item.csrf.key": "Key",
"form.security.item.csrf.secret": "Secret",
"form.security.item.hsts": "HOSTS",
"form.security.item.hsts.includeSubDomains": "Subdomain mit einschließen",
"form.security.item.hsts.maxAge": "Max Age",

View File

@ -54,11 +54,6 @@
"form.security.description": "Configure your security settings.",
"form.security.item.cors": "Cors",
"form.security.item.cors.origin": "Origin",
"form.security.item.csrf": "CSRF",
"form.security.item.csrf.angular": "Angular",
"form.security.item.csrf.cookie": "Cookie",
"form.security.item.csrf.key": "Key",
"form.security.item.csrf.secret": "Secret",
"form.security.item.hsts": "HOSTS",
"form.security.item.hsts.includeSubDomains": "Include Sub Domain",
"form.security.item.hsts.maxAge": "Max Age",

View File

@ -54,11 +54,6 @@
"form.security.description": "Configurar las opciones de seguridad.",
"form.security.item.cors": "Cors",
"form.security.item.cors.origin": "Origen",
"form.security.item.csrf": "CSRF",
"form.security.item.csrf.angular": "Angular",
"form.security.item.csrf.cookie": "Cookie",
"form.security.item.csrf.key": "Clave",
"form.security.item.csrf.secret": "Secreto",
"form.security.item.hsts": "HOSTS",
"form.security.item.hsts.includeSubDomains": "Incluir Sub Dominio",
"form.security.item.hsts.maxAge": "Edad Máxima",

View File

@ -54,12 +54,6 @@
"form.security.description": "Configurations de sécurité.",
"form.security.item.cors": "Cors",
"form.security.item.cors.origin": "Origine",
"form.security.item.csrf": "CSRF",
"form.security.item.csrf.angular": "Angular",
"form.security.item.csrf.cookie": "Cookie",
"form.security.item.csrf.key": "Clé",
"form.security.item.csrf.secret": "Clé secrète",
"form.security.item.hsts": "HOSTS",
"form.security.item.hsts.includeSubDomains": "Inclure les sous-domaines",
"form.security.item.hsts.maxAge": "Max Age",
"form.security.item.hsts.preload": "Preload",

View File

@ -54,11 +54,6 @@
"form.security.description": "Configurare le impostazioni di sicurezza.",
"form.security.item.cors": "Cors",
"form.security.item.cors.origin": "Origine",
"form.security.item.csrf": "CSRF",
"form.security.item.csrf.angular": "Angolare",
"form.security.item.csrf.cookie": "Cookie",
"form.security.item.csrf.key": "Chiave",
"form.security.item.csrf.secret": "Segreto",
"form.security.item.hsts": "OSPITA",
"form.security.item.hsts.includeSubDomains": "Includi domini secondari",
"form.security.item.hsts.maxAge": "Età Max",
@ -629,4 +624,4 @@
"strapi.notification.success.languageAdd": "Il linguaggio è stato aggiunto con successo.",
"strapi.notification.success.languageDelete": "Il linguaggio è stato eliminato con successo.",
"strapi.notification.success.settingsEdit": "Le impostazioni sono state aggiornate con successo."
}
}

View File

@ -54,11 +54,6 @@
"form.security.description": "セキュリティ設定を構成します。",
"form.security.item.cors": "Cors",
"form.security.item.cors.origin": "Origin",
"form.security.item.csrf": "CSRF",
"form.security.item.csrf.angular": "Angular",
"form.security.item.csrf.cookie": "Cookie",
"form.security.item.csrf.key": "Key",
"form.security.item.csrf.secret": "Secret",
"form.security.item.hsts": "HOSTS",
"form.security.item.hsts.includeSubDomains": "サブドメインを含める",
"form.security.item.hsts.maxAge": "Max Age",

View File

@ -54,11 +54,6 @@
"form.security.description": "보안 환경을 설정하세요.",
"form.security.item.cors": "Cors",
"form.security.item.cors.origin": "Origin",
"form.security.item.csrf": "CSRF",
"form.security.item.csrf.angular": "앵귤러 (Angular)",
"form.security.item.csrf.cookie": "쿠키 (Cookie)",
"form.security.item.csrf.key": "키 (Key)",
"form.security.item.csrf.secret": "시크릿 (Secret)",
"form.security.item.hsts": "호스트 (HOSTS)",
"form.security.item.hsts.includeSubDomains": "서브 도메인 포함",
"form.security.item.hsts.maxAge": "만료 (Max Age)",
@ -629,4 +624,4 @@
"strapi.notification.success.languageAdd": "언어를 추가했습니다.",
"strapi.notification.success.languageDelete": "언어를 제거했습니다.",
"strapi.notification.success.settingsEdit": "설정을 업데이트했습니다."
}
}

View File

@ -54,11 +54,6 @@
"form.security.description": "Configureer je beveiliging instellingen.",
"form.security.item.cors": "Cors",
"form.security.item.cors.origin": "Origin",
"form.security.item.csrf": "CSRF",
"form.security.item.csrf.angular": "Angular",
"form.security.item.csrf.cookie": "Cookie",
"form.security.item.csrf.key": "Key",
"form.security.item.csrf.secret": "Secret",
"form.security.item.hsts": "HOSTS",
"form.security.item.hsts.includeSubDomains": "Inclusief Sub Domain",
"form.security.item.hsts.maxAge": "Max Leeftijd",
@ -634,4 +629,4 @@
"strapi.notification.success.languageAdd": "De taal is succesvol toegevoegd.",
"strapi.notification.success.languageDelete": "De taal is succesvol verwijderd.",
"strapi.notification.success.settingsEdit": "De instellingen zijn succesvol geüpdatet"
}
}

View File

@ -54,11 +54,6 @@
"form.security.description": "Konfiguruj ustawienia bezpieczeństwa.",
"form.security.item.cors": "Cors",
"form.security.item.cors.origin": "Źródło",
"form.security.item.csrf": "CSRF",
"form.security.item.csrf.angular": "Angular",
"form.security.item.csrf.cookie": "Cookie",
"form.security.item.csrf.key": "Klucz",
"form.security.item.csrf.secret": "Sekretny klucz",
"form.security.item.hsts": "HOSTS",
"form.security.item.hsts.includeSubDomains": "Subdomeny",
"form.security.item.hsts.maxAge": "Max Age",
@ -634,4 +629,4 @@
"strapi.notification.success.languageAdd": "Język został pomyślnie dodany.",
"strapi.notification.success.languageDelete": "Język został pomyślnie usunięty.",
"strapi.notification.success.settingsEdit": "Ustawienia zostały pomyślnie zmienione."
}
}

View File

@ -54,11 +54,6 @@
"form.security.description": "Defina as suas configurações de segurança.",
"form.security.item.cors": "CORS",
"form.security.item.cors.origin": "Origem",
"form.security.item.csrf": "CSRF",
"form.security.item.csrf.angular": "Angular",
"form.security.item.csrf.cookie": "Cookie",
"form.security.item.csrf.key": "Chave",
"form.security.item.csrf.secret": "Segredo",
"form.security.item.hsts": "HOSTS",
"form.security.item.hsts.includeSubDomains": "Incluir Sub Domínio",
"form.security.item.hsts.maxAge": "Idade máxima",
@ -629,4 +624,4 @@
"strapi.notification.success.languageAdd": "O idioma foi adicionado com sucesso.",
"strapi.notification.success.languageDelete": "O idioma foi removido com sucesso.",
"strapi.notification.success.settingsEdit": "As configurações foram atualizadas com sucesso."
}
}

View File

@ -54,11 +54,6 @@
"form.security.description": "Configure as suas definições de segurança.",
"form.security.item.cors": "Cors",
"form.security.item.cors.origin": "Origem",
"form.security.item.csrf": "CSRF",
"form.security.item.csrf.angular": "Angular",
"form.security.item.csrf.cookie": "Cookie",
"form.security.item.csrf.key": "Chave",
"form.security.item.csrf.secret": "Secreta",
"form.security.item.hsts": "HOSTS",
"form.security.item.hsts.includeSubDomains": "Incluir Sub Domínio",
"form.security.item.hsts.maxAge": "Idade máxima",
@ -634,4 +629,4 @@
"strapi.notification.success.languageAdd": "O idioma foi adicionado com sucesso.",
"strapi.notification.success.languageDelete": "O idioma foi excluido com sucesso.",
"strapi.notification.success.settingsEdit": "As configurações foram actualizadas com sucesso."
}
}

View File

@ -54,11 +54,6 @@
"form.security.description": "Задайте настройки безопасности.",
"form.security.item.cors": "Cors",
"form.security.item.cors.origin": "Origin",
"form.security.item.csrf": "CSRF",
"form.security.item.csrf.angular": "Angular",
"form.security.item.csrf.cookie": "Cookie",
"form.security.item.csrf.key": "Key",
"form.security.item.csrf.secret": "Secret",
"form.security.item.hsts": "HOSTS",
"form.security.item.hsts.includeSubDomains": "Include Sub Domain",
"form.security.item.hsts.maxAge": "Max Age",

View File

@ -54,11 +54,6 @@
"form.security.description": "Güvenlik ayarlarınızı yapılandırın.",
"form.security.item.cors": "Cors",
"form.security.item.cors.origin": "Origin",
"form.security.item.csrf": "CSRF",
"form.security.item.csrf.angular": "Açısal",
"form.security.item.csrf.cookie": "Çerez",
"form.security.item.csrf.key": "Anahtar",
"form.security.item.csrf.secret": "Gizli",
"form.security.item.hsts": "HOSTS",
"form.security.item.hsts.includeSubDomains": "Alt Etki Alanını Dahil Et",
"form.security.item.hsts.maxAge": "Max Age",
@ -634,4 +629,4 @@
"strapi.notification.success.languageAdd": "Dil başarıyla eklendi.",
"strapi.notification.success.languageDelete": "Dil başarıyla silindi.",
"strapi.notification.success.settingsEdit": "Ayarlar başarıyla güncellendi."
}
}

View File

@ -53,11 +53,6 @@
"form.security.description": "配置安全设置。",
"form.security.item.cors": "Cors",
"form.security.item.cors.origin": "Origin",
"form.security.item.csrf": "CSRF",
"form.security.item.csrf.angular": "Angular",
"form.security.item.csrf.cookie": "Cookie",
"form.security.item.csrf.key": "Key",
"form.security.item.csrf.secret": "Secret",
"form.security.item.hsts": "HOSTS",
"form.security.item.hsts.includeSubDomains": "包括子域",
"form.security.item.hsts.maxAge": "Max Age",
@ -628,4 +623,4 @@
"strapi.notification.success.languageAdd": "该语言已成功添加。",
"strapi.notification.success.languageDelete": "数据库已被成功删除。",
"strapi.notification.success.settingsEdit": "设置已成功更新。"
}
}

View File

@ -54,11 +54,6 @@
"form.security.description": "調整安全性設定",
"form.security.item.cors": "跨域資源共享 (Cors)",
"form.security.item.cors.origin": "來源",
"form.security.item.csrf": "跨站請求偽造保護 (CSRF)",
"form.security.item.csrf.angular": "使用 Angular",
"form.security.item.csrf.cookie": "Cookie",
"form.security.item.csrf.key": "鍵值",
"form.security.item.csrf.secret": "密鑰鍵值",
"form.security.item.hsts": "強制安全傳輸 (HSTS)",
"form.security.item.hsts.includeSubDomains": "包含子域名",
"form.security.item.hsts.maxAge": "作用期",

View File

@ -285,70 +285,6 @@ module.exports = {
name: 'form.security.name',
description: 'form.security.description',
sections: [
{
name: 'form.security.item.csrf',
items: [
{
name: 'form.security.item.csrf.enabled',
target: 'security.csrf.enabled',
type: 'boolean',
value: _.get(
strapi.config,
`environments.${env}.security.csrf.enabled`,
null
),
items: [
{
name: 'form.security.item.csrf.key',
target: 'security.csrf.key',
type: 'string',
value: _.get(
strapi.config,
`environments.${env}.security.csrf.key`,
null
),
validations: {},
},
{
name: 'form.security.item.csrf.secret',
target: 'security.csrf.secret',
type: 'string',
value: _.get(
strapi.config,
`environments.${env}.security.csrf.secret`,
null
),
validations: {},
},
{
name: 'form.security.item.csrf.cookie',
target: 'security.csrf.cookie',
type: 'string',
value: _.get(
strapi.config,
`environments.${env}.security.csrf.cookie`,
null
),
validations: {},
},
{
name: 'form.security.item.csrf.angular',
target: 'security.csrf.angular',
type: 'boolean',
value: _.get(
strapi.config,
`environments.${env}.security.csrf.angular`,
null
),
validations: {},
},
],
validations: {
required: true,
},
},
],
},
{
name: 'form.security.item.p3p',
items: [

View File

@ -51,9 +51,7 @@ function* dataGet() {
function* uploadFiles(action) {
try {
yield put(setLoading());
const headers = {
'X-Forwarded-Host': 'strapi',
};
const headers = {};
const response = yield call(
request,
'/upload',

View File

@ -25,9 +25,17 @@ module.exports = {
if (config.enabled === false) {
return ctx.badRequest(
null,
ctx.request.admin
? [{ messages: [{ id: 'Upload.status.disabled' }] }]
: 'File upload is disabled'
[
{
messages: [
{
id: 'Upload.status.disabled',
message: 'File upload is disabled',
},
],
},
]
);
}
@ -36,12 +44,11 @@ module.exports = {
const { files = {} } = ctx.request.files || {};
if (_.isEmpty(files)) {
return ctx.badRequest(
null,
ctx.request.admin
? [{ messages: [{ id: 'Upload.status.empty' }] }]
: 'Files are empty'
);
return ctx.badRequest(null, [
{
messages: [{ id: 'Upload.status.empty', message: 'Files are empty' }],
},
]);
}
// Transform stream files to buffer
@ -49,21 +56,17 @@ module.exports = {
const enhancedFiles = buffers.map(file => {
if (file.size > config.sizeLimit) {
return ctx.badRequest(
null,
ctx.request.admin
? [
{
messages: [
{
id: 'Upload.status.sizeLimit',
values: { file: file.name },
},
],
},
]
: `${file.name} file is bigger than limit size!`
);
return ctx.badRequest(null, [
{
messages: [
{
id: 'Upload.status.sizeLimit',
message: `${file.name} file is bigger than limit size!`,
values: { file: file.name },
},
],
},
]);
}
// Add details to the file to be able to create the relationships.

View File

@ -153,7 +153,7 @@ describe('Upload plugin end to end tests', () => {
expect(res.statusCode).toBe(400);
expect(res.body).toMatchObject({
message: 'File upload is disabled',
message: [{ messages: [{ message: 'File upload is disabled' }] }],
});
});
@ -164,7 +164,7 @@ describe('Upload plugin end to end tests', () => {
expect(res.statusCode).toBe(400);
expect(res.body).toMatchObject({
message: 'Files are empty',
message: [{ messages: [{ message: 'Files are empty' }] }],
});
});
@ -181,7 +181,11 @@ describe('Upload plugin end to end tests', () => {
expect(res.statusCode).toBe(400);
expect(res.body).toMatchObject({
message: 'rec.jpg file is bigger than limit size!',
message: [
{
messages: [{ message: 'rec.jpg file is bigger than limit size!' }],
},
],
});
});
});

View File

@ -5,9 +5,16 @@ const lazyRateLimit = {
};
module.exports = async (ctx, next) => {
const message = ctx.request.admin
? [{ messages: [{ id: 'Auth.form.error.ratelimit' }] }]
: 'Too many attempts, please try again in a minute.';
const message = [
{
messages: [
{
id: 'Auth.form.error.ratelimit',
message: 'Too many attempts, please try again in a minute.',
},
],
},
];
return lazyRateLimit.RateLimit.middleware(
Object.assign(

View File

@ -10,8 +10,12 @@
const crypto = require('crypto');
const _ = require('lodash');
const grant = require('grant-koa');
const { sanitizeEntity } = require('strapi-utils');
const emailRegExp = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
const formatError = error => [
{ messages: [{ id: error.id, message: error.message, field: error.field }] },
];
module.exports = {
async callback(ctx) {
@ -25,10 +29,7 @@ module.exports = {
});
if (provider === 'local') {
if (
!_.get(await store.get({ key: 'grant' }), 'email.enabled') &&
!ctx.request.admin
) {
if (!_.get(await store.get({ key: 'grant' }), 'email.enabled')) {
return ctx.badRequest(null, 'This provider is disabled.');
}
@ -36,9 +37,10 @@ module.exports = {
if (!params.identifier) {
return ctx.badRequest(
null,
ctx.request.admin
? [{ messages: [{ id: 'Auth.form.error.email.provide' }] }]
: 'Please provide your username or your e-mail.'
formatError({
id: 'Auth.form.error.email.provide',
message: 'Please provide your username or your e-mail.',
})
);
}
@ -46,9 +48,10 @@ module.exports = {
if (!params.password) {
return ctx.badRequest(
null,
ctx.request.admin
? [{ messages: [{ id: 'Auth.form.error.password.provide' }] }]
: 'Please provide your password.'
formatError({
id: 'Auth.form.error.password.provide',
message: 'Please provide your password.',
})
);
}
@ -72,9 +75,10 @@ module.exports = {
if (!user) {
return ctx.badRequest(
null,
ctx.request.admin
? [{ messages: [{ id: 'Auth.form.error.invalid' }] }]
: 'Identifier or password invalid.'
formatError({
id: 'Auth.form.error.invalid',
message: 'Identifier or password invalid.',
})
);
}
@ -84,18 +88,20 @@ module.exports = {
) {
return ctx.badRequest(
null,
ctx.request.admin
? [{ messages: [{ id: 'Auth.form.error.confirmed' }] }]
: 'Your account email is not confirmed.'
formatError({
id: 'Auth.form.error.confirmed',
message: 'Your account email is not confirmed',
})
);
}
if (user.blocked === true) {
return ctx.badRequest(
null,
ctx.request.admin
? [{ messages: [{ id: 'Auth.form.error.blocked' }] }]
: 'Your account has been blocked by the administrator.'
formatError({
id: 'Auth.form.error.blocked',
message: 'Your account has been blocked by an administrator',
})
);
}
@ -103,9 +109,11 @@ module.exports = {
if (!user.password) {
return ctx.badRequest(
null,
ctx.request.admin
? [{ messages: [{ id: 'Auth.form.error.password.local' }] }]
: 'This user never set a local password, please login thanks to the provider used during account creation.'
formatError({
id: 'Auth.form.error.password.local',
message:
'This user never set a local password, please login thanks to the provider used during account creation.',
})
);
}
@ -116,24 +124,30 @@ module.exports = {
if (!validPassword) {
return ctx.badRequest(
null,
ctx.request.admin
? [{ messages: [{ id: 'Auth.form.error.invalid' }] }]
: 'Identifier or password invalid.'
formatError({
id: 'Auth.form.error.invalid',
message: 'Identifier or password invalid.',
})
);
} else {
ctx.send({
jwt: strapi.plugins['users-permissions'].services.jwt.issue({
id: user.id,
}),
user: _.omit(user.toJSON ? user.toJSON() : user, [
'password',
'resetPasswordToken',
]),
user: sanitizeEntity(user.toJSON ? user.toJSON() : user, {
model: strapi.query('user', 'users-permissions').model,
}),
});
}
} else {
if (!_.get(await store.get({ key: 'grant' }), [provider, 'enabled'])) {
return ctx.badRequest(null, 'This provider is disabled.');
return ctx.badRequest(
null,
formatError({
id: 'provider.disabled',
message: 'This provider is disabled.',
})
);
}
// Connect the user thanks to the third-party provider.
@ -143,27 +157,20 @@ module.exports = {
'users-permissions'
].services.providers.connect(provider, ctx.query);
} catch ([user, error]) {
return ctx.badRequest(
null,
error === 'array' ? (ctx.request.admin ? error[0] : error[1]) : error
);
return ctx.badRequest(null, error === 'array' ? error[0] : error);
}
if (!user) {
return ctx.badRequest(
null,
error === 'array' ? (ctx.request.admin ? error[0] : error[1]) : error
);
return ctx.badRequest(null, error === 'array' ? error[0] : error);
}
ctx.send({
jwt: strapi.plugins['users-permissions'].services.jwt.issue({
id: user.id,
}),
user: _.omit(user.toJSON ? user.toJSON() : user, [
'password',
'resetPasswordToken',
]),
user: sanitizeEntity(user.toJSON ? user.toJSON() : user, {
model: strapi.query('user', 'users-permissions').model,
}),
});
}
},
@ -184,9 +191,10 @@ module.exports = {
if (!user) {
return ctx.badRequest(
null,
ctx.request.admin
? [{ messages: [{ id: 'Auth.form.error.code.provide' }] }]
: 'Incorrect code provided.'
formatError({
id: 'Auth.form.error.code.provide',
message: 'Incorrect code provided.',
})
);
}
@ -206,10 +214,9 @@ module.exports = {
jwt: strapi.plugins['users-permissions'].services.jwt.issue({
id: user.id,
}),
user: _.omit(user.toJSON ? user.toJSON() : user, [
'password',
'resetPasswordToken',
]),
user: sanitizeEntity(user.toJSON ? user.toJSON() : user, {
model: strapi.query('user', 'users-permissions').model,
}),
});
} else if (
params.password &&
@ -218,16 +225,18 @@ module.exports = {
) {
return ctx.badRequest(
null,
ctx.request.admin
? [{ messages: [{ id: 'Auth.form.error.password.matching' }] }]
: 'Passwords do not match.'
formatError({
id: 'Auth.form.error.password.matching',
message: 'Passwords do not match.',
})
);
} else {
return ctx.badRequest(
null,
ctx.request.admin
? [{ messages: [{ id: 'Auth.form.error.params.provide' }] }]
: 'Incorrect params provided.'
formatError({
id: 'Auth.form.error.params.provide',
message: 'Incorrect params provided.',
})
);
}
},
@ -270,9 +279,10 @@ module.exports = {
if (!user) {
return ctx.badRequest(
null,
ctx.request.admin
? [{ messages: [{ id: 'Auth.form.error.user.not-exist' }] }]
: 'This email does not exist.'
formatError({
id: 'Auth.form.error.user.not-exist',
message: 'This email does not exist.',
})
);
}
@ -353,9 +363,10 @@ module.exports = {
if (!settings.allow_register) {
return ctx.badRequest(
null,
ctx.request.admin
? [{ messages: [{ id: 'Auth.advanced.allow_register' }] }]
: 'Register action is currently disabled.'
formatError({
id: 'Auth.advanced.allow_register',
message: 'Register action is currently disabled.',
})
);
}
@ -367,9 +378,10 @@ module.exports = {
if (!params.password) {
return ctx.badRequest(
null,
ctx.request.admin
? [{ messages: [{ id: 'Auth.form.error.password.provide' }] }]
: 'Please provide your password.'
formatError({
id: 'Auth.form.error.password.provide',
message: 'Please provide your password.',
})
);
}
@ -377,9 +389,10 @@ module.exports = {
if (!params.email) {
return ctx.badRequest(
null,
ctx.request.admin
? [{ messages: [{ id: 'Auth.form.error.email.provide' }] }]
: 'Please provide your email.'
formatError({
id: 'Auth.form.error.email.provide',
message: 'Please provide your email.',
})
);
}
@ -392,9 +405,11 @@ module.exports = {
) {
return ctx.badRequest(
null,
ctx.request.admin
? [{ messages: [{ id: 'Auth.form.error.password.format' }] }]
: 'Your password cannot contain more than three times the symbol `$`.'
formatError({
id: 'Auth.form.error.password.format',
message:
'Your password cannot contain more than three times the symbol `$`.',
})
);
}
@ -405,9 +420,10 @@ module.exports = {
if (!role) {
return ctx.badRequest(
null,
ctx.request.admin
? [{ messages: [{ id: 'Auth.form.error.role.notFound' }] }]
: 'Impossible to find the default role.'
formatError({
id: 'Auth.form.error.role.notFound',
message: 'Impossible to find the default role.',
})
);
}
@ -430,18 +446,20 @@ module.exports = {
if (user && user.provider === params.provider) {
return ctx.badRequest(
null,
ctx.request.admin
? [{ messages: [{ id: 'Auth.form.error.email.taken' }] }]
: 'Email is already taken.'
formatError({
id: 'Auth.form.error.email.taken',
message: 'Email is already taken.',
})
);
}
if (user && user.provider !== params.provider && settings.unique_email) {
return ctx.badRequest(
null,
ctx.request.admin
? [{ messages: [{ id: 'Auth.form.error.email.taken' }] }]
: 'Email is already taken.'
formatError({
id: 'Auth.form.error.email.taken',
message: 'Email is already taken.',
})
);
}
@ -515,20 +533,19 @@ module.exports = {
ctx.send({
jwt,
user: _.omit(user.toJSON ? user.toJSON() : user, [
'password',
'resetPasswordToken',
]),
user: sanitizeEntity(user.toJSON ? user.toJSON() : user, {
model: strapi.query('user', 'users-permissions').model,
}),
});
} catch (err) {
const adminError = _.includes(err.message, 'username')
? 'Auth.form.error.username.taken'
: 'Auth.form.error.email.taken';
? {
id: 'Auth.form.error.username.taken',
message: 'Username already taken',
}
: { id: 'Auth.form.error.email.taken', message: 'Email already taken' };
ctx.badRequest(
null,
ctx.request.admin ? [{ messages: [{ id: adminError }] }] : err.message
);
ctx.badRequest(null, formatError(adminError));
}
},

View File

@ -7,10 +7,15 @@
*/
const _ = require('lodash');
const { sanitizeEntity } = require('strapi-utils');
const sanitizeUser = user => _.omit(user, ['password', 'resetPasswordToken']);
const adminError = error => [
{ messages: [{ id: error.message, field: error.field }] },
const sanitizeUser = user =>
sanitizeEntity(user, {
model: strapi.query('user', 'users-permissions').model,
});
const formatError = error => [
{ messages: [{ id: error.id, message: error.message, field: error.field }] },
];
module.exports = {
@ -99,12 +104,11 @@ module.exports = {
if (userWithSameUsername) {
return ctx.badRequest(
null,
ctx.request.admin
? adminError({
message: 'Auth.form.error.username.taken',
field: ['username'],
})
: 'username.alreadyTaken.'
formatError({
id: 'Auth.form.error.username.taken',
message: 'Username already taken.',
field: ['username'],
})
);
}
@ -116,12 +120,12 @@ module.exports = {
if (userWithSameEmail) {
return ctx.badRequest(
null,
ctx.request.admin
? adminError({
message: 'Auth.form.error.email.taken',
field: ['email'],
})
: 'email.alreadyTaken'
formatError({
id: 'Auth.form.error.email.taken',
message: 'Email already taken.',
field: ['email'],
})
);
}
}
@ -146,10 +150,7 @@ module.exports = {
ctx.created(data);
} catch (error) {
ctx.badRequest(
null,
ctx.request.admin ? adminError(error) : error.message
);
ctx.badRequest(null, formatError(error));
}
},
@ -190,12 +191,11 @@ module.exports = {
if (userWithSameUsername && userWithSameUsername.id != id) {
return ctx.badRequest(
null,
ctx.request.admin
? adminError({
message: 'Auth.form.error.username.taken',
field: ['username'],
})
: 'username.alreadyTaken.'
formatError({
id: 'Auth.form.error.username.taken',
message: 'username.alreadyTaken.',
field: ['username'],
})
);
}
}
@ -208,12 +208,11 @@ module.exports = {
if (userWithSameEmail && userWithSameEmail.id != id) {
return ctx.badRequest(
null,
ctx.request.admin
? adminError({
message: 'Auth.form.error.email.taken',
field: ['email'],
})
: 'email.alreadyTaken'
formatError({
id: 'Auth.form.error.email.taken',
message: 'Eamil already taken',
field: ['email'],
})
);
}
}

View File

@ -6,6 +6,8 @@
const convertRestQueryParams = require('./convertRestQueryParams');
const buildQuery = require('./buildQuery');
const parseMultipartData = require('./parse-multipart');
const sanitizeEntity = require('./sanitize-entity');
module.exports = {
cli: require('./cli'),
@ -21,4 +23,6 @@ module.exports = {
templateConfiguration: require('./templateConfiguration'),
convertRestQueryParams,
buildQuery,
parseMultipartData,
sanitizeEntity,
};

View File

@ -0,0 +1,38 @@
'use strict';
module.exports = function sanitizeEntity(data, { model, withPrivate = false }) {
if (typeof data !== 'object' || data == null) return data;
let plainData = typeof data.toJSON === 'function' ? data.toJSON() : data;
const attributes = model.attributes;
return Object.keys(plainData).reduce((acc, key) => {
const attribute = attributes[key];
if (attribute && attribute.private === true && withPrivate !== true) {
return acc;
}
if (
attribute &&
(attribute.model || attribute.collection || attribute.type === 'group')
) {
const targetName =
attribute.model || attribute.collection || attribute.group;
const targetModel = strapi.getModel(targetName, attribute.plugin);
if (targetModel && plainData[key] !== null) {
acc[key] = Array.isArray(plainData[key])
? plainData[key].map(entity =>
sanitizeEntity(entity, { model: targetModel, withPrivate })
)
: sanitizeEntity(plainData[key], { model: targetModel, withPrivate });
return acc;
}
}
acc[key] = plainData[key];
return acc;
}, {});
};

View File

@ -1,17 +1,13 @@
'use strict';
const parseMultipartData = require('./utils/parse-multipart');
const proto = {
parseMultipartData,
};
const { parseMultipartData, sanitizeEntity } = require('strapi-utils');
/**
* default bookshelf controller
*
*/
module.exports = ({ service }) => {
return Object.assign(Object.create(proto), {
module.exports = ({ service, model }) => {
return {
/**
* expose some utils so the end users can use them
*/
@ -22,11 +18,15 @@ module.exports = ({ service }) => {
* @return {Object|Array}
*/
find(ctx) {
async find(ctx) {
let entities;
if (ctx.query._q) {
return service.search(ctx.query);
entities = await service.search(ctx.query);
} else {
entities = await service.find(ctx.query);
}
return service.find(ctx.query);
return entities.map(entity => sanitizeEntity(entity, { model }));
},
/**
@ -35,8 +35,9 @@ module.exports = ({ service }) => {
* @return {Object}
*/
findOne(ctx) {
return service.findOne(ctx.params);
async findOne(ctx) {
const entity = await service.findOne(ctx.params);
return sanitizeEntity(entity, { model });
},
/**
@ -58,13 +59,15 @@ module.exports = ({ service }) => {
* @return {Object}
*/
create(ctx) {
async create(ctx) {
let entity;
if (ctx.is('multipart')) {
const { data, files } = this.parseMultipartData(ctx);
return service.create(data, { files });
const { data, files } = parseMultipartData(ctx);
entity = await service.create(data, { files });
} else {
entity = await service.create(ctx.request.body);
}
return service.create(ctx.request.body);
return sanitizeEntity(entity, { model });
},
/**
@ -73,13 +76,16 @@ module.exports = ({ service }) => {
* @return {Object}
*/
update(ctx) {
async update(ctx) {
let entity;
if (ctx.is('multipart')) {
const { data, files } = this.parseMultipartData(ctx);
return service.update(ctx.params, data, { files });
const { data, files } = parseMultipartData(ctx);
entity = await service.update(ctx.params, data, { files });
} else {
entity = await service.update(ctx.params, ctx.request.body);
}
return service.update(ctx.params, ctx.request.body);
return sanitizeEntity(entity, { model });
},
/**
@ -88,8 +94,9 @@ module.exports = ({ service }) => {
* @return {Object}
*/
delete(ctx) {
return service.delete(ctx.params);
async delete(ctx) {
const entity = await service.delete(ctx.params);
return sanitizeEntity(entity, { model });
},
});
};
};

View File

@ -77,7 +77,7 @@ module.exports = function(strapi) {
);
const controller = Object.assign(
createController({ service }),
createController({ service, model }),
userController,
{ identity: userController.identity || _.upperFirst(index) }
);
@ -209,10 +209,6 @@ module.exports = function(strapi) {
boom: {
enabled: true,
},
mask: {
enabled: true,
},
// Necessary middlewares for the administration panel.
cors: {
enabled: true,
},

View File

@ -10,13 +10,7 @@ const defaults = {
maxAge: 31536000,
credentials: true,
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'],
headers: [
'Content-Type',
'Authorization',
'Origin',
'Accept',
'X-Forwarded-Host',
],
headers: ['Content-Type', 'Authorization', 'Origin', 'Accept'],
keepHeadersOnError: false,
};

View File

@ -1,7 +0,0 @@
{
"csrf": {
"enabled": false,
"key": "_csrf",
"secret": "_csrfSecret"
}
}

View File

@ -1,30 +0,0 @@
'use strict';
/**
* Module dependencies
*/
const convert = require('koa-convert');
const { csrf } = require('koa-lusca');
/**
* CSRF hook
*/
module.exports = strapi => {
return {
/**
* Initialize the hook
*/
initialize() {
strapi.app.use(async (ctx, next) => {
if (ctx.request.admin) return await next();
return await convert(csrf(strapi.config.middleware.settings.csrf))(
ctx,
next
);
});
},
};
};

View File

@ -3,17 +3,6 @@
const { uniq, difference, get, isUndefined, merge } = require('lodash');
module.exports = async function() {
// Set if is admin destination for middleware application.
this.app.use(async (ctx, next) => {
if (ctx.request.header['origin'] === 'http://localhost:4000') {
ctx.request.header['x-forwarded-host'] = 'strapi';
}
ctx.request.admin = ctx.request.header['x-forwarded-host'] === 'strapi';
await next();
});
/** Utils */
const middlewareConfig = this.config.middleware;

View File

@ -1,5 +0,0 @@
{
"mask": {
"enabled": false
}
}

View File

@ -1,189 +0,0 @@
'use strict';
/**
* Module dependencies
*/
/**
* Mask filter middleware
*/
const _ = require('lodash');
module.exports = strapi => {
return {
/**
* Initialize the hook
*/
initialize() {
// Enable the middleware if we need it.
const enabled = (() => {
const main = Object.keys(strapi.models).reduce((acc, current) => {
if (
Object.values(strapi.models[current].attributes).find(
attr => attr.private === true
)
) {
acc = true;
}
return acc;
}, false);
const plugins = Object.keys(strapi.plugins).reduce((acc, plugin) => {
const bool = Object.keys(strapi.plugins[plugin].models).reduce(
(acc, model) => {
if (
Object.values(
strapi.plugins[plugin].models[model].attributes
).find(attr => attr.private === true)
) {
acc = true;
}
return acc;
},
false
);
if (bool) {
acc = true;
}
return acc;
}, false);
return main || plugins;
})();
if (enabled) {
strapi.app.use(async (ctx, next) => {
// Execute next middleware.
await next();
// Recursive to mask the private properties.
const mask = payload => {
// Handle ORM toJSON() method to work on real JSON object.
payload = payload && payload.toJSON ? payload.toJSON() : payload;
if (_.isArray(payload)) {
return payload.map(mask);
} else if (_.isPlainObject(payload)) {
return this.mask(
ctx,
Object.keys(payload).reduce((acc, current) => {
acc[current] = _.isObjectLike(payload[current])
? mask(payload[current])
: payload[current];
return acc;
}, {})
);
}
return payload;
};
// Only pick successful JSON requests.
if (
[200, 201, 202].includes(ctx.status) &&
ctx.type === 'application/json' &&
!ctx.request.admin
) {
ctx.body = mask(ctx.body);
}
});
}
},
mask(ctx, value) {
const models = this.filteredModels(
this.whichModels(value, ctx.request.route.plugin)
);
if (models.length === 0) {
return value;
}
const attributesToHide = models.reduce((acc, match) => {
const attributes = match.plugin
? strapi.plugins[match.plugin].models[match.model].attributes
: strapi.models[match.model].attributes;
acc = acc.concat(
Object.keys(attributes).filter(
attr => attributes[attr].private === true
)
);
return acc;
}, []);
// Hide attribute.
return _.omit(value, attributesToHide);
},
whichModels(value) {
const keys = Object.keys(value);
let maxMatch = 0;
let matchs = [];
const match = (model, plugin) => {
const attributes = plugin
? Object.keys(strapi.plugins[plugin].models[model].attributes)
: Object.keys(strapi.models[model].attributes);
const intersection = _.intersection(
keys,
attributes.filter(attr => ['id', '_id', '_v'].indexOf(attr) === -1)
).length;
// Most matched model.
if (intersection > maxMatch) {
maxMatch = intersection;
matchs = [
{
plugin,
model,
intersection,
},
];
} else if (intersection === maxMatch && intersection > 0) {
matchs.push({
plugin,
model,
intersection,
});
}
};
// Application models.
Object.keys(strapi.models).forEach(model => match(model));
// Plugins models.
Object.keys(strapi.plugins).forEach(plugin => {
Object.keys(strapi.plugins[plugin].models).forEach(model =>
match(model, plugin)
);
});
return matchs;
},
filteredModels(matchs) {
return matchs.reduce((acc, match, index) => {
const attributes = match.plugin
? strapi.plugins[match.plugin].models[match.model].attributes
: strapi.models[match.model].attributes;
// Filtered model which have more than 50% of the attributes
// in common with the original model.
if (match.intersection >= Object.keys(attributes).length / 2) {
acc[index] = match;
}
return acc;
}, []);
},
};
};