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` - `json`
- `email` - `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 #### Example
**Path —** `User.settings.json`. **Path —** `User.settings.json`.
@ -50,14 +67,23 @@ The following types are currently available:
"lastname": { "lastname": {
"type": "string" "type": "string"
}, },
"email": {
"type": "email",
"required": true,
"unique": true
},
"password": { "password": {
"type": "password" "type": "password",
"required": true,
"private": true
}, },
"about": { "about": {
"type": "description" "type": "description"
}, },
"age": { "age": {
"type": "integer" "type": "integer",
"min": 18,
"max": 99
}, },
"birthday": { "birthday": {
"type": "date" "type": "date"

View File

@ -1,5 +1,5 @@
{ {
"timeout": 1000, "timeout": 3000,
"load": { "load": {
"order": [ "order": [
"Define the hooks' load order by putting their names in this array in the right 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 _ = require('lodash');
const crypto = require('crypto'); 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,}))$/; 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 = { module.exports = {
@ -137,6 +136,7 @@ module.exports = {
return ctx.badRequest(null, 'This provider is disabled.'); return ctx.badRequest(null, 'This provider is disabled.');
} }
const Grant = require('grant-koa');
const grant = new Grant(strapi.plugins['users-permissions'].config.grant); const grant = new Grant(strapi.plugins['users-permissions'].config.grant);
return strapi.koaMiddlewares.compose(grant.middleware)(ctx, next); return strapi.koaMiddlewares.compose(grant.middleware)(ctx, next);

View File

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

View File

@ -244,6 +244,9 @@ module.exports.app = async function() {
boom: { boom: {
enabled: true enabled: true
}, },
mask: {
enabled: true
},
// Necessary middlewares for the administration panel. // Necessary middlewares for the administration panel.
cors: { cors: {
enabled: true 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;
}, []);
}
};
};