diff --git a/app/scripts/util/generators/password-generator.js b/app/scripts/util/generators/password-generator.js index bf7f6762..88cdae7d 100644 --- a/app/scripts/util/generators/password-generator.js +++ b/app/scripts/util/generators/password-generator.js @@ -1,5 +1,6 @@ import kdbxweb from 'kdbxweb'; import { phonetic } from 'util/generators/phonetic'; +import { shuffle } from 'util/fn'; const CharRanges = { upper: 'ABCDEFGHJKLMNPQRSTUVWXYZ', @@ -41,18 +42,33 @@ const PasswordGenerator = { } const rangesByPatternChar = { ...DefaultCharRangesByPattern, - 'X': ranges.join(''), 'I': opts.include || '' }; const pattern = opts.pattern || 'X'; + + const rangeIxRandomBytes = kdbxweb.Random.getBytes(opts.length); + const rangeCharRandomBytes = kdbxweb.Random.getBytes(opts.length); + const defaultRangeGeneratedChars = []; + for (let i = 0; i < opts.length; i++) { + const rangeIx = i < ranges.length ? i : rangeIxRandomBytes[i] % ranges.length; + const range = ranges[rangeIx]; + const char = range[rangeCharRandomBytes[i] % range.length]; + defaultRangeGeneratedChars.push(char); + } + shuffle(defaultRangeGeneratedChars); + const randomBytes = kdbxweb.Random.getBytes(opts.length); const chars = []; for (let i = 0; i < opts.length; i++) { const rand = Math.round(Math.random() * 1000) + randomBytes[i]; const patternChar = pattern[i % pattern.length]; - const range = rangesByPatternChar[patternChar]; - const char = range ? range[rand % range.length] : patternChar; - chars.push(char); + if (patternChar === 'X') { + chars.push(defaultRangeGeneratedChars.pop()); + } else { + const range = rangesByPatternChar[patternChar]; + const char = range ? range[rand % range.length] : patternChar; + chars.push(char); + } } return chars.join(''); }, diff --git a/release-notes.md b/release-notes.md index c127fcbf..eed50019 100644 --- a/release-notes.md +++ b/release-notes.md @@ -4,6 +4,7 @@ Release notes `+` optimized memory consumption for large files `+` option to use short-lived tokens in cloud storages `+` opening XML and CSV files using the Open button +`*` password generator now includes all selected character ranges `-` legacy auto-type removed ##### v1.17.5 (2021-03-27) diff --git a/test/src/util/generators/password-generator.js b/test/src/util/generators/password-generator.js index ff09cf1d..5afa1dc8 100644 --- a/test/src/util/generators/password-generator.js +++ b/test/src/util/generators/password-generator.js @@ -57,4 +57,24 @@ describe('PasswordGenerator', () => { }) ).to.match(/^([A-Z][a-z][0-9][0-9A-Z@#][@#]-){10}$/); }); + + it('should include all groups of characters at least once', () => { + for (let i = 0; i < 10; i++) { + const password = PasswordGenerator.generate({ + length: 6, + upper: true, + lower: true, + digits: true, + brackets: true, + special: true, + ambiguous: true + }); + expect(password).to.match(/[A-Z]/); + expect(password).to.match(/[a-z]/); + expect(password).to.match(/[0-9]/); + expect(password).to.match(/[(){}[\]<>]/); + expect(password).to.match(/[!-\/:-@[-`~]/); + expect(password).to.match(/[O0oIl]/); + } + }); });