Ghost/core/server/data/validation/index.js
Hannah Wolfe 690ff05588 🔥 🎨 Themes & settings misc cleanup (#8061)
no issue

🔥 remove unused loadThemes API method
🚨 Add tests for themes.readOne
🔥 Don't update settings cache for imports
- this isn't needed as of #8057
- settings.edit fires an event, that will result in the update happening automatically
🎨 Move validation to themes
- slowly collecting all theme-related code together
🔥 Reduce DEBUG output
- all this info is a bit tooooo much!
2017-02-27 23:30:49 +01:00

180 lines
6.7 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

var schema = require('../schema').tables,
_ = require('lodash'),
validator = require('validator'),
moment = require('moment-timezone'),
assert = require('assert'),
Promise = require('bluebird'),
errors = require('../../errors'),
i18n = require('../../i18n'),
validateSchema,
validateSettings,
validate;
function assertString(input) {
assert(typeof input === 'string', 'Validator js validates strings only');
}
// extends has been removed in validator >= 5.0.0, need to monkey-patch it back in
validator.extend = function (name, fn) {
validator[name] = function () {
var args = Array.prototype.slice.call(arguments);
assertString(args[0]);
return fn.apply(validator, args);
};
};
// Provide a few custom validators
validator.extend('empty', function empty(str) {
return _.isEmpty(str);
});
validator.extend('notContains', function notContains(str, badString) {
return !_.includes(str, badString);
});
validator.extend('isTimezone', function isTimezone(str) {
return moment.tz.zone(str) ? true : false;
});
validator.extend('isEmptyOrURL', function isEmptyOrURL(str) {
return (_.isEmpty(str) || validator.isURL(str, {require_protocol: false}));
});
validator.extend('isSlug', function isSlug(str) {
return validator.matches(str, /^[a-z0-9\-_]+$/);
});
// Validation against schema attributes
// values are checked against the validation objects from schema.js
validateSchema = function validateSchema(tableName, model) {
var columns = _.keys(schema[tableName]),
validationErrors = [];
_.each(columns, function each(columnKey) {
var message = '',
strVal = _.toString(model[columnKey]);
// check nullable
if (model.hasOwnProperty(columnKey) && schema[tableName][columnKey].hasOwnProperty('nullable')
&& schema[tableName][columnKey].nullable !== true) {
if (validator.empty(strVal)) {
message = i18n.t('notices.data.validation.index.valueCannotBeBlank', {tableName: tableName, columnKey: columnKey});
validationErrors.push(new errors.ValidationError({message: message, context: tableName + '.' + columnKey}));
}
}
// validate boolean columns
if (model.hasOwnProperty(columnKey) && schema[tableName][columnKey].hasOwnProperty('type')
&& schema[tableName][columnKey].type === 'bool') {
if (!(validator.isBoolean(strVal) || validator.empty(strVal))) {
message = i18n.t('notices.data.validation.index.valueMustBeBoolean', {tableName: tableName, columnKey: columnKey});
validationErrors.push(new errors.ValidationError({message: message, context: tableName + '.' + columnKey}));
}
}
// TODO: check if mandatory values should be enforced
if (model[columnKey] !== null && model[columnKey] !== undefined) {
// check length
if (schema[tableName][columnKey].hasOwnProperty('maxlength')) {
if (!validator.isLength(strVal, 0, schema[tableName][columnKey].maxlength)) {
message = i18n.t('notices.data.validation.index.valueExceedsMaxLength',
{tableName: tableName, columnKey: columnKey, maxlength: schema[tableName][columnKey].maxlength});
validationErrors.push(new errors.ValidationError({message: message, context: tableName + '.' + columnKey}));
}
}
// check validations objects
if (schema[tableName][columnKey].hasOwnProperty('validations')) {
validationErrors = validationErrors.concat(validate(strVal, columnKey, schema[tableName][columnKey].validations));
}
// check type
if (schema[tableName][columnKey].hasOwnProperty('type')) {
if (schema[tableName][columnKey].type === 'integer' && !validator.isInt(strVal)) {
message = i18n.t('notices.data.validation.index.valueIsNotInteger', {tableName: tableName, columnKey: columnKey});
validationErrors.push(new errors.ValidationError({message: message, context: tableName + '.' + columnKey}));
}
}
}
});
if (validationErrors.length !== 0) {
return Promise.reject(validationErrors);
}
return Promise.resolve();
};
// Validation for settings
// settings are checked against the validation objects
// form default-settings.json
validateSettings = function validateSettings(defaultSettings, model) {
var values = model.toJSON(),
validationErrors = [],
matchingDefault = defaultSettings[values.key];
if (matchingDefault && matchingDefault.validations) {
validationErrors = validationErrors.concat(validate(values.value, values.key, matchingDefault.validations));
}
if (validationErrors.length !== 0) {
return Promise.reject(validationErrors);
}
return Promise.resolve();
};
// Validate default settings using the validator module.
// Each validation's key is a method name and its value is an array of options
//
// eg:
// validations: { isURL: true, isLength: [20, 40] }
//
// will validate that a setting's length is a URL between 20 and 40 chars.
//
// If you pass a boolean as the value, it will specify the "good" result. By default
// the "good" result is assumed to be true.
//
// eg:
// validations: { isNull: false } // means the "good" result would
// // fail the `isNull` check, so
// // not null.
//
// available validators: https://github.com/chriso/validator.js#validators
validate = function validate(value, key, validations) {
var validationErrors = [];
value = _.toString(value);
_.each(validations, function each(validationOptions, validationName) {
var goodResult = true;
if (_.isBoolean(validationOptions)) {
goodResult = validationOptions;
validationOptions = [];
} else if (!_.isArray(validationOptions)) {
validationOptions = [validationOptions];
}
validationOptions.unshift(value);
// equivalent of validator.isSomething(option1, option2)
if (validator[validationName].apply(validator, validationOptions) !== goodResult) {
validationErrors.push(new errors.ValidationError({
message: i18n.t('notices.data.validation.index.validationFailed', {validationName: validationName, key: key})
}));
}
validationOptions.shift();
}, this);
return validationErrors;
};
module.exports = {
validate: validate,
validator: validator,
validateSchema: validateSchema,
validateSettings: validateSettings
};