var crypto = require('crypto'); const RANDOM_BATCH_SIZE = 256; var randomIndex; var randomBytes; var getNextRandomValue = function() { if (randomIndex === undefined || randomIndex >= randomBytes.length) { randomIndex = 0; randomBytes = crypto.randomBytes(RANDOM_BATCH_SIZE); } var result = randomBytes[randomIndex]; randomIndex += 1; return result; }; // Generates a random number var randomNumber = function(max) { // gives a number between 0 (inclusive) and max (exclusive) var rand = getNextRandomValue(); while (rand >= 256 - (256 % max)) { rand = getNextRandomValue(); } return rand % max; }; // Possible combinations var lowercase = 'abcdefghijklmnopqrstuvwxyz', uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', numbers = '0123456789', symbols = '!@#$%^&*()+_-=}{[]|:;"/?.><,`~', similarCharacters = /[ilLI|`oO0]/g, strictRules = [ { name: 'lowercase', rule: /[a-z]/ }, { name: 'uppercase', rule: /[A-Z]/ }, { name: 'numbers', rule: /[0-9]/ }, { name: 'symbols', rule: /[!@#$%^&*()+_\-=}{[\]|:;"/?.><,`~]/ } ]; var generate = function(options, pool) { var password = '', optionsLength = options.length, poolLength = pool.length; for (var i = 0; i < optionsLength; i++) { password += pool[randomNumber(poolLength)]; } if (options.strict) { // Iterate over each rule, checking to see if the password works. var fitsRules = strictRules.every(function(rule) { // If the option is not checked, ignore it. if (options[rule.name] == false) return true; // Treat symbol differently if explicit string is provided if (rule.name === 'symbols' && typeof options[rule.name] === 'string') { // Create a regular expression from the provided symbols var re = new RegExp('['+options[rule.name]+']'); return re.test(password); } // Run the regex on the password and return whether // or not it matches. return rule.rule.test(password); }); // If it doesn't fit the rules, generate a new one (recursion). if (!fitsRules) return generate(options, pool); } return password; }; // Generate a random password. module.exports.generate = function(options) { // Set defaults. options = options || {}; if (!Object.prototype.hasOwnProperty.call(options, 'length')) options.length = 10; if (!Object.prototype.hasOwnProperty.call(options, 'numbers')) options.numbers = false; if (!Object.prototype.hasOwnProperty.call(options, 'symbols')) options.symbols = false; if (!Object.prototype.hasOwnProperty.call(options, 'exclude')) options.exclude = ''; if (!Object.prototype.hasOwnProperty.call(options, 'uppercase')) options.uppercase = true; if (!Object.prototype.hasOwnProperty.call(options, 'lowercase')) options.lowercase = true; if (!Object.prototype.hasOwnProperty.call(options, 'excludeSimilarCharacters')) options.excludeSimilarCharacters = false; if (!Object.prototype.hasOwnProperty.call(options, 'strict')) options.strict = false; if (options.strict) { var minStrictLength = 1 + (options.numbers ? 1 : 0) + (options.symbols ? 1 : 0) + (options.uppercase ? 1 : 0); if (minStrictLength > options.length) { throw new TypeError('Length must correlate with strict guidelines'); } } // Generate character pool var pool = ''; // lowercase if (options.lowercase) { pool += lowercase; } // uppercase if (options.uppercase) { pool += uppercase; } // numbers if (options.numbers) { pool += numbers; } // symbols if (options.symbols) { if (typeof options.symbols === 'string') { pool += options.symbols; } else { pool += symbols; } } // Throw error if pool is empty. if (!pool) { throw new TypeError('At least one rule for pools must be true'); } // similar characters if (options.excludeSimilarCharacters) { pool = pool.replace(similarCharacters, ''); } // excludes characters from the pool var i = options.exclude.length; while (i--) { pool = pool.replace(options.exclude[i], ''); } var password = generate(options, pool); return password; }; // Generates multiple passwords at once with the same options. module.exports.generateMultiple = function(amount, options) { var passwords = []; for (var i = 0; i < amount; i++) { passwords[i] = module.exports.generate(options); } return passwords; };