Source: functions/strengthChecker.js

/**
* simplePass - A JavaScript password generator.
* Copyright (C) 2023  Jordan Vezina(staticBanter)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/
'use strict';
import calculateMaxPossibleCharacters from "./calculateMaxPossibleCharacters.js";
/**
 * @file
 * @module strengthChecker
 */
/**
 * A that provides various form of information about a password such as the:
 * - Bits of entropy calculated as ```E = l(log2(p))```; where:
 *  - *E* = Entropy of the password,
 *  - *l* = Length of the password,
 *  - *p* = Is the number of Possibilities (number of possible characters).
 * - Possible combinations for the password,
 * - Binary string representation of the password,
 * - Length of the binary string,
 * - Average bit length of each character.
 * @function strengthChecker
 * @param {string} password The password to check the strength of.
 * @param {strengthCheckerConstraints} constraints An [object]{@link module:strengthCheckerConstraints} representing constraints the password was created under.
 * @requires calculateMaxPossibleCharacters
 * @returns {strengthCheckedPassword} An [object]{@link module:strengthCheckedPassword} containing the strength checked password, its strength score, and the bit entropy of the password.
 */
export default function strengthChecker(password, constraints, compressions) {
    const numberOfCharacters = calculateMaxPossibleCharacters({
        characterSets: constraints.characterSets.used
            .filter((item) => {
            return constraints.characterSets.available.includes(item);
        }),
        excludeCharacters: constraints.excludeCharacters
    });
    const binaryStringDelimiter = ' ';
    const binaryString = password.split('')
        .map((char) => {
        const bin = char.charCodeAt(0).toString(2);
        return ((Array((Math.ceil(bin.length / 8) * 8)
            - bin.length + 1)
            .join("0"))
            + bin);
    })
        .join(binaryStringDelimiter);
    let averageBitLength = 0;
    binaryString.split(binaryStringDelimiter)
        .forEach((byte) => {
        averageBitLength += byte.length;
    });
    let uniqueCharacters = '';
    password.split('').forEach(element => {
        if (!uniqueCharacters.includes(element)) {
            uniqueCharacters += element;
        }
    });
    if (compressions) {
        const compressionResponses = [];
        for (let i = 0; i < compressions.length; i++) {
            const byteArray = new TextEncoder().encode(password);
            const cs = new CompressionStream(compressions[i]);
            const writer = cs.writable.getWriter();
            writer.write(byteArray);
            writer.close();
            compressionResponses.push(new Response(cs.readable).arrayBuffer());
        }
        if (compressions.length) {
            return Promise.all(compressionResponses)
                .then((compressionBuffers) => {
                const compressionStats = {};
                let averageCompression = 0;
                compressions.forEach((compression, index) => {
                    compressionStats[compression] = compressionBuffers[index].byteLength;
                    averageCompression += compressionBuffers[index].byteLength;
                });
                compressionStats['average'] = (averageCompression / compressionBuffers.length);
                return {
                    password: password,
                    entropy: (password.length * Math.log2(numberOfCharacters)),
                    possibleCombinations: (numberOfCharacters ** password.length),
                    binaryStringLength: binaryString.replaceAll(binaryStringDelimiter, '').length,
                    binaryString: binaryString,
                    averageCharacterBitLength: (averageBitLength / password.length),
                    uniqueCharactersPercentage: ((uniqueCharacters.length / password.length) * 100),
                    compressionStats: compressionStats
                };
            });
        }
    }
    return {
        password: password,
        entropy: (password.length * Math.log2(numberOfCharacters)),
        possibleCombinations: (numberOfCharacters ** password.length),
        binaryStringLength: binaryString.replaceAll(binaryStringDelimiter, '').length,
        binaryString: binaryString,
        averageCharacterBitLength: (averageBitLength / password.length),
        uniqueCharactersPercentage: ((uniqueCharacters.length / password.length) * 100),
    };
}