695 lines
18 KiB
JavaScript
695 lines
18 KiB
JavaScript
// Sourced from https://github.com/apple/password-manager-resources/blob/5f6da89483e75cdc4165a6fc4756796e0ced7a21/tools/PasswordRulesParser.js
|
|
// Copyright (c) 2019 - 2020 Apple Inc. Licensed under MIT License.
|
|
|
|
export const PasswordRulesParser = {
|
|
parsePasswordRules,
|
|
};
|
|
|
|
const Identifier = {
|
|
ASCII_PRINTABLE: "ascii-printable",
|
|
DIGIT: "digit",
|
|
LOWER: "lower",
|
|
SPECIAL: "special",
|
|
UNICODE: "unicode",
|
|
UPPER: "upper",
|
|
};
|
|
|
|
const RuleName = {
|
|
ALLOWED: "allowed",
|
|
MAX_CONSECUTIVE: "max-consecutive",
|
|
REQUIRED: "required",
|
|
MIN_LENGTH: "minlength",
|
|
MAX_LENGTH: "maxlength",
|
|
};
|
|
|
|
const CHARACTER_CLASS_START_SENTINEL = "[";
|
|
const CHARACTER_CLASS_END_SENTINEL = "]";
|
|
const PROPERTY_VALUE_SEPARATOR = ",";
|
|
const PROPERTY_SEPARATOR = ";";
|
|
const PROPERTY_VALUE_START_SENTINEL = ":";
|
|
|
|
const SPACE_CODE_POINT = " ".codePointAt(0);
|
|
|
|
const SHOULD_NOT_BE_REACHED = "Should not be reached";
|
|
|
|
class Rule {
|
|
constructor(name, value) {
|
|
this._name = name;
|
|
this.value = value;
|
|
}
|
|
get name() {
|
|
return this._name;
|
|
}
|
|
toString() {
|
|
return JSON.stringify(this);
|
|
}
|
|
}
|
|
|
|
class NamedCharacterClass {
|
|
constructor(name) {
|
|
console.assert(_isValidRequiredOrAllowedPropertyValueIdentifier(name));
|
|
this._name = name;
|
|
}
|
|
get name() {
|
|
return this._name.toLowerCase();
|
|
}
|
|
toString() {
|
|
return this._name;
|
|
}
|
|
toHTMLString() {
|
|
return this._name;
|
|
}
|
|
}
|
|
|
|
class CustomCharacterClass {
|
|
constructor(characters) {
|
|
console.assert(characters instanceof Array);
|
|
this._characters = characters;
|
|
}
|
|
get characters() {
|
|
return this._characters;
|
|
}
|
|
toString() {
|
|
return `[${this._characters.join("")}]`;
|
|
}
|
|
toHTMLString() {
|
|
return `[${this._characters.join("").replace('"', """)}]`;
|
|
}
|
|
}
|
|
|
|
// MARK: Lexer functions
|
|
|
|
function _isIdentifierCharacter(c) {
|
|
console.assert(c.length === 1);
|
|
return (c >= "a" && c <= "z") || (c >= "A" && c <= "Z") || c === "-";
|
|
}
|
|
|
|
function _isASCIIDigit(c) {
|
|
console.assert(c.length === 1);
|
|
return c >= "0" && c <= "9";
|
|
}
|
|
|
|
function _isASCIIPrintableCharacter(c) {
|
|
console.assert(c.length === 1);
|
|
return c >= " " && c <= "~";
|
|
}
|
|
|
|
function _isASCIIWhitespace(c) {
|
|
console.assert(c.length === 1);
|
|
return c === " " || c === "\f" || c === "\n" || c === "\r" || c === "\t";
|
|
}
|
|
|
|
// MARK: ASCII printable character bit set and canonicalization functions
|
|
|
|
function _bitSetIndexForCharacter(c) {
|
|
console.assert(c.length == 1);
|
|
return c.codePointAt(0) - SPACE_CODE_POINT;
|
|
}
|
|
|
|
function _characterAtBitSetIndex(index) {
|
|
return String.fromCodePoint(index + SPACE_CODE_POINT);
|
|
}
|
|
|
|
function _markBitsForNamedCharacterClass(bitSet, namedCharacterClass) {
|
|
console.assert(bitSet instanceof Array);
|
|
console.assert(namedCharacterClass.name !== Identifier.UNICODE);
|
|
console.assert(namedCharacterClass.name !== Identifier.ASCII_PRINTABLE);
|
|
if (namedCharacterClass.name === Identifier.UPPER) {
|
|
bitSet.fill(
|
|
true,
|
|
_bitSetIndexForCharacter("A"),
|
|
_bitSetIndexForCharacter("Z") + 1
|
|
);
|
|
} else if (namedCharacterClass.name === Identifier.LOWER) {
|
|
bitSet.fill(
|
|
true,
|
|
_bitSetIndexForCharacter("a"),
|
|
_bitSetIndexForCharacter("z") + 1
|
|
);
|
|
} else if (namedCharacterClass.name === Identifier.DIGIT) {
|
|
bitSet.fill(
|
|
true,
|
|
_bitSetIndexForCharacter("0"),
|
|
_bitSetIndexForCharacter("9") + 1
|
|
);
|
|
} else if (namedCharacterClass.name === Identifier.SPECIAL) {
|
|
bitSet.fill(
|
|
true,
|
|
_bitSetIndexForCharacter(" "),
|
|
_bitSetIndexForCharacter("/") + 1
|
|
);
|
|
bitSet.fill(
|
|
true,
|
|
_bitSetIndexForCharacter(":"),
|
|
_bitSetIndexForCharacter("@") + 1
|
|
);
|
|
bitSet.fill(
|
|
true,
|
|
_bitSetIndexForCharacter("["),
|
|
_bitSetIndexForCharacter("`") + 1
|
|
);
|
|
bitSet.fill(
|
|
true,
|
|
_bitSetIndexForCharacter("{"),
|
|
_bitSetIndexForCharacter("~") + 1
|
|
);
|
|
} else {
|
|
console.assert(false, SHOULD_NOT_BE_REACHED, namedCharacterClass);
|
|
}
|
|
}
|
|
|
|
function _markBitsForCustomCharacterClass(bitSet, customCharacterClass) {
|
|
for (let character of customCharacterClass.characters) {
|
|
bitSet[_bitSetIndexForCharacter(character)] = true;
|
|
}
|
|
}
|
|
|
|
function _canonicalizedPropertyValues(
|
|
propertyValues,
|
|
keepCustomCharacterClassFormatCompliant
|
|
) {
|
|
let asciiPrintableBitSet = new Array(
|
|
"~".codePointAt(0) - " ".codePointAt(0) + 1
|
|
);
|
|
|
|
for (let propertyValue of propertyValues) {
|
|
if (propertyValue instanceof NamedCharacterClass) {
|
|
if (propertyValue.name === Identifier.UNICODE) {
|
|
return [new NamedCharacterClass(Identifier.UNICODE)];
|
|
}
|
|
|
|
if (propertyValue.name === Identifier.ASCII_PRINTABLE) {
|
|
return [new NamedCharacterClass(Identifier.ASCII_PRINTABLE)];
|
|
}
|
|
|
|
_markBitsForNamedCharacterClass(asciiPrintableBitSet, propertyValue);
|
|
} else if (propertyValue instanceof CustomCharacterClass) {
|
|
_markBitsForCustomCharacterClass(asciiPrintableBitSet, propertyValue);
|
|
}
|
|
}
|
|
|
|
let charactersSeen = [];
|
|
|
|
function checkRange(start, end) {
|
|
let temp = [];
|
|
for (
|
|
let i = _bitSetIndexForCharacter(start);
|
|
i <= _bitSetIndexForCharacter(end);
|
|
++i
|
|
) {
|
|
if (asciiPrintableBitSet[i]) {
|
|
temp.push(_characterAtBitSetIndex(i));
|
|
}
|
|
}
|
|
|
|
let result =
|
|
temp.length ===
|
|
_bitSetIndexForCharacter(end) - _bitSetIndexForCharacter(start) + 1;
|
|
if (!result) {
|
|
charactersSeen = charactersSeen.concat(temp);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
let hasAllUpper = checkRange("A", "Z");
|
|
let hasAllLower = checkRange("a", "z");
|
|
let hasAllDigits = checkRange("0", "9");
|
|
|
|
// Check for special characters, accounting for characters that are given special treatment (i.e. '-' and ']')
|
|
let hasAllSpecial = false;
|
|
let hasDash = false;
|
|
let hasRightSquareBracket = false;
|
|
let temp = [];
|
|
for (
|
|
let i = _bitSetIndexForCharacter(" ");
|
|
i <= _bitSetIndexForCharacter("/");
|
|
++i
|
|
) {
|
|
if (!asciiPrintableBitSet[i]) {
|
|
continue;
|
|
}
|
|
|
|
let character = _characterAtBitSetIndex(i);
|
|
if (keepCustomCharacterClassFormatCompliant && character === "-") {
|
|
hasDash = true;
|
|
} else {
|
|
temp.push(character);
|
|
}
|
|
}
|
|
for (
|
|
let i = _bitSetIndexForCharacter(":");
|
|
i <= _bitSetIndexForCharacter("@");
|
|
++i
|
|
) {
|
|
if (asciiPrintableBitSet[i]) {
|
|
temp.push(_characterAtBitSetIndex(i));
|
|
}
|
|
}
|
|
for (
|
|
let i = _bitSetIndexForCharacter("[");
|
|
i <= _bitSetIndexForCharacter("`");
|
|
++i
|
|
) {
|
|
if (!asciiPrintableBitSet[i]) {
|
|
continue;
|
|
}
|
|
|
|
let character = _characterAtBitSetIndex(i);
|
|
if (keepCustomCharacterClassFormatCompliant && character === "]") {
|
|
hasRightSquareBracket = true;
|
|
} else {
|
|
temp.push(character);
|
|
}
|
|
}
|
|
for (
|
|
let i = _bitSetIndexForCharacter("{");
|
|
i <= _bitSetIndexForCharacter("~");
|
|
++i
|
|
) {
|
|
if (asciiPrintableBitSet[i]) {
|
|
temp.push(_characterAtBitSetIndex(i));
|
|
}
|
|
}
|
|
|
|
if (hasDash) {
|
|
temp.unshift("-");
|
|
}
|
|
if (hasRightSquareBracket) {
|
|
temp.push("]");
|
|
}
|
|
|
|
let numberOfSpecialCharacters =
|
|
_bitSetIndexForCharacter("/") -
|
|
_bitSetIndexForCharacter(" ") +
|
|
1 +
|
|
(_bitSetIndexForCharacter("@") - _bitSetIndexForCharacter(":") + 1) +
|
|
(_bitSetIndexForCharacter("`") - _bitSetIndexForCharacter("[") + 1) +
|
|
(_bitSetIndexForCharacter("~") - _bitSetIndexForCharacter("{") + 1);
|
|
hasAllSpecial = temp.length === numberOfSpecialCharacters;
|
|
if (!hasAllSpecial) {
|
|
charactersSeen = charactersSeen.concat(temp);
|
|
}
|
|
|
|
let result = [];
|
|
if (hasAllUpper && hasAllLower && hasAllDigits && hasAllSpecial) {
|
|
return [new NamedCharacterClass(Identifier.ASCII_PRINTABLE)];
|
|
}
|
|
if (hasAllUpper) {
|
|
result.push(new NamedCharacterClass(Identifier.UPPER));
|
|
}
|
|
if (hasAllLower) {
|
|
result.push(new NamedCharacterClass(Identifier.LOWER));
|
|
}
|
|
if (hasAllDigits) {
|
|
result.push(new NamedCharacterClass(Identifier.DIGIT));
|
|
}
|
|
if (hasAllSpecial) {
|
|
result.push(new NamedCharacterClass(Identifier.SPECIAL));
|
|
}
|
|
if (charactersSeen.length) {
|
|
result.push(new CustomCharacterClass(charactersSeen));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// MARK: Parser functions
|
|
|
|
function _indexOfNonWhitespaceCharacter(input, position = 0) {
|
|
console.assert(position >= 0);
|
|
console.assert(position <= input.length);
|
|
|
|
let length = input.length;
|
|
while (position < length && _isASCIIWhitespace(input[position])) {
|
|
++position;
|
|
}
|
|
|
|
return position;
|
|
}
|
|
|
|
function _parseIdentifier(input, position) {
|
|
console.assert(position >= 0);
|
|
console.assert(position < input.length);
|
|
console.assert(_isIdentifierCharacter(input[position]));
|
|
|
|
let length = input.length;
|
|
let seenIdentifiers = [];
|
|
do {
|
|
let c = input[position];
|
|
if (!_isIdentifierCharacter(c)) {
|
|
break;
|
|
}
|
|
|
|
seenIdentifiers.push(c);
|
|
++position;
|
|
} while (position < length);
|
|
|
|
return [seenIdentifiers.join(""), position];
|
|
}
|
|
|
|
function _isValidRequiredOrAllowedPropertyValueIdentifier(identifier) {
|
|
return (
|
|
identifier && Object.values(Identifier).includes(identifier.toLowerCase())
|
|
);
|
|
}
|
|
|
|
function _parseCustomCharacterClass(input, position) {
|
|
console.assert(position >= 0);
|
|
console.assert(position < input.length);
|
|
console.assert(input[position] === CHARACTER_CLASS_START_SENTINEL);
|
|
|
|
let length = input.length;
|
|
++position;
|
|
if (position >= length) {
|
|
console.error("Found end-of-line instead of character class character");
|
|
return [null, position];
|
|
}
|
|
|
|
let initialPosition = position;
|
|
let result = [];
|
|
do {
|
|
let c = input[position];
|
|
if (!_isASCIIPrintableCharacter(c)) {
|
|
++position;
|
|
continue;
|
|
}
|
|
|
|
if (c === "-" && position - initialPosition > 0) {
|
|
// FIXME: Should this be an error?
|
|
console.warn(
|
|
"Ignoring '-'; a '-' may only appear as the first character in a character class"
|
|
);
|
|
++position;
|
|
continue;
|
|
}
|
|
|
|
result.push(c);
|
|
++position;
|
|
if (c === CHARACTER_CLASS_END_SENTINEL) {
|
|
break;
|
|
}
|
|
} while (position < length);
|
|
|
|
if (
|
|
(position < length && input[position] !== CHARACTER_CLASS_END_SENTINEL) ||
|
|
(position == length && input[position - 1] == CHARACTER_CLASS_END_SENTINEL)
|
|
) {
|
|
// Fix up result; we over consumed.
|
|
result.pop();
|
|
return [result, position];
|
|
}
|
|
|
|
if (position < length && input[position] == CHARACTER_CLASS_END_SENTINEL) {
|
|
return [result, position + 1];
|
|
}
|
|
|
|
console.error("Found end-of-line instead of end of character class");
|
|
return [null, position];
|
|
}
|
|
|
|
function _parsePasswordRequiredOrAllowedPropertyValue(input, position) {
|
|
console.assert(position >= 0);
|
|
console.assert(position < input.length);
|
|
|
|
let length = input.length;
|
|
let propertyValues = [];
|
|
while (true) {
|
|
if (_isIdentifierCharacter(input[position])) {
|
|
let identifierStartPosition = position;
|
|
var [propertyValue, position] = _parseIdentifier(input, position);
|
|
if (!_isValidRequiredOrAllowedPropertyValueIdentifier(propertyValue)) {
|
|
console.error(
|
|
"Unrecognized property value identifier: " + propertyValue
|
|
);
|
|
return [null, identifierStartPosition];
|
|
}
|
|
propertyValues.push(new NamedCharacterClass(propertyValue));
|
|
} else if (input[position] == CHARACTER_CLASS_START_SENTINEL) {
|
|
var [propertyValue, position] = _parseCustomCharacterClass(
|
|
input,
|
|
position
|
|
);
|
|
if (propertyValue && propertyValue.length) {
|
|
propertyValues.push(new CustomCharacterClass(propertyValue));
|
|
}
|
|
} else {
|
|
console.error(
|
|
"Failed to find start of property value: " + input.substr(position)
|
|
);
|
|
return [null, position];
|
|
}
|
|
|
|
position = _indexOfNonWhitespaceCharacter(input, position);
|
|
if (position >= length || input[position] === PROPERTY_SEPARATOR) {
|
|
break;
|
|
}
|
|
|
|
if (input[position] === PROPERTY_VALUE_SEPARATOR) {
|
|
position = _indexOfNonWhitespaceCharacter(input, position + 1);
|
|
if (position >= length) {
|
|
console.error(
|
|
"Found end-of-line instead of start of next property value"
|
|
);
|
|
return [null, position];
|
|
}
|
|
continue;
|
|
}
|
|
|
|
console.error(
|
|
"Failed to find start of next property or property value: " +
|
|
input.substr(position)
|
|
);
|
|
return [null, position];
|
|
}
|
|
return [propertyValues, position];
|
|
}
|
|
|
|
function _parsePasswordRule(input, position) {
|
|
console.assert(position >= 0);
|
|
console.assert(position < input.length);
|
|
console.assert(_isIdentifierCharacter(input[position]));
|
|
|
|
let length = input.length;
|
|
|
|
let mayBeIdentifierStartPosition = position;
|
|
var [identifier, position] = _parseIdentifier(input, position);
|
|
if (!Object.values(RuleName).includes(identifier)) {
|
|
console.error("Unrecognized property name: " + identifier);
|
|
return [null, mayBeIdentifierStartPosition];
|
|
}
|
|
|
|
if (position >= length) {
|
|
console.error("Found end-of-line instead of start of property value");
|
|
return [null, position];
|
|
}
|
|
|
|
if (input[position] !== PROPERTY_VALUE_START_SENTINEL) {
|
|
console.error(
|
|
"Failed to find start of property value: " + input.substr(position)
|
|
);
|
|
return [null, position];
|
|
}
|
|
|
|
let property = { name: identifier, value: null };
|
|
|
|
position = _indexOfNonWhitespaceCharacter(input, position + 1);
|
|
// Empty value
|
|
if (position >= length || input[position] === PROPERTY_SEPARATOR) {
|
|
return [new Rule(property.name, property.value), position];
|
|
}
|
|
|
|
switch (identifier) {
|
|
case RuleName.ALLOWED:
|
|
case RuleName.REQUIRED: {
|
|
var [
|
|
propertyValue,
|
|
position,
|
|
] = _parsePasswordRequiredOrAllowedPropertyValue(input, position);
|
|
if (propertyValue) {
|
|
property.value = propertyValue;
|
|
}
|
|
return [new Rule(property.name, property.value), position];
|
|
}
|
|
case RuleName.MAX_CONSECUTIVE: {
|
|
var [propertyValue, position] = _parseMaxConsecutivePropertyValue(
|
|
input,
|
|
position
|
|
);
|
|
if (propertyValue) {
|
|
property.value = propertyValue;
|
|
}
|
|
return [new Rule(property.name, property.value), position];
|
|
}
|
|
case RuleName.MIN_LENGTH:
|
|
case RuleName.MAX_LENGTH: {
|
|
var [propertyValue, position] = _parseMinLengthMaxLengthPropertyValue(
|
|
input,
|
|
position
|
|
);
|
|
if (propertyValue) {
|
|
property.value = propertyValue;
|
|
}
|
|
return [new Rule(property.name, property.value), position];
|
|
}
|
|
}
|
|
console.assert(false, SHOULD_NOT_BE_REACHED);
|
|
}
|
|
|
|
function _parseMinLengthMaxLengthPropertyValue(input, position) {
|
|
return _parseInteger(input, position);
|
|
}
|
|
|
|
function _parseMaxConsecutivePropertyValue(input, position) {
|
|
return _parseInteger(input, position);
|
|
}
|
|
|
|
function _parseInteger(input, position) {
|
|
console.assert(position >= 0);
|
|
console.assert(position < input.length);
|
|
|
|
if (!_isASCIIDigit(input[position])) {
|
|
console.error(
|
|
"Failed to parse value of type integer; not a number: " +
|
|
input.substr(position)
|
|
);
|
|
return [null, position];
|
|
}
|
|
|
|
let length = input.length;
|
|
let initialPosition = position;
|
|
let result = 0;
|
|
do {
|
|
result = 10 * result + parseInt(input[position], 10);
|
|
++position;
|
|
} while (
|
|
position < length &&
|
|
input[position] !== PROPERTY_SEPARATOR &&
|
|
_isASCIIDigit(input[position])
|
|
);
|
|
|
|
if (position >= length || input[position] === PROPERTY_SEPARATOR) {
|
|
return [result, position];
|
|
}
|
|
|
|
console.error(
|
|
"Failed to parse value of type integer; not a number: " +
|
|
input.substr(initialPosition)
|
|
);
|
|
return [null, position];
|
|
}
|
|
|
|
function _parsePasswordRulesInternal(input) {
|
|
let parsedProperties = [];
|
|
let length = input.length;
|
|
|
|
var position = _indexOfNonWhitespaceCharacter(input);
|
|
while (position < length) {
|
|
if (!_isIdentifierCharacter(input[position])) {
|
|
console.warn(
|
|
"Failed to find start of property: " + input.substr(position)
|
|
);
|
|
return parsedProperties;
|
|
}
|
|
|
|
var [parsedProperty, position] = _parsePasswordRule(input, position);
|
|
if (parsedProperty && parsedProperty.value) {
|
|
parsedProperties.push(parsedProperty);
|
|
}
|
|
|
|
position = _indexOfNonWhitespaceCharacter(input, position);
|
|
if (position >= length) {
|
|
break;
|
|
}
|
|
|
|
if (input[position] === PROPERTY_SEPARATOR) {
|
|
position = _indexOfNonWhitespaceCharacter(input, position + 1);
|
|
if (position >= length) {
|
|
return parsedProperties;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
console.error(
|
|
"Failed to find start of next property: " + input.substr(position)
|
|
);
|
|
return null;
|
|
}
|
|
|
|
return parsedProperties;
|
|
}
|
|
|
|
function parsePasswordRules(input, formatRulesForMinifiedVersion) {
|
|
let passwordRules = _parsePasswordRulesInternal(input) || [];
|
|
|
|
// When formatting rules for minified version, we should keep the formatted rules
|
|
// as similar to the input as possible. Avoid copying required rules to allowed rules.
|
|
let suppressCopyingRequiredToAllowed = formatRulesForMinifiedVersion;
|
|
|
|
let newPasswordRules = [];
|
|
let newAllowedValues = [];
|
|
let minimumMaximumConsecutiveCharacters = null;
|
|
let maximumMinLength = 0;
|
|
let minimumMaxLength = null;
|
|
|
|
for (let rule of passwordRules) {
|
|
switch (rule.name) {
|
|
case RuleName.MAX_CONSECUTIVE:
|
|
minimumMaximumConsecutiveCharacters = minimumMaximumConsecutiveCharacters
|
|
? Math.min(rule.value, minimumMaximumConsecutiveCharacters)
|
|
: rule.value;
|
|
break;
|
|
|
|
case RuleName.MIN_LENGTH:
|
|
maximumMinLength = Math.max(rule.value, maximumMinLength);
|
|
break;
|
|
|
|
case RuleName.MAX_LENGTH:
|
|
minimumMaxLength = minimumMaxLength
|
|
? Math.min(rule.value, minimumMaxLength)
|
|
: rule.value;
|
|
break;
|
|
|
|
case RuleName.REQUIRED:
|
|
rule.value = _canonicalizedPropertyValues(
|
|
rule.value,
|
|
formatRulesForMinifiedVersion
|
|
);
|
|
newPasswordRules.push(rule);
|
|
if (!suppressCopyingRequiredToAllowed) {
|
|
newAllowedValues = newAllowedValues.concat(rule.value);
|
|
}
|
|
break;
|
|
|
|
case RuleName.ALLOWED:
|
|
newAllowedValues = newAllowedValues.concat(rule.value);
|
|
break;
|
|
}
|
|
}
|
|
|
|
newAllowedValues = _canonicalizedPropertyValues(
|
|
newAllowedValues,
|
|
suppressCopyingRequiredToAllowed
|
|
);
|
|
if (!suppressCopyingRequiredToAllowed && !newAllowedValues.length) {
|
|
newAllowedValues = [new NamedCharacterClass(Identifier.ASCII_PRINTABLE)];
|
|
}
|
|
if (newAllowedValues.length) {
|
|
newPasswordRules.push(new Rule(RuleName.ALLOWED, newAllowedValues));
|
|
}
|
|
|
|
if (minimumMaximumConsecutiveCharacters !== null) {
|
|
newPasswordRules.push(
|
|
new Rule(RuleName.MAX_CONSECUTIVE, minimumMaximumConsecutiveCharacters)
|
|
);
|
|
}
|
|
|
|
if (maximumMinLength > 0) {
|
|
newPasswordRules.push(new Rule(RuleName.MIN_LENGTH, maximumMinLength));
|
|
}
|
|
|
|
if (minimumMaxLength !== null) {
|
|
newPasswordRules.push(new Rule(RuleName.MAX_LENGTH, minimumMaxLength));
|
|
}
|
|
|
|
return newPasswordRules;
|
|
}
|