2014-02-19 18:32:23 +01:00
|
|
|
|
var schema = require('../schema').tables,
|
|
|
|
|
_ = require('lodash'),
|
|
|
|
|
validator = require('validator'),
|
2016-03-27 18:19:32 +08:00
|
|
|
|
assert = require('assert'),
|
2014-08-17 06:17:23 +00:00
|
|
|
|
Promise = require('bluebird'),
|
2014-05-09 12:11:29 +02:00
|
|
|
|
errors = require('../../errors'),
|
2014-07-09 22:11:04 +00:00
|
|
|
|
config = require('../../config'),
|
2016-06-03 09:06:18 +01:00
|
|
|
|
readThemes = require('../../utils/read-themes'),
|
2015-11-12 13:29:45 +01:00
|
|
|
|
i18n = require('../../i18n'),
|
2014-02-19 18:32:23 +01:00
|
|
|
|
|
|
|
|
|
validateSchema,
|
|
|
|
|
validateSettings,
|
2014-07-09 22:11:04 +00:00
|
|
|
|
validateActiveTheme,
|
|
|
|
|
validate,
|
|
|
|
|
|
|
|
|
|
availableThemes;
|
2014-02-19 18:32:23 +01:00
|
|
|
|
|
2016-03-27 18:19:32 +08:00
|
|
|
|
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);
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
2014-02-27 23:51:52 -07:00
|
|
|
|
// Provide a few custom validators
|
2015-06-02 09:52:39 +01:00
|
|
|
|
validator.extend('empty', function empty(str) {
|
2014-02-27 23:51:52 -07:00
|
|
|
|
return _.isEmpty(str);
|
|
|
|
|
});
|
|
|
|
|
|
2015-06-02 09:52:39 +01:00
|
|
|
|
validator.extend('notContains', function notContains(str, badString) {
|
2016-06-11 12:23:27 -06:00
|
|
|
|
return !_.includes(str, badString);
|
2014-02-27 23:51:52 -07:00
|
|
|
|
});
|
|
|
|
|
|
2015-06-02 09:52:39 +01:00
|
|
|
|
validator.extend('isEmptyOrURL', function isEmptyOrURL(str) {
|
2014-09-10 00:06:24 -04:00
|
|
|
|
return (_.isEmpty(str) || validator.isURL(str, {require_protocol: false}));
|
2014-07-14 16:32:55 +00:00
|
|
|
|
});
|
|
|
|
|
|
2015-09-22 17:38:30 +01:00
|
|
|
|
validator.extend('isSlug', function isSlug(str) {
|
|
|
|
|
return validator.matches(str, /^[a-z0-9\-_]+$/);
|
|
|
|
|
});
|
|
|
|
|
|
2014-12-11 15:50:10 +00:00
|
|
|
|
// Validation against schema attributes
|
|
|
|
|
// values are checked against the validation objects from schema.js
|
2015-06-02 09:52:39 +01:00
|
|
|
|
validateSchema = function validateSchema(tableName, model) {
|
2014-05-05 15:51:21 +02:00
|
|
|
|
var columns = _.keys(schema[tableName]),
|
2014-05-09 12:11:29 +02:00
|
|
|
|
validationErrors = [];
|
2014-02-19 18:32:23 +01:00
|
|
|
|
|
2015-06-02 09:52:39 +01:00
|
|
|
|
_.each(columns, function each(columnKey) {
|
2016-03-27 18:19:32 +08:00
|
|
|
|
var message = '',
|
2016-06-03 09:06:18 +01:00
|
|
|
|
strVal = _.toString(model[columnKey]);
|
2014-06-26 04:52:04 +00:00
|
|
|
|
|
2014-02-19 18:32:23 +01:00
|
|
|
|
// check nullable
|
|
|
|
|
if (model.hasOwnProperty(columnKey) && schema[tableName][columnKey].hasOwnProperty('nullable')
|
|
|
|
|
&& schema[tableName][columnKey].nullable !== true) {
|
2016-03-27 18:19:32 +08:00
|
|
|
|
if (validator.empty(strVal)) {
|
2015-11-12 13:29:45 +01:00
|
|
|
|
message = i18n.t('notices.data.validation.index.valueCannotBeBlank', {tableName: tableName, columnKey: columnKey});
|
2014-05-09 12:11:29 +02:00
|
|
|
|
validationErrors.push(new errors.ValidationError(message, tableName + '.' + columnKey));
|
2014-02-27 23:51:52 -07:00
|
|
|
|
}
|
2014-02-19 18:32:23 +01:00
|
|
|
|
}
|
2014-06-26 04:52:04 +00:00
|
|
|
|
|
2016-03-27 18:19:32 +08:00
|
|
|
|
// 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, tableName + '.' + columnKey));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-02-19 18:32:23 +01:00
|
|
|
|
// TODO: check if mandatory values should be enforced
|
2014-06-26 04:52:04 +00:00
|
|
|
|
if (model[columnKey] !== null && model[columnKey] !== undefined) {
|
2014-02-19 18:32:23 +01:00
|
|
|
|
// check length
|
2015-06-02 09:52:39 +01:00
|
|
|
|
if (schema[tableName][columnKey].hasOwnProperty('maxlength')) {
|
2016-03-27 18:19:32 +08:00
|
|
|
|
if (!validator.isLength(strVal, 0, schema[tableName][columnKey].maxlength)) {
|
2015-11-12 13:29:45 +01:00
|
|
|
|
message = i18n.t('notices.data.validation.index.valueExceedsMaxLength',
|
|
|
|
|
{tableName: tableName, columnKey: columnKey, maxlength: schema[tableName][columnKey].maxlength});
|
2014-05-09 12:11:29 +02:00
|
|
|
|
validationErrors.push(new errors.ValidationError(message, tableName + '.' + columnKey));
|
2014-02-27 23:51:52 -07:00
|
|
|
|
}
|
2014-02-19 18:32:23 +01:00
|
|
|
|
}
|
|
|
|
|
|
2014-09-10 00:06:24 -04:00
|
|
|
|
// check validations objects
|
2014-02-19 18:32:23 +01:00
|
|
|
|
if (schema[tableName][columnKey].hasOwnProperty('validations')) {
|
2016-03-27 18:19:32 +08:00
|
|
|
|
validationErrors = validationErrors.concat(validate(strVal, columnKey, schema[tableName][columnKey].validations));
|
2014-02-19 18:32:23 +01:00
|
|
|
|
}
|
|
|
|
|
|
2014-09-10 00:06:24 -04:00
|
|
|
|
// check type
|
2014-02-19 18:32:23 +01:00
|
|
|
|
if (schema[tableName][columnKey].hasOwnProperty('type')) {
|
2016-03-27 18:19:32 +08:00
|
|
|
|
if (schema[tableName][columnKey].type === 'integer' && !validator.isInt(strVal)) {
|
2015-11-12 13:29:45 +01:00
|
|
|
|
message = i18n.t('notices.data.validation.index.valueIsNotInteger', {tableName: tableName, columnKey: columnKey});
|
2014-05-09 12:11:29 +02:00
|
|
|
|
validationErrors.push(new errors.ValidationError(message, tableName + '.' + columnKey));
|
2014-02-19 18:32:23 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
2014-05-05 15:51:21 +02:00
|
|
|
|
|
2014-05-09 12:11:29 +02:00
|
|
|
|
if (validationErrors.length !== 0) {
|
2014-08-17 06:17:23 +00:00
|
|
|
|
return Promise.reject(validationErrors);
|
2014-05-05 15:51:21 +02:00
|
|
|
|
}
|
2014-06-26 04:52:04 +00:00
|
|
|
|
|
2014-08-17 06:17:23 +00:00
|
|
|
|
return Promise.resolve();
|
2014-02-19 18:32:23 +01:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Validation for settings
|
|
|
|
|
// settings are checked against the validation objects
|
|
|
|
|
// form default-settings.json
|
2015-06-02 09:52:39 +01:00
|
|
|
|
validateSettings = function validateSettings(defaultSettings, model) {
|
2014-02-19 18:32:23 +01:00
|
|
|
|
var values = model.toJSON(),
|
2014-05-14 15:30:46 +02:00
|
|
|
|
validationErrors = [],
|
2014-02-19 18:32:23 +01:00
|
|
|
|
matchingDefault = defaultSettings[values.key];
|
|
|
|
|
|
|
|
|
|
if (matchingDefault && matchingDefault.validations) {
|
2014-05-14 15:30:46 +02:00
|
|
|
|
validationErrors = validationErrors.concat(validate(values.value, values.key, matchingDefault.validations));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (validationErrors.length !== 0) {
|
2014-08-17 06:17:23 +00:00
|
|
|
|
return Promise.reject(validationErrors);
|
2014-02-19 18:32:23 +01:00
|
|
|
|
}
|
2014-06-26 04:52:04 +00:00
|
|
|
|
|
2014-08-17 06:17:23 +00:00
|
|
|
|
return Promise.resolve();
|
2014-02-19 18:32:23 +01:00
|
|
|
|
};
|
|
|
|
|
|
2015-06-02 09:52:39 +01:00
|
|
|
|
validateActiveTheme = function validateActiveTheme(themeName) {
|
2014-07-09 22:11:04 +00:00
|
|
|
|
// If Ghost is running and its availableThemes collection exists
|
|
|
|
|
// give it priority.
|
2014-07-17 10:33:21 -04:00
|
|
|
|
if (config.paths.availableThemes && Object.keys(config.paths.availableThemes).length > 0) {
|
2014-08-17 06:17:23 +00:00
|
|
|
|
availableThemes = Promise.resolve(config.paths.availableThemes);
|
2014-07-09 22:11:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
2014-10-01 23:19:46 +00:00
|
|
|
|
if (!availableThemes) {
|
|
|
|
|
// A Promise that will resolve to an object with a property for each installed theme.
|
|
|
|
|
// This is necessary because certain configuration data is only available while Ghost
|
|
|
|
|
// is running and at times the validations are used when it's not (e.g. tests)
|
2015-10-13 13:55:24 +02:00
|
|
|
|
availableThemes = readThemes(config.paths.themePath);
|
2014-10-01 23:19:46 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-06-02 09:52:39 +01:00
|
|
|
|
return availableThemes.then(function then(themes) {
|
2014-07-09 22:11:04 +00:00
|
|
|
|
if (!themes.hasOwnProperty(themeName)) {
|
2015-11-12 13:29:45 +01:00
|
|
|
|
return Promise.reject(new errors.ValidationError(i18n.t('notices.data.validation.index.themeCannotBeActivated', {themeName: themeName}), 'activeTheme'));
|
2014-07-09 22:11:04 +00:00
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2014-02-27 23:51:52 -07:00
|
|
|
|
// Validate default settings using the validator module.
|
|
|
|
|
// Each validation's key is a method name and its value is an array of options
|
|
|
|
|
//
|
|
|
|
|
// eg:
|
2014-07-14 16:32:55 +00:00
|
|
|
|
// validations: { isURL: true, isLength: [20, 40] }
|
2014-02-27 23:51:52 -07:00
|
|
|
|
//
|
|
|
|
|
// 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.
|
2014-02-19 18:32:23 +01:00
|
|
|
|
//
|
|
|
|
|
// eg:
|
2014-02-27 23:51:52 -07:00
|
|
|
|
// validations: { isNull: false } // means the "good" result would
|
|
|
|
|
// // fail the `isNull` check, so
|
|
|
|
|
// // not null.
|
2014-02-19 18:32:23 +01:00
|
|
|
|
//
|
2014-02-27 23:51:52 -07:00
|
|
|
|
// available validators: https://github.com/chriso/validator.js#validators
|
2015-06-02 09:52:39 +01:00
|
|
|
|
validate = function validate(value, key, validations) {
|
2014-05-09 12:11:29 +02:00
|
|
|
|
var validationErrors = [];
|
2016-06-03 09:06:18 +01:00
|
|
|
|
value = _.toString(value);
|
2014-06-26 04:52:04 +00:00
|
|
|
|
|
2015-06-02 09:52:39 +01:00
|
|
|
|
_.each(validations, function each(validationOptions, validationName) {
|
2014-02-27 23:51:52 -07:00
|
|
|
|
var goodResult = true;
|
2014-02-19 18:32:23 +01:00
|
|
|
|
|
2014-02-27 23:51:52 -07:00
|
|
|
|
if (_.isBoolean(validationOptions)) {
|
|
|
|
|
goodResult = validationOptions;
|
|
|
|
|
validationOptions = [];
|
|
|
|
|
} else if (!_.isArray(validationOptions)) {
|
2014-02-19 18:32:23 +01:00
|
|
|
|
validationOptions = [validationOptions];
|
|
|
|
|
}
|
2014-02-27 23:51:52 -07:00
|
|
|
|
|
|
|
|
|
validationOptions.unshift(value);
|
|
|
|
|
|
|
|
|
|
// equivalent of validator.isSomething(option1, option2)
|
|
|
|
|
if (validator[validationName].apply(validator, validationOptions) !== goodResult) {
|
2015-11-12 13:29:45 +01:00
|
|
|
|
validationErrors.push(new errors.ValidationError(i18n.t('notices.data.validation.index.validationFailed',
|
|
|
|
|
{validationName: validationName, key: key})));
|
2014-02-27 23:51:52 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
validationOptions.shift();
|
2014-02-19 18:32:23 +01:00
|
|
|
|
}, this);
|
2014-05-05 15:51:21 +02:00
|
|
|
|
|
2014-05-14 15:30:46 +02:00
|
|
|
|
return validationErrors;
|
2014-02-19 18:32:23 +01:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
module.exports = {
|
2015-07-01 19:17:56 +01:00
|
|
|
|
validate: validate,
|
2014-12-11 15:50:10 +00:00
|
|
|
|
validator: validator,
|
2014-02-19 18:32:23 +01:00
|
|
|
|
validateSchema: validateSchema,
|
2014-07-09 22:11:04 +00:00
|
|
|
|
validateSettings: validateSettings,
|
|
|
|
|
validateActiveTheme: validateActiveTheme
|
2014-02-27 02:44:09 +00:00
|
|
|
|
};
|