Merge branch 'master' into design/icons

This commit is contained in:
Jim LAURIE 2018-02-09 18:19:21 +01:00 committed by GitHub
commit 36f9f27d0c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 200 additions and 6 deletions

View File

@ -33,6 +33,23 @@ The following types are currently available:
- `json`
- `email`
#### Validations
You can apply basic validations to the attributes. The following supported validations are *only supported by MongoDB* connection.
If you're using SQL databases, you should use the native SQL constraints to apply them.
- `required` (boolean) — if true adds a required validator for this property.
- `unique` (boolean) — whether to define a unique index on this property.
- `max` (integer) — checks if the value is greater than or equal to the given minimum.
- `min` (integer) — checks if the value is less than or equal to the given maximum.
**Security validations**
To improve the Developer eXperience when developing or using the administration panel, the framework enhances the attributes with these "security validations":
- `private` (boolean) — if true, the attribute will be removed from the server response (it's useful to hide sensitive data).
- `configurable` (boolean) - if false, the attribute isn't configurable from the Content Type Builder plugin.
#### Example
**Path —** `User.settings.json`.
@ -50,14 +67,23 @@ The following types are currently available:
"lastname": {
"type": "string"
},
"email": {
"type": "email",
"required": true,
"unique": true
},
"password": {
"type": "password"
"type": "password",
"required": true,
"private": true
},
"about": {
"type": "description"
},
"age": {
"type": "integer"
"type": "integer",
"min": 18,
"max": 99
},
"birthday": {
"type": "date"

View File

@ -1,5 +1,5 @@
{
"timeout": 1000,
"timeout": 3000,
"load": {
"order": [
"Define the hooks' load order by putting their names in this array in the right order"

View File

@ -8,7 +8,6 @@
const _ = require('lodash');
const crypto = require('crypto');
const Grant = require('grant-koa');
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,}))$/;
module.exports = {
@ -137,6 +136,7 @@ module.exports = {
return ctx.badRequest(null, 'This provider is disabled.');
}
const Grant = require('grant-koa');
const grant = new Grant(strapi.plugins['users-permissions'].config.grant);
return strapi.koaMiddlewares.compose(grant.middleware)(ctx, next);

View File

@ -25,11 +25,13 @@
"password": {
"type": "password",
"minLength": 6,
"configurable": false
"configurable": false,
"private": true
},
"resetPasswordToken": {
"type": "string",
"configurable": false
"configurable": false,
"private": true
},
"role": {
"model": "role",

View File

@ -244,6 +244,9 @@ module.exports.app = async function() {
boom: {
enabled: true
},
mask: {
enabled: true
},
// Necessary middlewares for the administration panel.
cors: {
enabled: true

View File

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

View File

@ -0,0 +1,158 @@
'use strict';
/**
* Module dependencies
*/
/**
* Mask filter middleware
*/
const _ = require('lodash');
module.exports = strapi => {
return {
/**
* Initialize the hook
*/
initialize: function (cb) {
// 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) => {
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.body = mask(ctx.body);
}
});
}
cb();
},
mask: function (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: function (value, plugin) {
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: function (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;
}, []);
}
};
};