0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-01-13 22:41:32 -05:00
ghost/core/server/data/validation/index.js
Jason Williams ef1858b6c7 Fix validations on user settings page
Closes #3271
- Change validations on both server and client to allow the
  Website field to be empty or a valid URL.
- Add new schema validation helper isEmptyOrURL.
- Remove duplicate call to UserValidator in the save action
  of the SettingsUser controller.
- User.last_login and User.created_at are already Moment objects
  so Moment#fromNow can be called on them directly.
2014-07-14 18:12:57 +00:00

169 lines
6.2 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'),
when = require('when'),
errors = require('../../errors'),
config = require('../../config'),
requireTree = require('../../require-tree').readAll,
validateSchema,
validateSettings,
validateActiveTheme,
validate,
availableThemes;
// Provide a few custom validators
//
validator.extend('empty', function (str) {
return _.isEmpty(str);
});
validator.extend('notContains', function (str, badString) {
return !_.contains(str, badString);
});
validator.extend('isEmptyOrURL', function (str) {
return (_.isEmpty(str) || validator.isURL(str, { protocols: ['http', 'https'], require_protocol: true }));
});
// Validation validation against schema attributes
// values are checked against the validation objects
// form schema.js
validateSchema = function (tableName, model) {
var columns = _.keys(schema[tableName]),
validationErrors = [];
_.each(columns, function (columnKey) {
var message = '';
// check nullable
if (model.hasOwnProperty(columnKey) && schema[tableName][columnKey].hasOwnProperty('nullable')
&& schema[tableName][columnKey].nullable !== true) {
if (validator.isNull(model[columnKey]) || validator.empty(model[columnKey])) {
message = 'Value in [' + tableName + '.' + columnKey + '] cannot be blank.';
validationErrors.push(new errors.ValidationError(message, 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(model[columnKey], 0, schema[tableName][columnKey].maxlength)) {
message = 'Value in [' + tableName + '.' + columnKey + '] exceeds maximum length of '
+ schema[tableName][columnKey].maxlength + ' characters.';
validationErrors.push(new errors.ValidationError(message, tableName + '.' + columnKey));
}
}
//check validations objects
if (schema[tableName][columnKey].hasOwnProperty('validations')) {
validationErrors = validationErrors.concat(validate(model[columnKey], columnKey, schema[tableName][columnKey].validations));
}
//check type
if (schema[tableName][columnKey].hasOwnProperty('type')) {
if (schema[tableName][columnKey].type === 'integer' && !validator.isInt(model[columnKey])) {
message = 'Value in [' + tableName + '.' + columnKey + '] is not an integer.';
validationErrors.push(new errors.ValidationError(message, tableName + '.' + columnKey));
}
}
}
});
if (validationErrors.length !== 0) {
return when.reject(validationErrors);
}
return when.resolve();
};
// Validation for settings
// settings are checked against the validation objects
// form default-settings.json
validateSettings = function (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 when.reject(validationErrors);
}
return when.resolve();
};
// 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)
availableThemes = requireTree(config().paths.themePath);
validateActiveTheme = function (themeName) {
// If Ghost is running and its availableThemes collection exists
// give it priority.
if (Object.keys(config().paths.availableThemes).length > 0) {
availableThemes = when(config().paths.availableThemes);
}
return availableThemes.then(function (themes) {
if (!themes.hasOwnProperty(themeName)) {
return when.reject(new errors.ValidationError(themeName + ' cannot be activated because it is not currently installed.', 'activeTheme'));
}
return when.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 (value, key, validations) {
var validationErrors = [];
_.each(validations, function (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('Validation (' + validationName + ') failed for ' + key, key));
}
validationOptions.shift();
}, this);
return validationErrors;
};
module.exports = {
validateSchema: validateSchema,
validateSettings: validateSettings,
validateActiveTheme: validateActiveTheme
};