passbolt-styleguide
Version:
Passbolt styleguide contains common styling assets used by the different sites, plugin, etc.
144 lines (130 loc) • 5.34 kB
JavaScript
/**
* Passbolt ~ Open source password manager for teams
* Copyright (c) Passbolt SA (https://www.passbolt.com)
*
* Licensed under GNU Affero General Public License version 3 of the or any later version.
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Passbolt SA (https://www.passbolt.com)
* @license https://opensource.org/licenses/AGPL-3.0 AGPL License
* @link https://www.passbolt.com Passbolt(tm)
* @since 3.3.0
*/
import {default as PassphraseGeneratorWords} from "./PassphraseGeneratorWords";
/**
* Returns a number between the given min and max
* @param min The minimum number
* @param max The maximum number
*/
function randomNumberRange(min, max) {
const arr = new Uint32Array(1);
window.crypto.getRandomValues(arr);
const random = arr[0] / (0xffffffff + 1);
return Math.floor(random * (max - min + 1)) + min;
}
/**
* Returns randomly a word from a given list of word and apply the given word case
* @param words A list of words
* @param wordCase A case strategy to apply to the word
*/
function extractWordWithCase(words, wordCase) {
const extractWord = () => words[randomNumberRange(0, words.length - 1)];
const toCamelCase = word => word.charAt(0).toUpperCase() + word.slice(1);
switch (wordCase) {
case "lowercase":
return extractWord().toLowerCase();
case "uppercase":
return extractWord().toUpperCase();
case "camelcase":
return toCamelCase(extractWord());
default:
return extractWord();
}
}
/**
* Detects if the given secret is a Passbolt passphrase.
* If yes, it returns the number of words, the separator and the flag to tell it's a passphrase
* @param secret
*/
function detectPassphrase(secret) {
const passwordDetected = {
isPassphrase: false
};
if (!secret) {
return passwordDetected;
}
// Remove all the words from the dictionary present in the secret and keep the count.
const separatorsSecret = PassphraseGeneratorWords['en-UK'].reduce((result, word) => {
const remainingSecret = result.remainingSecret.replace(new RegExp(word, 'g'), '');
const newNumberReplacement = (result.remainingSecret.length - remainingSecret.length) / word.length;
return {
numberReplacement: result.numberReplacement + newNumberReplacement,
remainingSecret: remainingSecret
};
}, {numberReplacement: 0, remainingSecret: secret.toLowerCase()});
const remainingSecret = separatorsSecret.remainingSecret;
/*
* For a passphrase we exepect to have a separator count of <n-detected-words> - 1
* If 2 words are detected, we expected to have only 1 separator. But, with the modulo
* computation that happens next, the entropy will be erronous in that case (every interger is a multiple of 1)
* So we handle that specific case and expect the following format:
* `${word1}${separator}${word2}`
*/
const numberSeparators = separatorsSecret.numberReplacement - 1;
if (numberSeparators === 1) {
// the resulting string might not be present at all due to the way we remove the words previously
const isPassword = secret.indexOf(remainingSecret) === -1
|| secret.startsWith(remainingSecret)
|| secret.endsWith(remainingSecret);
if (isPassword) {
return passwordDetected;
}
return {
numberWords: 2,
separator: remainingSecret,
isPassphrase: true
};
}
const hasEmptySeparator = remainingSecret.length == 0;
if (hasEmptySeparator) {
return {
numberWords: separatorsSecret.numberReplacement,
separator: '',
isPassphrase: true
};
}
// From the remaining, check if a separator can be identified.
const cannotBeSplitSeparatorsWithSameLength = remainingSecret.length % numberSeparators !== 0;
if (cannotBeSplitSeparatorsWithSameLength) {
return passwordDetected;
}
const lengthSeparators = remainingSecret.length / numberSeparators;
const firstSeparator = remainingSecret.substring(0, lengthSeparators);
const firstSeparatorRegexEscaped = String(firstSeparator).replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1');
const isPassphrase = remainingSecret.replace(new RegExp(firstSeparatorRegexEscaped, 'g'), '').length === 0;
return {
numberWords: separatorsSecret.numberReplacement,
separator: firstSeparator,
isPassphrase: isPassphrase && !secret.startsWith(firstSeparator) && !secret.endsWith(firstSeparator)
};
}
/**
* Passphrase generator using diceware method from a file containing words
*/
export const PassphraseGenerator = {
generate: configuration => {
const wordCount = configuration.default_options.word_count;
const canGenerate = wordCount >= configuration.default_options.min_word && wordCount <= configuration.default_options.max_word;
if (canGenerate) {
const wordCase = configuration.default_options.word_case;
const words = PassphraseGeneratorWords['en-UK'];
const extractWordMapper = () => extractWordWithCase(words, wordCase);
const wordsGenerated = Array.from({length: wordCount}, extractWordMapper);
const secret = wordsGenerated.join(configuration.default_options.separator);
return secret;
}
return '';
},
detectPassphrase
};