Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add emoji support for passwords with expanded Unicode ranges #841

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 35 additions & 49 deletions tools/PasswordRulesParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@

if (!console) {
console = {
assert: function() { },
error: function() { },
warn: function() { },
assert: function () { },
error: function () { },
warn: function () { },
};
}

Expand All @@ -17,6 +17,7 @@ const Identifier = {
SPECIAL: "special",
UNICODE: "unicode",
UPPER: "upper",
EMOJI: "emoji",
};

const RuleName = {
Expand All @@ -38,8 +39,7 @@ const SPACE_CODE_POINT = " ".codePointAt(0);
const SHOULD_NOT_BE_REACHED = "Should not be reached";

class Rule {
constructor(name, value)
{
constructor(name, value) {
this._name = name;
this.value = value;
}
Expand All @@ -48,8 +48,7 @@ class Rule {
};

class NamedCharacterClass {
constructor(name)
{
constructor(name) {
console.assert(_isValidRequiredOrAllowedPropertyValueIdentifier(name));
this._name = name;
}
Expand All @@ -59,8 +58,7 @@ class NamedCharacterClass {
};

class CustomCharacterClass {
constructor(characters)
{
constructor(characters) {
console.assert(characters instanceof Array);
this._characters = characters;
}
Expand All @@ -71,45 +69,38 @@ class CustomCharacterClass {

// MARK: Lexer functions

function _isIdentifierCharacter(c)
{
function _isIdentifierCharacter(c) {
console.assert(c.length === 1);
return c >= "a" && c <= "z" || c >= "A" && c <= "Z" || c === "-";
}

function _isASCIIDigit(c)
{
function _isASCIIDigit(c) {
console.assert(c.length === 1);
return c >= "0" && c <= "9";
}

function _isASCIIPrintableCharacter(c)
{
function _isASCIIPrintableCharacter(c) {
console.assert(c.length === 1);
return c >= " " && c <= "~";
}

function _isASCIIWhitespace(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)
{
function _bitSetIndexForCharacter(c) {
console.assert(c.length == 1);
return c.codePointAt(0) - SPACE_CODE_POINT;
}

function _characterAtBitSetIndex(index)
{
function _characterAtBitSetIndex(index) {
return String.fromCodePoint(index + SPACE_CODE_POINT);
}

function _markBitsForNamedCharacterClass(bitSet, namedCharacterClass)
{
function _markBitsForNamedCharacterClass(bitSet, namedCharacterClass) {
console.assert(bitSet instanceof Array);
console.assert(namedCharacterClass.name !== Identifier.UNICODE);
console.assert(namedCharacterClass.name !== Identifier.ASCII_PRINTABLE);
Expand All @@ -128,20 +119,26 @@ function _markBitsForNamedCharacterClass(bitSet, namedCharacterClass)
bitSet.fill(true, _bitSetIndexForCharacter("["), _bitSetIndexForCharacter("`") + 1);
bitSet.fill(true, _bitSetIndexForCharacter("{"), _bitSetIndexForCharacter("~") + 1);
}
else if (namedCharacterClass.name === Identifier.EMOJI) {
// Emoji Unicode range; this will need to account for a range, often between U+1F600 and U+1F64F.
const emojiStart = 0x1F600; // range start
const emojiEnd = 0x1F64F; // range end
for (let i = emojiStart; i <= emojiEnd; i++) {
bitSet[i] = true; // This assumes bitSet can index by code points directly.
}
}
else {
console.assert(false, SHOULD_NOT_BE_REACHED, namedCharacterClass);
}
}

function _markBitsForCustomCharacterClass(bitSet, customCharacterClass)
{
function _markBitsForCustomCharacterClass(bitSet, customCharacterClass) {
for (let character of customCharacterClass.characters) {
bitSet[_bitSetIndexForCharacter(character)] = true;
}
}

function _canonicalizedPropertyValues(propertyValues, keepCustomCharacterClassFormatCompliant)
{
function _canonicalizedPropertyValues(propertyValues, keepCustomCharacterClassFormatCompliant) {
let asciiPrintableBitSet = new Array("~".codePointAt(0) - " ".codePointAt(0) + 1);

for (let propertyValue of propertyValues) {
Expand Down Expand Up @@ -264,8 +261,7 @@ function _canonicalizedPropertyValues(propertyValues, keepCustomCharacterClassFo

// MARK: Parser functions

function _indexOfNonWhitespaceCharacter(input, position = 0)
{
function _indexOfNonWhitespaceCharacter(input, position = 0) {
console.assert(position >= 0);
console.assert(position <= input.length);

Expand All @@ -276,8 +272,7 @@ function _indexOfNonWhitespaceCharacter(input, position = 0)
return position;
}

function _parseIdentifier(input, position)
{
function _parseIdentifier(input, position) {
console.assert(position >= 0);
console.assert(position < input.length);
console.assert(_isIdentifierCharacter(input[position]));
Expand All @@ -297,13 +292,11 @@ function _parseIdentifier(input, position)
return [seenIdentifiers.join(""), position];
}

function _isValidRequiredOrAllowedPropertyValueIdentifier(identifier)
{
function _isValidRequiredOrAllowedPropertyValueIdentifier(identifier) {
return identifier && Object.values(Identifier).includes(identifier.toLowerCase());
}

function _parseCustomCharacterClass(input, position)
{
function _parseCustomCharacterClass(input, position) {
console.assert(position >= 0);
console.assert(position < input.length);
console.assert(input[position] === CHARACTER_CLASS_START_SENTINEL);
Expand Down Expand Up @@ -352,8 +345,7 @@ function _parseCustomCharacterClass(input, position)
return [null, position];
}

function _parsePasswordRequiredOrAllowedPropertyValue(input, position)
{
function _parsePasswordRequiredOrAllowedPropertyValue(input, position) {
console.assert(position >= 0);
console.assert(position < input.length);

Expand Down Expand Up @@ -400,8 +392,7 @@ function _parsePasswordRequiredOrAllowedPropertyValue(input, position)
return [propertyValues, position];
}

function _parsePasswordRule(input, position)
{
function _parsePasswordRule(input, position) {
console.assert(position >= 0);
console.assert(position < input.length);
console.assert(_isIdentifierCharacter(input[position]));
Expand Down Expand Up @@ -461,18 +452,15 @@ function _parsePasswordRule(input, position)
console.assert(false, SHOULD_NOT_BE_REACHED);
}

function _parseMinLengthMaxLengthPropertyValue(input, position)
{
function _parseMinLengthMaxLengthPropertyValue(input, position) {
return _parseInteger(input, position);
}

function _parseMaxConsecutivePropertyValue(input, position)
{
function _parseMaxConsecutivePropertyValue(input, position) {
return _parseInteger(input, position);
}

function _parseInteger(input, position)
{
function _parseInteger(input, position) {
console.assert(position >= 0);
console.assert(position < input.length);

Expand All @@ -497,8 +485,7 @@ function _parseInteger(input, position)
return [null, position];
}

function _parsePasswordRulesInternal(input)
{
function _parsePasswordRulesInternal(input) {
let parsedProperties = [];
let length = input.length;

Expand Down Expand Up @@ -535,8 +522,7 @@ function _parsePasswordRulesInternal(input)
return parsedProperties;
}

function parsePasswordRules(input, formatRulesForMinifiedVersion)
{
function parsePasswordRules(input, formatRulesForMinifiedVersion) {
let passwordRules = _parsePasswordRulesInternal(input) || [];

// When formatting rules for minified version, we should keep the formatted rules
Expand Down