buttercup-pw/buttercup-core

View on GitHub
source/insight/vault.ts

Summary

Maintainability
A
3 hrs
Test Coverage
import { Vault } from "../core/Vault.js";
import { VaultInsights } from "../types.js";

const DIGITS = /[0-9]+/;
const LOWER_CASE = /[a-z]+/;
const OTHER = /[^0-9a-zA-Z`~!@#$%^&*()=+\[\]{};:'",.<>\/?|\\_£€-]+/;
const SYMBOLS = /[`~!@#$%^&*()=+\[\]{};:'",.<>\/?|\\_£€-]+/;
const UPPER_CASE = /[A-Z]+/;

/**
 * Generate insights for a vault instance
 * @param vault The vault instance
 * @private
 */
export function generateVaultInsights(vault: Vault): VaultInsights {
    let groupCount = 0,
        entryCount = 0,
        trashEntryCount = 0,
        trashGroupCount = 0,
        longestPasswordLength = null,
        shortestPasswordLength = null,
        weakPasswords = 0;
    const passwordCounts = {};
    const usernameSet = new Set();
    const entryPasswordLengths = [];
    const processGroup = (group) => {
        const entries = group.getEntries();
        groupCount += 1;
        entryCount += entries.length;
        entries.forEach((entry) => {
            const password = entry.getProperty("password");
            if (typeof password === "string") {
                entryPasswordLengths.push(password.length);
                if (longestPasswordLength === null || password.length > longestPasswordLength) {
                    longestPasswordLength = password.length;
                }
                if (shortestPasswordLength === null || password.length < shortestPasswordLength) {
                    shortestPasswordLength = password.length;
                }
            }
            const username = entry.getProperty("username");
            if (["login", "website"].includes(entry.getType())) {
                if (username) {
                    usernameSet.add(username);
                }
                if (password) {
                    passwordCounts[password] = (passwordCounts[password] || 0) + 1;
                    if (isWeak(password)) {
                        weakPasswords += 1;
                    }
                }
            }
        });
        group.getGroups().forEach((subGroup) => processGroup(subGroup));
    };
    vault.getGroups().forEach((group) => processGroup(group));
    const avgPassLen =
        entryPasswordLengths.reduce((total, next) => total + next, 0) / entryPasswordLengths.length;
    const trashGroup = vault.getTrashGroup();
    if (trashGroup) {
        const processTrashGroup = (group) => {
            const subGroups = group.getGroups();
            trashGroupCount += subGroups.length;
            subGroups.forEach((subGroup) => processTrashGroup(subGroup));
            trashEntryCount += group.getEntries().length;
        };
        processTrashGroup(trashGroup);
    }
    const duplicatePasswords = Object.keys(passwordCounts).reduce(
        (total, nextPass) => total + (passwordCounts[nextPass] > 1 ? passwordCounts[nextPass] : 0),
        0
    );
    return {
        avgPassLen,
        entries: entryCount,
        groups: groupCount,
        longPassLen: longestPasswordLength,
        shortPassLen: shortestPasswordLength,
        trashEntries: trashEntryCount,
        trashGroups: trashGroupCount,
        usernames: usernameSet.size,
        duplicatePasswords,
        weakPasswords
    };
}

function isWeak(password: string): boolean {
    if (!password) return true;
    if (password.length < 10) return true;
    const matchingSets = [DIGITS, LOWER_CASE, OTHER, SYMBOLS, UPPER_CASE].filter((set) =>
        set.test(password)
    ).length;
    if (matchingSets >= 3) return false;
    return true;
}