index.js

var lib = require('./lib');
var error = require('./constants').error;

/**
 * Validates that a number is a valid length (positive number)
 *
 * @private
 * @param {number} num - Number to validate
 */
function _validateLength(num) {
  if (!num || typeof num !== 'number' || num < 0) {
    throw new Error(error.length);
  }
}

/**
 * Tests a validation and return the result
 *
 * @private
 * @param {string} property - Property to validate
 * @return {boolean} Boolean value indicting the validity
 *           of the password against the property
 */
function _isPasswordValidFor(property) {
  return lib[property.method].apply(this, property.arguments);
}

/**
 * Registers the properties of a password-validation schema object
 *
 * @private
 * @param {string} func - Property name
 * @param {array} args - arguments for the func property
 */
function _register(func, args) {
  // Add property to the schema
  this.properties.push({ method: func, arguments: args });
  return this;
}

/**
 * Creates a password-validator schema
 *
 * @constructor
 */
function PasswordValidator() {
  // Initialize a schema with no properties defined
  this.properties = [];
}

/**
 * Method to validate the password against schema
 *
 * @param {string} pwd - password to valdiate
 * @param {object} options - optional options to configure validation
 * @param {boolean} [options.list] - asks for a list of validation
 *           failures instead of just true/false
 * @return {boolean|array} Boolean value indicting the validity
 *           of the password as per schema, if 'options.list'
 *           is not set. Otherwise, it returns an array of
 *           property names which failed validations
 */
PasswordValidator.prototype.validate = function (pwd, options) {
  // Checks if pwd is invalid
  if (!pwd || typeof pwd !== 'string') {
    throw new Error(error.password);
  }

  // Sets password string
  this.password = pwd;

  // Sets that no inversion takes place by default
  this.positive = true;

  var _this = this;

  if (options && options.list === true) {
    return this.properties.reduce(function (errorList, property) {
      // Applies all validations defined in lib one by one
      if (!_isPasswordValidFor.call(_this, property)) {
        // If the validation for a property fails,
        // add it to the error list
        return errorList.concat(property.method);
      }
      return errorList;
    }, []);
  }

  // Returns the result of the validations
  return this.properties.every(function (property) {
    // Applies all validations defined in lib one by one
    return _isPasswordValidFor.call(_this, property);
  });
};

/**
 * Rule to invert the next applied rules.
 * All the rules applied after 'not' will have opposite effect,
 * until 'has' rule is applied
 */
PasswordValidator.prototype.not = function not() {
  return _register.call(this, 'not', arguments);
};

/**
 * Rule to invert the effects of 'not'
 * Apart from that, 'has' is also used
 * to make the api readable and chainable
 */
PasswordValidator.prototype.has = function has() {
  return _register.call(this, 'has', arguments);
};

/**
 * Rule to invert the effects of 'not'
 * Apart from that, 'is' is also used
 * to make the api readable and chainable
 */
PasswordValidator.prototype.is = function is() {
  return _register.call(this, 'is', arguments);
};

/**
 * Rule to specify a minimum length of the password
 *
 * @param {number} num - minimum length
 */
PasswordValidator.prototype.min = function min(num) {
  _validateLength(num);
  return _register.call(this, 'min', arguments);
};

/**
 * Rule to specify a maximum length of the password
 *
 * @param {number} num - maximum length
 */
PasswordValidator.prototype.max = function max(num) {
  _validateLength(num);
  return _register.call(this, 'max', arguments);
};

/**
 * Rule to mendate the presense of digits in the password
 */
PasswordValidator.prototype.digits = function digits() {
  return _register.call(this, 'digits', arguments);
};

/**
 * Rule to mendate the presense of letters in the password
 */
PasswordValidator.prototype.letters = function letters() {
  return _register.call(this, 'letters', arguments);
};

/**
 * Rule to mendate the presense of uppercase letters in the password
 */
PasswordValidator.prototype.uppercase = function uppercase() {
  return _register.call(this, 'uppercase', arguments);
};

/**
 * Rule to mendate the presense of lowercase letters in the password
 */
PasswordValidator.prototype.lowercase = function lowercase() {
  return _register.call(this, 'lowercase', arguments);
};

/**
 * Rule to mendate the presense of symbols in the password
 */
PasswordValidator.prototype.symbols = function symbols() {
  return _register.call(this, 'symbols', arguments);
};

/**
 * Rule to mendate the presense of space in the password
 * It can be used along with 'not' to not allow spaces
 * in the password
 */
PasswordValidator.prototype.spaces = function spaces() {
  return _register.call(this, 'spaces', arguments);
};

/**
 * Rule to whitelist words to be used as password
 *
 * @param {array} list - list of values allowed
 */
PasswordValidator.prototype.oneOf = function oneOf() {
  return _register.call(this, 'oneOf', arguments);
};

module.exports = PasswordValidator;